Errors
The standard shape
Every error response from the gateway itself — across every product — is a single JSON object with one field:
{ "error": "<human-readable message>" }There is no machine-readable error code in this envelope today (no code/type field) — treat error as a string for logging/display, not something to switch on by exact value if you can avoid it. The HTTP status code is the reliable signal for branching logic.
One documented exception
A proxy-style product forwarding to a third-party upstream can return that upstream's own error shape instead, for anything that gets far enough to actually reach it. otpauth is the one live example — its own reference page documents both shapes and exactly which status codes use which. Don't assume every response from every product uses the {"error": "..."} envelope; check the specific product's page for products marked as proxying to a third party.
Status codes, gateway-wide
| Status | Meaning | Typical error message |
|---|---|---|
400 | Malformed request (bad/missing required parameter, invalid JSON body) | varies per endpoint, see that product's reference |
401 | Missing or invalid X-API-Key, or this key isn't subscribed to this product | missing X-API-Key header / invalid API key, or key is not subscribed to {product} |
403 | This product is in private testing and your key hasn't been granted access | {product} is in private testing, this key hasn't been granted access |
404 | Route doesn't exist | — |
429 | Monthly quota exceeded, or per-minute burst limit exceeded | monthly quota exceeded for {product} / too many requests for {product}, slow down |
502 | (Proxy-style products only) the upstream provider failed to respond | upstream request failed |
503 | Product temporarily disabled, or the gateway's quota backend is unreachable — the gateway fails closed, so this can appear even when your key and request are both fine | {product} is temporarily disabled / quota service unavailable |
A 503 here is not a signal to retry aggressively — see Rate Limits for backoff guidance. A 500 should never happen; if you see one, it's a bug, not something to branch logic around.
Retrying
429from the per-minute burst limit includes aRetry-Afterheader (seconds). Respect it.429from the monthly quota has noRetry-After— it doesn't reset until the next calendar month. Don't retry in a loop; check your dashboard usage instead.503from a Redis outage is expected to be short (the gateway is designed to fail closed fast, not hang) — a short exponential backoff (e.g. 1s, 2s, 4s, capped) is reasonable.