Skip to main content
Every error uses a standard HTTP status code and returns a stable, machine-readable code in a JSON error field: an object with a code on both API catalogs, a flat string on transport failures between you and the gateway. Branch on the code, never on message or the HTTP status alone. There are two catalogs because there are two planes, plus a short list of transport errors.
Both planes take the same sk_live_ key, as Authorization: Bearer on the hosting API and as X-Agent37-Key on instance URLs, but their error envelopes differ: the Agent API adds optional param, hint, and response_id fields. See Core concepts for the two planes.

Hosting API errors

Errors from https://api.agent37.com/v1/* always carry code and message, plus detail on the one error that has raw output worth showing: a failed create’s provisioning_failed includes the tail of your container’s console log, so a container that crashed on boot returns its own startup error. There is no param or hint on this plane.
string
A stable, machine-readable identifier. Branch on this.
string
A human-readable description. Safe to show, but do not parse it.
string
Optional. Raw failure output, present on a failed create’s provisioning_failed: the last lines of your container’s console log (host paths and IPs redacted, truncated to the final 2,000 characters). The same text persists on the instance as status_reason.detail.

Hosting API codes

Lookups are uniform: an id that belongs to another workspace returns the same 404 as an id that does not exist, and unknown /v1 paths 404 only after your key is validated. Nothing about other workspaces leaks through error responses.
A template build that fails after starting is not an HTTP error: GET /v1/template-builds/{id} returns 200 with status: "failed" and the reason (build_failed, build_timeout, invalid_request, invalid_image_archive, image_too_large, image_ingest_failed, or internal_error) in its error field.

Agent API errors

Errors from the gateway at https://{instanceId}.agent37.app/v1/* use the same envelope plus optional param, hint, and response_id.
string
The request field that was invalid. Present on validation_error when a specific field is at fault; a malformed JSON body has no param.
string
A suggested next step, when one applies.
string
On session_busy: the id of the response already running on the session. Reattach with GET /v1/responses/{id}/stream or cancel it. Every session_busy from a current gateway carries it; treat it as optional only for instances still on an older gateway.

Transport errors

Auth, instance lookup, and routing happen on the platform between you and the gateway, and rejections there use a flat string instead of the envelope: {"error": "<code>"}. Some carry a human-readable message (and, on a 401 with no credentials, a docs link); branch on the code, not on either. Check whether error is a string before reading code.

Agent API codes

The Agent API catalog is open-ended past this table. Failures inside the agent can surface provider-specific codes at 502 or 503 (for example a provider auth or quota error passes its raw code through, and an agent that is still warming up returns 503 with its own code). On OpenClaw a turn that ends in error carries OpenClaw’s own error kind as error.code (refusal, timeout, context_length, or unknown); a provider rate limit arrives as rate_limited, and when OpenClaw reports no kind the code is agent_error. Treat any code you do not recognize as an agent-side failure: log it and show message.
Not every failed turn is an HTTP error. POST /v1/responses never rejects because the agent failed: once the turn is accepted, the call returns 200 with status: "failed" and the same error object in the response body’s error field, and streams end with a response.failed event. rate_limited, agent_error, agent_unavailable, and the provider-specific codes reach you this way on a turn, never as their HTTP status; the statuses in the table apply when the same failure hits a session or model call. Check status, not just the HTTP code. See Send a message and Streaming.
The BYO-account harnesses signal a missing account with auth_error. Until an account is connected, a turn comes back status: "failed" with error.code auth_error and a hint, and GET /v1/health reports "healthy": false. On Claude Code the hint says to connect a Claude account; on Codex it says to run codex login --device-auth or set OPENAI_API_KEY; on Grok it says to set XAI_API_KEY or run grok login --device-code.

Handle them

Read code, then act by remedy: busy sessions get a cancel or a new session, transient codes get a retry with backoff, validation errors get fixed (read param), and anything unknown is agent-side.
The same grouping works on the Hosting API: insufficient_balance sends your user to the billing dashboard, no_capacity is retryable, and invalid_request means fix the request before retrying.
no_capacity (503, hosting) and rate_limited (429, agent) are safe to retry with backoff. So are try_again (409, hosting) and a provisioning_failed create: creates debit nothing (the meter only starts once the instance runs), so retrying never double-bills.

Codes worth a closer look

The workspace wallet cannot cover a charge. You see it in three places: at create, when the wallet holds less than one day of the instance’s running rate (a check, not a debit; the meter only starts once the instance runs); at start, when a past_due instance’s workspace balance is still negative; and inside agent behavior, when a managed call (LLM, Brave search, Composio) finds the wallet empty. That third case does not reach the Agent API as insufficient_balance: the refused LLM call fails the turn (status: "failed", or a response.failed event) with error.code quota_exhausted, the same code a spent instance budget produces; the message names the workspace balance, so it tells the two apart. The fix is the same everywhere: top up the wallet at https://www.agent37.com/dashboard/cloud/billing ($5 minimum, $1000 max per top-up), and enable automatic top-up so it does not recur. See Billing.
The wallet has funds, but this instance has used up its own managed-spend budget: the monthly cap is consumed and no one-time top-up headroom remains. Only managed calls are refused; the instance keeps running and compute billing is unaffected. Raise the cap with PATCH /v1/instances/{id}/budget or add headroom with POST /v1/instances/{id}/budget/top-up. See Budgets. You will not see this code in an API error on either plane: it is what the managed endpoint returns to the agent’s own call inside the instance. What you see is the failed turn (status: "failed", or a response.failed event); on Hermes its error.code is quota_exhausted, with a hint naming the fix.
A session runs one response at a time. Posting new input while a turn is in flight returns this, with the running response’s id in error.response_id. Three ways out: reattach to the running turn with GET /v1/responses/{response_id}/stream, cancel it with POST /v1/responses/{response_id}/cancel (a finished response just returns its terminal state), or start a fresh session by omitting session_id. If the field is absent (an instance still on an older gateway), read active_response_id from GET /v1/sessions/{id} instead. See Sessions.
Three different walls, three different fixes. instance_limit_reached (409, create): the workspace is at its instance limit (one instance on the free credit, 10 once you have topped up, 50 once top-ups total $100, 200 once they total $250); delete instances you no longer need, or email vishnu@agent37.com to raise the ceiling. no_capacity (503, create): no host can fit the requested shape right now; retry with backoff or pick a smaller shape, and a create that fails this way does not keep your money. capacity_unavailable (409, start or resize): the host that holds the instance’s disk cannot fit it, and no other host has room to take it either (when the home host is full, the platform moves the instance to one with room before it ever fails); retry later. See Instances.
Both 402 reasons are billing limits, not bugs. When a managed call is refused mid-turn, the refusal shows up in agent behavior (the turn fails or the agent reports it); the instance itself never goes down over managed spend.