application/problem+json following RFC 9457, and every one carries a stable snake_case code you can branch on.
There is no second error shape. Auth failures, validation failures, rate limits and dependency outages all arrive in the same envelope, from every route.
The envelope
The invariant worth building on:
code always equals the tail of the type URI. Read either one, never both.
errors[] entries have a fixed shape:
errors[].code is a field-level code and is a different vocabulary from the top-level code. See validation_error for the full list.
Handling errors
Statuses the API returns
There is no 500 in the contract by design. Not one of the 80 published operations declares a 500 response. A failure Exo can anticipate is a 4xx; a dependency Exo cannot reach is a 503, which tells a client to retry rather than to give up. The
internal_error page exists so the type URI resolves if one ever escapes.
The code catalogue
Every code below has a page, because thetype URI on the wire points at it.
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
422 Unprocessable Content
429 Too Many Requests
503 Service Unavailable
Reserved
These exist in the problem envelope’s status map. No published operation declares them, and the pages exist only so thetype URI resolves rather than returning a 404.
Next
Rate limits
The families, their quotas, and how to read the RateLimit headers.
Request IDs
What
X-Request-Id is for and how to quote it.