Skip to main content
Version: 2026-05

Errors

This is the canonical reference for the &Open API error model. Every endpoint reports failures the same way, so once you handle errors here you handle them everywhere. Each endpoint's reference page lists the specific failures you can expect from that call and links back to this page for the shared contract.

The error model

The API uses conventional HTTP status codes to signal the broad outcome of a request:

  • 2xx — the request succeeded.
  • 4xx — the request failed because of something in the request: a missing field, an invalid value, an unknown resource, or a permission problem. The body tells you exactly what to fix.
  • 5xx — something failed on our side. These are rare.

Every non-2xx response also carries a structured JSON body so failures are machine-readable, not just a status code.

Response body

All error responses share one shape: a top-level errors array, always an array — even when there is a single error. Validation failures return every problem at once, so a client can surface them together rather than one round-trip at a time.

{
"errors": [
{
"type": "validation_error",
"code": "required",
"message": "is required",
"param": "recipient.email"
},
{
"type": "validation_error",
"code": "invalid_reference",
"message": "does not exist",
"param": "campaign_id"
}
]
}

Each object in the array describes one problem:

FieldTypeDescription
typestringThe error category — one of the closed set below.
codestringA machine-readable code scoped to the type, in snake_case. An open enum (see Error codes).
messagestringA human-readable explanation of this specific occurrence. For logs and developers, not end users.
paramstringThe request field that caused the error, in dot notation (e.g. recipient.email). Present only for field-level errors.

Every response — success or error — also carries a Request-Id header. Include it when contacting support; it lets us find the exact request in our logs.

Status codes and categories

Every error has an HTTP status code and a type. Each type maps to one or more statuses, shown below. The table is the full set of error statuses the API returns, plus the closed list of type values — both taken directly from the API contract:

StatusMeaning
400The request is malformed or violates a precondition. Typical triggers are a missing or unsupported `AndOpen-API-Version` header, a request body that is not valid JSON, or pagination parameters that fail server-side validation (`limit` non-numeric or non-positive, `after` referencing a record that does not exist).
401Authentication failed — missing or invalid bearer token.
403Authorised but not permitted to access this resource.
404The requested resource does not exist.
422Validation error — the request was parseable but contains invalid data. All validation errors are returned at once.
429Too many requests — rate limit exceeded.
500Internal server error.
503Service unavailable — the tenant may be under maintenance. Check the `Retry-After` header.

Every error object carries a type from this closed set: validation_error, authentication_error, authorization_error, not_found_error, rate_limit_error, api_error.

type is a closed enum: it will not grow without a new API version, so it is safe to switch on exhaustively. code is open — see below.

Error codes

code is an open enum: it gives a specific, stable reason within the type, but new codes may be added under an existing type without a version bump. Match the codes you know; for any you don't, fall back to the type or message. Codes are always snake_case.

The codes below are the ones in use today, grouped by their type.

validation_error (422)

The request was well-formed but a value failed validation. All validation problems for a request are returned together, each with the param it applies to.

  • required — a required field was missing or empty.
  • invalid — the value is not acceptable for the field, in a way the more specific codes here don't cover.
  • invalid_format — the value is the right kind of thing but malformed — for example an email that isn't a valid address, or a malformed UUID.
  • invalid_reference — the value is a well-formed identifier, but no such record exists or it isn't reachable in this context (e.g. a campaign_id that doesn't belong to your tenant).
  • not_unique — the value duplicates one that must be unique.
  • out_of_stock — a requested line item cannot be fulfilled because the variant has no available stock. The param points at the offending line item.

authentication_error (401)

The request could not be authenticated. See Authentication for how to present a token, and Environments for the regional base URLs — a token is only valid in the region it was issued for.

  • missing_token — no Authorization header was provided.
  • invalid_token — the token is not recognised (wrong value, or presented to the wrong region).
  • expired_token — the token was valid but has expired; issue a new one.

authorization_error (403)

The request was authenticated, but the token is not allowed to perform it.

  • forbidden — your token isn't allowed to access this resource.
  • insufficient_scope — the token is valid but lacks the scope this operation requires.

not_found_error (404)

  • not_found — the resource named in the path does not exist, or is not visible to your tenant. The API does not distinguish "absent" from "not yours"; both return not_found.

rate_limit_error (429)

  • rate_limited — you have exceeded the rate limit. The response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers, plus a Retry-After header telling you how long to wait before retrying.

api_error (400, 500, 503)

A request-level problem that doesn't fit the categories above, or a fault on our side.

  • unsupported_api_version — the AndOpen-API-Version header is missing or not supported. Returned as a 400, before authentication.
  • malformed_request — the request could not be parsed at all — typically a body that is not valid JSON. Returned as a 400.
  • internal_error — an unexpected error on our side. Returned as a 500; rare and usually transient, so retry after a short delay.
  • service_unavailable — the service is temporarily unavailable. Returned as a 503; honour the Retry-After header before retrying.

Handling errors

Handle errors in this order:

  1. Check the status code. Retry 5xx and 429 — back off using Retry-After where it is present. Don't retry any other 4xx without changing the request first; it will fail the same way.
  2. Branch on type to handle the category.
  3. Refine on code where you need specific handling.
  4. Use param to attach validation messages to the right field in your UI.
  5. Log the Request-Id so a failure can be traced if you contact support.