Error Handling and Messages

The API returns a standard HTTP status code with every response. Successful reads are 200; problems carry a JSON error body describing what went wrong.

Status codes

StatusReasonDescription
200OKThe request succeeded.
400Bad RequestThe request was malformed — e.g. an invalid cursor, an unknown filter attribute, or an unsupported aggregation.
401UnauthorizedMissing or invalid credentials — no app_id/X-Api-Key, or the key doesn't match the app.
404Not FoundThe requested resource doesn't exist.
415Unsupported Media TypeThe request wasn't sent as application/json.
422Unprocessable EntityThe body failed validation — e.g. page_size outside 1–2000, or an unrecognised field.
429Too Many RequestsA rate limit was exceeded (see below).
500 / 502 / 503 / 504Server ErrorAn unexpected server-side condition. Retry after a short delay.

Error body

For most 4xx responses the body carries a stable machine-readable code and a human-readable message under detail:

{
  "detail": {
    "code": "INVALID_CREDENTIALS",
    "message": "Invalid app id or API key."
  }
}

Switch on detail.code (stable) rather than the message text (which may be retuned). Body-validation failures (422) use FastAPI's standard validation shape, with detail as an array describing each invalid field.

CodeStatusWhen
MISSING_CREDENTIALS401No app_id in the body, or no X-Api-Key header.
INVALID_CREDENTIALS401The key doesn't match the app (unknown app and wrong key share this code so app ids can't be probed).
INVALID_REQUEST400A bad cursor, unknown filter attribute, or unsupported aggregation.
INVALID_CONTENT_TYPE415Content-Type wasn't application/json.
RATE_LIMITED429A rate limit was exceeded.

Rate limiting

[status code: 429] — limits are enforced per API key (each app has exactly one key, so this is effectively per app):

  • A maximum of 5 requests per second
  • A maximum of 500 requests per hour
  • A maximum of 2000 records per request

When a limit is exceeded the API returns 429 RATE_LIMITED. The per-second window clears within a second and the hourly window rolls forward each hour, so back off briefly and retry. A per-second burst that's rejected doesn't consume your hourly budget.

Rate-limit headers

Authenticated responses advertise your current hourly budget so you can pace requests before hitting a limit. They appear on a successful 200 and on a 429; a request rejected before authentication (e.g. 401 for missing/invalid credentials, or 415 for the wrong content type) carries no budget headers, because no key has been metered yet:

HeaderMeaning
X-RateLimit-LimitYour hourly request budget.
X-RateLimit-RemainingRequests remaining in the current hour.
X-RateLimit-ResetSeconds until the hourly window resets.

A 429 additionally returns a Retry-After header — the seconds to wait before retrying (1 for a per-second burst, or the seconds until the hourly window resets).

X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1800
Retry-After: 1800
📘

Note

To stay under the limits, do more per call: combine several filters, groupings, and aggregations into a single analytics request instead of firing many small ones, and use page_size up to 2000 and request only the sections you need via show_only.



Did this page help you?