Skip to main content
Every error carries a stable code. Branch on the code, not the HTTP status and not the message — the status is shared across unrelated failures and the message is prose we may reword. Where a finite set of legal values exists, the body carries it in details.accepted_values, so a 400 is usually enough to fix the call without reading anything.

Authentication

The request never reached a handler. Check the key, not the query.

Entitlement

The endpoint exists and your key is valid, but this workspace does not carry the flag it needs. Always an explicit error — never an empty result, which would be indistinguishable from “we have no data for you”.

Validation

A parameter was rejected rather than silently ignored. A filter that looks accepted and does nothing produces a confident, wrong answer, so the API fails loudly instead. The body carries details.accepted_values wherever a finite set exists.

Data availability

The request is valid, but the evidence required to answer it under the requested semantics is not available. Never interpret this as an empty result or as evidence that nothing happened.

Not found and withdrawn

The id is well-formed but resolves to nothing, or to something that has since been superseded. A withdrawn or merged record returns a pointer to its replacement rather than a bare 404.

Limits

The request was well-formed and refused on volume or account state.

Server

Our fault. Safe to retry with backoff.

Rejected values, by vocabulary

A closed vocabulary rejects anything outside its list. Most return INVALID_ENUM; a few carry their own code so a caller can branch without inspecting details. Derived from the same vocabulary registry the server validates against.
An observed vocabulary never rejects on membership — it is a dated measurement, not an allowlist, and the API accepts values outside it. Only closed vocabularies appear above.