Every error uses the same envelope, so one handler covers the whole API.
{
"error": {
"code": "invalid_request",
"message": "A human-readable explanation."
}
}
Branch on code, not on the message - messages get reworded, codes don't.
The codes
| Status | Code | Means | Retry? |
|---|---|---|---|
401 | unauthorized | Missing, malformed, unknown or revoked key | No - fix the key |
403 | forbidden | Valid key, not permitted to do this | No |
403 | account_suspended | The account is suspended | No - get in touch |
404 | not_found | No such pass, template or subscription | No |
422 | invalid_request | The request is malformed or missing something required | No - fix the request |
429 | rate_limited | Too many requests | Yes, after backing off |
5xx | - | Something went wrong our end | Yes |
The rule of thumb: 4xx means fix your request, 429 and 5xx mean try again. Retrying a 422 will produce the same 422.
The most common mistakes
401 on a first integration is nearly always the header format. It must be exactly Authorization: Bearer <key>.
422 on creating a pass is usually the missing passTemplate query parameter. It's required, and there's no default.
404 when the pass definitely exists is usually the wrong account - a key only ever sees its own account's data. See Working across multiple accounts.
A field that silently doesn't appear isn't an error at all: an unrecognised key in passContent is ignored rather than rejected. Check the field id against the template. See Creating passes over the API.
Rate limits
600 requests per 60 seconds per API key.
That's a generous ceiling for normal use - it only bites when you're bulk-issuing or backfilling.
Over the limit you get 429 with code rate_limited.
The limit is per key, so a batch job on its own key can't starve your live checkout of requests. That's a good reason to give each system its own key anyway. See API keys and authentication.
Staying inside it
For anything bulk:
- Add a small delay between requests rather than firing them as fast as possible.
- On a
429, wait and retry with exponential backoff - a second, then two, then four. - Cap concurrency. Ten parallel workers hammering the API will hit the limit that ten sequential ones never would.
- Set your bulk job's own key so it can't affect anything customer-facing.
Don't retry a 429 immediately in a tight loop. You'll stay rate-limited and turn a brief slowdown into a sustained failure. Back off properly.
Retrying safely
Retries are only safe if repeating a request can't cause damage. For pass creation, that's what extId guarantees - the same request twice produces one pass, not two.
If you're going to retry anything, read External IDs and idempotency first.
Useful response headers
X-Total-CountonGET /api/v1/passes- the total number matching your filters, for pagination.X-Notification-Statuson pass updates that carry a message, and on the notification endpoint - whether the push was delivered.