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:
{
"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
Idempotency-Key was reused with a different payload.limit).request_id ties it to our logs.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.