Skip to main content
Every Apollo API error — any surface, any status — renders as the same envelope:
error.code
string
required
The stable, machine-readable code your integration branches on. Codes are append-only: new codes may be added, existing ones are never removed or repurposed.
error.message
string
required
A human-readable explanation for logs and debugging. Not part of the contract — the wording can change at any time, so never parse it.
error.request_id
string
Correlation id for the request. Include it when contacting support.
error.details
array
Optional structured sub-errors — for validation failures, one entry per offending field (field, message).

Error codes

Codes come in two levels. Every status has a generic code, and some failures carry a namespaced code that is more specific (e.g. agents.not_found rather than not_found). Always keep a default branch — the set is open and grows over time.
HTTP statusGeneric codeWhen
400bad_requestThe request was malformed.
401unauthorizedMissing or invalid credentials.
403forbiddenAuthenticated, but not allowed to act on this resource.
404not_foundThe resource doesn’t exist.
409conflictThe request conflicts with current state.
422validation_errorA field failed validation — see details.
429rate_limitedToo many requests — back off and retry.
5xxinternal_errorSomething went wrong on our side. Retry with backoff.

Handling errors

Branch on error.code, fall back on the status class:

Exceptions to the envelope

Two places deliberately speak a different error dialect:
  • The token endpoint (POST /management/v1/auth/token) follows the OAuth 2.0 convention: { "error": "invalid_grant", "error_description": "..." }. See Authentication.
  • Streaming transports deliver errors in-stream: SSE as an error event, WebSocket as a type: "error" envelope. The connection stays open where recovery is possible. See WebSocket.