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:
| Field | Type | Description |
|---|---|---|
type | string | The error category — one of the closed set below. |
code | string | A machine-readable code scoped to the type, in snake_case. An open enum (see Error codes). |
message | string | A human-readable explanation of this specific occurrence. For logs and developers, not end users. |
param | string | The 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:
| Status | Meaning |
|---|---|
| 400 | The 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). |
| 401 | Authentication failed — missing or invalid bearer token. |
| 403 | Authorised but not permitted to access this resource. |
| 404 | The requested resource does not exist. |
| 422 | Validation error — the request was parseable but contains invalid data. All validation errors are returned at once. |
| 429 | Too many requests — rate limit exceeded. |
| 500 | Internal server error. |
| 503 | Service 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. acampaign_idthat 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. Theparampoints 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— noAuthorizationheader 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 returnnot_found.
rate_limit_error (429)
rate_limited— you have exceeded the rate limit. The response carriesRateLimit-Limit,RateLimit-Remaining, andRateLimit-Resetheaders, plus aRetry-Afterheader 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— theAndOpen-API-Versionheader is missing or not supported. Returned as a400, before authentication.malformed_request— the request could not be parsed at all — typically a body that is not valid JSON. Returned as a400.internal_error— an unexpected error on our side. Returned as a500; rare and usually transient, so retry after a short delay.service_unavailable— the service is temporarily unavailable. Returned as a503; honour theRetry-Afterheader before retrying.
Handling errors
Handle errors in this order:
- Check the status code. Retry
5xxand429— back off usingRetry-Afterwhere it is present. Don't retry any other4xxwithout changing the request first; it will fail the same way. - Branch on
typeto handle the category. - Refine on
codewhere you need specific handling. - Use
paramto attach validation messages to the right field in your UI. - Log the
Request-Idso a failure can be traced if you contact support.