Skip to main content
Every non-2xx response uses a single, consistent envelope:
{
  "error": {
    "code": "not_found",
    "message": "The requested resource was not found."
  }
}
  • code is a machine-readable value from the fixed set below — branch your logic on it.
  • message is a human-readable explanation. It may change over time; never parse or switch on it.

Error codes

The set of codes is closed and stable — new codes are added rarely and announced.
CodeHTTPWhen
invalid_request400A malformed or invalid path, query, or body parameter (including a bad cursor or an offset-less updated_since).
unauthorized401Missing, malformed, invalid, or revoked API key.
not_found404Unknown resource, a resource outside your entitlements, or no such route.
rate_limited429Per-minute rate limit exhausted. Carries a Retry-After header.
request_failed4xx/5xxCatch-all for an otherwise-unclassified request failure.
internal_error500An unexpected server error. Retry later; if it persists, contact support.
Entitlement boundaries are enforced as 404, not 403: a resource your key isn’t entitled to is indistinguishable from one that doesn’t exist, so existence is never leaked across organizations.

Handling errors

Switch on error.code and treat the HTTP status as a coarse category:
resp = requests.get(url, headers=headers)
if resp.status_code == 429:
    wait = int(resp.headers.get("Retry-After", "1"))
    # back off for `wait` seconds, then retry
elif not resp.ok:
    code = resp.json()["error"]["code"]
    # branch on `code` — e.g. refresh credentials on "unauthorized"