Browse the docs
DocsDevelopers / APIDevelopers

Errors

The single JSON error envelope, the status codes the API returns, and how rate limiting works.

The API speaks a single, predictable error shape. Anything that is not a 2xx is an error envelope with a machine-readable code, a human message, and the request id, never a silent empty result standing in for a denial.

The error envelope

Every error response has the same shape:

json
{
  "error": {
    "code": "forbidden_scope",
    "message": "API key is missing the required scope: time:write",
    "request_id": "5f4e3d2c-1b0a-4987-8675-3c2b1a0f9e8d"
  }
}

Branch on error.code, not the message text: the message is for humans and may change. The request_id (also returned as the x-request-id header on every response) is what to quote if you need us to look up a specific call. Internal database detail is never echoed back; it stays in our logs under that id.

Status codes

401 unauthorized
Missing, malformed, unknown, revoked, or expired key. We do not say which: see authentication.
403 forbidden_scope
The key is valid but lacks the scope the endpoint requires.
403 forbidden_role
The scope is present but the key's member is not allowed to perform the action (for example a non-admin creating catalogue records). See roles and permissions.
403 forbidden_membership
The key's member has lost their place in the organisation, or the organisation is suspended. The key is retired automatically.
404 not_found
The resource does not exist, or it is outside your organisation. The two are deliberately indistinguishable so a key cannot probe for another organisation's ids. See data isolation.
405 method_not_allowed
The method is not supported on that path.
409 idempotency_conflict
An Idempotency-Key was reused with a different payload.
422 validation
The request body or query is invalid (missing required field, bad timestamp, reversed time range, malformed cursor, out-of-range limit).
429 rate_limited
The organisation's request budget is exhausted. See below.
500 internal
Something failed on our side. The request_id ties it to our logs.
Blocked is never an empty list
A denial is always a 4xx, never a 200 with an empty array. An empty dataarray genuinely means "nothing to show", so you can always tell "not allowed" apart from "nothing here".

Rate limiting

The API enforces a per-organisation daily request budget, set by your plan. When it is exhausted, further requests return 429 rate_limited with a Retry-After header (in seconds). The limit fails closed: if it cannot be resolved, the request is denied rather than allowed through unbounded.

Back off when you see a 429, and spread bulk work out rather than bursting. Keyset pagination already keeps large reads to bounded pages, which helps you stay well under the budget.