Skip to main content
Every failure the Exo API returns is 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 the type 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 the type 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.