Skip to main content
Every error is a JSON:API error document:
source.pointer names the field, so a validation error can be shown against the right input rather than as a sentence at the top of a form.

404 and 403 are deliberate

Something that exists but is not yours answers 404, not 403. The two are the same answer on purpose: ids cannot be used to find out what exists, or to count what somebody else has. So a 404 on a listing you believe you own is usually not a missing listing. It is the wrong business in the path, or a grant that was withdrawn.
/oauth/token is the exception to all of this, apart from maintenance: it follows OAuth 2.0 and answers with {"error": "invalid_grant"} and friends. An invalid_grant on a refresh means the link is broken — re-authorize rather than retrying. The app’s own sign-in adds mfa_required, invalid_otp and account_blocked, and asking for too many guest tokens from one address answers too_many_requests; see the app guide.

Every code this API answers with

errors[].code is the stable part of a refusal: the status says what kind of thing went wrong, and the code says which one. Branch on the code, never on detail — that sentence is translated by Accept-Language, so matching on it breaks in the next language. A code is only ever added, never repurposed. Treat an unfamiliar one as its status class.
This table is checked against the code by a test, so a refusal the API can answer with and this page does not name is a failing build rather than something you discover at runtime.