← Documentation
API Error Codes
Every v1 API error carries a stable machine-readable code. Branch on the code — never on the message.
The error shape
Errors return a non-2xx HTTP status and a JSON body with a human-readable error message and a stable code:
{
"error": "No barcodes remaining in this pass's pool.",
"code": "pool_exhausted"
}The code values below are a contract: they never change meaning and are only ever added to. The error strings are for humans and may be reworded — don't match on them. Some errors carry extra context fields (e.g. matchCount on ambiguous_barcode, retryable: true on retry-safe 500s).
Codes
| Code | Status | Meaning |
|---|---|---|
missing_authorization | 401 | No Bearer token in the Authorization header. |
invalid_api_key | 401 | The API key is unknown or has been revoked. |
forbidden | 403 | The resource exists but belongs to another account. |
invalid_request | 400 | Malformed body or parameters — the error message says exactly what to fix. |
pass_not_found | 404 / 400 | No pass with that ID on your account (400 from Process Order when the passId is inactive). |
instance_not_found | 404 | No pass instance matches that instanceId or barcode. |
holder_not_found | 404 | No loyalty holder matches that identifier. |
pass_archived | 400 | Archived passes can't issue new instances. |
fields_not_editable | 400 | This pass type's fields aren't a flat key→value map — edit it in the studio. |
template_owned_field | 400 | The key is template-owned (title, details, terms, expiry) — update it for ALL holders instead. |
unknown_field_key | 400 | The field key doesn't exist on the pass; the message lists the valid keys. |
no_loyalty_program | 400 | Points operations on a pass with no loyalty program. |
program_inactive | 400 | The loyalty program exists but is not active. |
program_not_launched | 400 | The loyalty program has not been launched yet. |
pool_exhausted | 409 | No barcodes remain in the pass's pool — upload more codes. |
ambiguous_barcode | 409 | A shared Simple-mode code matches several instances; the response carries matchCount and how to disambiguate. |
insufficient_credits | 402 | Not enough credits — top up in Billing and re-run. |
rate_limited | 429 | Reserved: request rate exceeded. See Rate limits. |
internal_error | 500 | Something failed on our side. When retrying is safe the response carries retryable: true. |
Retry guidance
- 4xx — don't retry unchanged; fix the request. The exceptions:
insufficient_credits(retry after topping up) andrate_limited(retry with backoff). - 500 with retryable: true — safe to retry: the operation released its claim, and idempotent endpoints (issue, points with externalRef, Process Order) return the original result instead of double-applying.
- 500 without retryable — contact support with the request details.
See also Rate limits and the API & integrations guide.