Errors
Two shapes, and how to tell them apart
Errors come back in one of two shapes depending on how far into the system your request got. Handle both — a client that parses only one will throw on the other exactly when something is already going wrong.
Rejected at the door — authentication and the API's own rate limiting:
{ "message": "invalid API key" }
Rejected by the endpoint — anything the API itself refused:
{
"code": 3,
"message": "limit must be at most 100, got 5000"
}
The safe way to read both: use the HTTP status for control flow, and message
for the human. Both shapes always carry message, so logging
response.message works either way.
One case carries neither shape: a request stopped by the per-IP edge ceiling never
reaches the API, and comes back as the gateway's HTML error page. That is the one
place a message lookup throws — see what you will not get.
The code in the second shape is not the HTTP status. It is a gRPC status
code, because these endpoints are generated from service definitions — 3 is
invalid argument, 5 is not found. If you are branching on anything, branch on
the HTTP status.
Status codes
| Status | Meaning | Worth retrying? |
|---|---|---|
400 | Your request was malformed — a bad limit, both cursors at once, an unknown enum value (glossary) | No. Fix the request. |
401 | Missing, malformed or revoked token | No. See authentication. |
404 | No such record, or not one you can see | No. |
429 | Rate limit exceeded — your token's quota, or the per-IP edge ceiling | Yes. After Retry-After if the response carries one, otherwise back off. |
500 | Something broke on our side | Yes, with backoff. Report it if it persists. |
503 | No healthy backend behind the edge | Yes, with backoff. |
A 404 covers both "this does not exist" and "this exists but is not yours". That
is intentional: distinguishing them would let anyone confirm whether a reference
belongs to another company.
Retrying
Retry 429, 500 and 503. Do not retry 400, 401 or 404 — the same
request will fail the same way, and a loop around a 401 is how a token ends up
locked out of a log file rather than fixed.
For 429, wait the Retry-After seconds when the response carries one. The
edge ceiling does not send the API's headers, so back off exponentially there
instead. For 500 and 503, do the same and give up after a handful of attempts
rather than hammering a service that is already unwell.
Every read on this API is safe to repeat: nothing here changes state, so a retry can never double-apply anything.
What you will not get
- No HTML from the API. Everything the API itself refuses is JSON. The
exception is the per-IP edge ceiling: it is enforced in front of the API and
serves the gateway's own HTML error page, so do not assume a
429body is parseable — check theRateLimit-*headers instead. - No stack traces or internal detail. A
500tells you it failed, not what our database was doing. If you need to know, quote the time and the endpoint in a support request. - No partial pages. A list request either returns a page or an error. It will not return half the records and an error field.