Errors

Every failed request answers with an HTTP status and a JSON body under error:

{
  "error": {
    "message": "Customer not found",
    "code": "NOT_FOUND"
  }
}

message is written to be shown to a person. code is stable and is what your own code should branch on.

Status codes

StatusCodeWhat happened
400VALIDATION_ERRORA parameter or body field is missing, the wrong type, or outside its allowed range.
400BAD_REQUESTThe request is well formed but cannot be carried out, including reaching a plan limit.
403UNAUTHORIZEDNo credential, or one that is not valid or has expired.
403FORBIDDENAuthenticated, but not allowed to do this: a key missing the endpoint's permission, an address off the allowlist, or a session-only endpoint.
404NOT_FOUNDNo such record, at least not in this organization.
409CONFLICTThe record cannot be created or changed because of one that already exists.
429TOO_MANY_REQUESTSRate limited. Read the Retry-After header.
500INTERNAL_SERVER_ERRORSomething broke on our side. Retrying is reasonable.

Note that a missing or invalid credential answers 403, not 401.

Validation errors

A VALIDATION_ERROR carries an issues array saying exactly which fields were wrong and where they were:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "issues": [
      {
        "code": "invalid_type",
        "expected": "number",
        "path": ["rating"],
        "message": "Invalid input: expected number, received string"
      }
    ]
  }
}

path is the route to the offending field, so ["items", 0, "productId"] means the first item in the list you sent.

Filtering has its own validation: an unknown field, an operator mixed with plain keys, or a value that does not fit the column all return 400 describing the problem. See Filtering.

Permission errors

A key that does not hold the endpoint's permission gets the missing scope back in the message, which is usually enough to fix it without reading anything else:

{
  "error": {
    "message": "This API key does not have the `reviews:write` permission.",
    "code": "FORBIDDEN"
  }
}

Update the key's permissions under Settings → API keys, or use a key that already holds it. See Authentication.

Plan limits

Reaching a limit on your plan is a 400 with BAD_REQUEST, worded as what you reached and what to do about it:

{
  "error": {
    "message": "You have reached the limit of 5 sites. Upgrade your plan to add more.",
    "code": "BAD_REQUEST"
  }
}

Some limits block the action outright and some only warn in the dashboard. See Plans and limits.

Rate limiting

A 429 carries a Retry-After header with whole seconds until the request can be afforded again. Wait that long rather than retrying immediately, and prefer larger pages to more requests: one call for 250 records costs the same as one call for 10.

HTTP/1.1 429 Too Many Requests
Retry-After: 3

See Rate limits for the size of the budget and what spends it.

Retrying

Retry 429 after the interval it gives you, and 500 with a backoff. Do not retry 400, 403, 404 or 409: the same request will fail the same way until something changes.

If you are retrying a request that records an event, send an idempotency key so a duplicate does not count twice. See Conventions.