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.
A human-readable explanation for logs and debugging. Not part of the
contract — the wording can change at any time, so never parse it.
Correlation id for the request. Include it when contacting support.
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 status | Generic code | When |
|---|---|---|
400 | bad_request | The request was malformed. |
401 | unauthorized | Missing or invalid credentials. |
403 | forbidden | Authenticated, but not allowed to act on this resource. |
404 | not_found | The resource doesn’t exist. |
409 | conflict | The request conflicts with current state. |
422 | validation_error | A field failed validation — see details. |
429 | rate_limited | Too many requests — back off and retry. |
5xx | internal_error | Something went wrong on our side. Retry with backoff. |
Handling errors
Branch onerror.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
errorevent, WebSocket as atype: "error"envelope. The connection stays open where recovery is possible. See WebSocket.