Status codes you’ll actually see
| HTTP | error.type | When |
|---|---|---|
400 | invalid_request_error | Malformed input — bad UUID, bad date, unknown metric name, wrong HTTP method. |
401 | authentication_error | Missing / malformed / unknown / revoked Bearer key. |
403 | permission_error | Key is valid but the caller doesn’t own the requested universe. |
404 | not_found | Universe doesn’t exist, or no history row for the given filters. |
429 | rate_limit_error | Per-key request budget exceeded. See Rate limits. |
500 | api_error | Server bug or upstream DB failure. Try again with backoff; if it persists, reach out. |
503 | api_error | Backend temporarily unreachable. Retry with backoff. |
Common cases
Forgot the header
Bad UUID in the path
Wrong owner
No data for that filter combo
Bad date
Idiomatic client handling
What we don’t do (and why)
- No RFC 7807
application/problem+json— the format hasn’t reached meaningful adoption among public APIs developers integrate with daily. We follow Stripe’s pattern instead because that’s what the ecosystem is already wired to consume. - No nested error trees / multiple errors per response — every
failed response carries exactly one
errorobject. If you submitted multiple invalid filters, you’ll see the first one we noticed. - No localised messages —
messagestrings are English-only. Don’t show them to end users verbatim; show your own copy and log ours.