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).

Tracing a call

Every response carries an X-Request-Id header, and error bodies also include it as requestId. Quote that value when you contact support and we can find the exact call.

You can supply your own instead: send an X-Request-Id header with any request — your log id, up to 64 characters (letters, digits, ., _, -). We record it alongside ours and echo it back, so a failure in your logs matches ours exactly. If you don't send one, we generate it.

Codes

CodeStatusMeaning
missing_authorization401No Bearer token in the Authorization header.
invalid_api_key401The API key is unknown or has been revoked.
forbidden403The resource exists but belongs to another account.
invalid_request400Malformed body or parameters — the error message says exactly what to fix.
pass_not_found404 / 400No pass with that ID on your account (400 from Process Order when the passId is inactive).
instance_not_found404No pass instance matches that instanceId or barcode.
holder_not_found404No loyalty holder matches that identifier.
no_reward_pending409POST /loyalty/redeem on a membership card that runs a loyalty program: no reward is pending on the card (the response carries balance and threshold). Rewards on program cards unlock at the threshold; use POST /loyalty/points to award.
insufficient_points409The membership card's POINTS balance is below the reward's cost (POST /loyalty/redeem). The response carries balance and cost; nothing was deducted.
partner_not_found404No partner with that id on this workspace (unknown, deleted, or another workspace's).
pass_archived400Archived passes can't issue new instances.
fields_not_editable400This pass has no editable fields — add fields in the studio first.
template_owned_field400The key is template-owned (title, details, terms, expiry) — update it for ALL holders instead.
unknown_field_key400The field key doesn't exist on the pass; the message lists the valid keys.
no_loyalty_program400Points operations on a pass with no loyalty program.
program_inactive400The loyalty program exists but is not active.
program_not_launched400The loyalty program has not been launched yet.
fields_update_unsupported409This holder's card predates per-holder addressing or ownership tracking — per-holder values (or clearing one with null) aren't possible on it; re-adding the pass mints an addressable card.
pool_exhausted409No barcodes remain in the pass's pool — upload more codes.
ambiguous_barcode409A shared Simple-mode code matches several instances; the response carries matchCount and how to disambiguate.
asset_fetch_failed422An image URL could not be used — slot names the parameter and reason says why: invalid_url (not https), blocked_host (private network, localhost, or cloud metadata address), unreachable, timeout (10 s), too_many_redirects (max 3), not_an_image, unsupported_type (PNG, JPEG, WebP only), too_small (below the slot's minimum). Nothing was created or changed; host the file publicly and retry.
payload_too_large413An image URL points to a file over 20 MB. Nothing was created or changed; resize the file and retry.
insufficient_credits402Not enough credits — top up in Billing and re-run.
insufficient_scope403The API key is read-only, or restricted to other passes. Use a key with the right scope; see Key scopes and rotation.
rate_limited429Request rate exceeded for this API key. Honor Retry-After, then retry. See Rate limits.
internal_error500Something failed on our side. When retrying is safe the response carries retryable: true.

Retry guidance

  • 202 — saved but not delivered: the update applied, but the wallet push was skipped for lack of credits (pushed: false, pushSkipReason: "insufficient_credits", plus a warnings entry). Top up in Billing and re-send (with a fresh externalRef if you use them) to deliver.
  • 4xx — don't retry unchanged; fix the request. The exceptions: insufficient_credits (retry after topping up) and rate_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.