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 operator, a malformed or inverted date filter, or an unsupported aggregation or group_by.
401UnauthorizedMissing or invalid credentials — no app_id/X-Api-Key, or the key doesn't match the app.
403ForbiddenCredentials are valid, but the plan does not include the Data Access API, or the subscription is cancelled or expired.
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. A 503 with code PLAN_CHECK_UNAVAILABLE means your plan could not be verified; retry.

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, unsupported filter operator, malformed or inverted date filter, unsupported aggregation or group_by; on /event, a non-equal operator or a device_platform value outside 1–3.
PLAN_FEATURE_DISABLED403The Data Access API is not enabled on your current plan.
SUBSCRIPTION_INACTIVE403Your subscription is cancelled or expired.
PLAN_CHECK_UNAVAILABLE503Your plan could not be verified. Retry shortly.
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.

References:

Sessions
Users
Events
Screen Analytics
Query Parameters
Filter Operators


Did this page help you?