UnifyCart Developers

Errors and Conventions

Status codes, error shapes, how money and dates are represented, and the conventions worth knowing before you start.

Status Codes

Code Means
200 Fine
201 Created
401 No token, or a token that is wrong, revoked or expired
403 Valid token without the ability for this endpoint
404 No such thing, or not visible to you
422 The request was understood and the data was not acceptable
429 Too many requests
500 Something broke at our end
502 We asked a third party and could not get an answer
503 The shop has not connected the service this endpoint needs

Treat 401 and 403 differently. A 401 means ask the merchant to reconnect. A 403 means you need a different ability and retrying will never work.

A 422 names the fields that failed. Show the merchant what was wrong rather than a generic failure.

Error Shape

Two shapes exist, and a client has to handle both.

Most errors come back with message, and a validation failure adds errors with the detail per field. Read message for display and the field detail for correcting.

{
  "message": "Invalid or expired token."
}

The terminal endpoints and the whole App API use error instead, sometimes with a code you can branch on. Checking message ?? error covers both.

What each one actually looks like

No token, 401:

{
  "message": "Unauthenticated."
}

A record that does not exist, 404:

{
  "message": "No query results for model [App\\Models\\Product]."
}

A validation failure, 422. Note that message summarises and errors carries the detail, so a form can be marked up field by field:

{
  "message": "The customer email field is required. (and 2 more errors)",
  "errors": {
    "customer_email": [
      "The customer email field is required."
    ],
    "customer_name": [
      "The customer name field is required."
    ],
    "items": [
      "The items field is required."
    ]
  }
}

A shop that has not connected the thing you are asking about, 503. This one carries a code, which is the stable part: the sentence is written to be shown to a person and may be reworded.

{
  "error": "Stripe is not connected for this shop, so card readers cannot be used.",
  "code": "stripe_not_configured"
}

Money

Money is a decimal string, not a number: "999.99".

Do not parse money into a float

Reading "19.99" into a floating point number and multiplying by three does not give 59.97 on every platform. Use a decimal type. This is the single most common source of penny differences between an integration and a shop.

Every monetary field follows this, including totals, tax, shipping and discounts.

Currency

A shop has a base currency and may display others. Figures on the API are in the shop's base currency unless a field says otherwise.

Do not assume a currency symbol from a locale. Read it from the shop.

Dates

ISO 8601 with a timezone. Convert to whatever you need at the point of display, and store what you were given.

A shop has its own timezone, and that is what decides which day an order belongs to in the merchant's own reports. If your reconciliation is a day out at month end, this is usually why.

Identifiers

Numeric ids are stable within one shop and meaningless across shops. A product id of 42 in two shops is two unrelated products.

Where you store references, store the shop as well as the id.

SKUs are the merchant's own codes and are not guaranteed unique across variants and products: the plain edition of a book legitimately shares the book's own code. Match on id where you can.

Nulls

A field that has no value is null rather than an empty string or zero. A price of null is not a price of zero, and a weight of null is not weightless: it is unknown, and the shop treats it as weightless only because it has no choice.

Lists

options on an order line is a list of {label, value}, not a map, because a label can legitimately appear twice and the order matters.

Code that assumes a map will silently drop the second of two identical labels.

Pagination

Standard envelope with data, current_page, last_page, total and per_page. Page using last_page rather than reading until empty.

Be a Good Citizen

Ask for what you need and keep it. Use webhooks instead of polling. Page once rather than repeatedly. Send a useful user agent so a merchant looking at their logs can tell who you are.

A shop is a small business's livelihood, and an integration hammering it at midday is a support call they make about you.