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 fromhttps://api.agent37.com/v1/* always carry exactly code and message. There is no param or hint on this plane.
A stable, machine-readable identifier. Branch on this.
A human-readable description. Safe to show, but do not parse it.
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.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 athttps://{instanceId}.agent37.app/v1/* use the same envelope plus optional param, hint, and response_id.
The request field that was invalid. Present on
validation_error when a specific field is at fault; a malformed JSON body has no param.A suggested next step, when one applies.
On
session_busy: the id of the response already running on the session — reattach with GET /v1/responses/{id}/stream or cancel it. Treat it as optional; in a rare race the code arrives without it.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). 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 mid-run: 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. Check status, not just the HTTP code. See Send a message and Streaming.Handle them
Readcode, 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.
insufficient_balance sends your user to the billing dashboard, no_capacity is retryable, and invalid_request means fix the request before retrying.
Codes worth a closer look
insufficient_balance (402): the workspace wallet is empty
insufficient_balance (402): the workspace wallet is empty
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. 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.instance_budget_exhausted (402): this instance hit its budget
instance_budget_exhausted (402): this instance hit its budget
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 on Hosting API calls: it surfaces when the agent’s managed calls are refused mid-turn.session_busy (409): one response at a time
session_busy (409): one response at a time
A session runs one response at a time. Posting new input while a turn is in flight returns this, usually 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 (best effort; a finished response just returns its terminal state), or start a fresh session by omitting session_id. If the field is absent (a rare race, or an older gateway), read active_response_id from GET /v1/sessions/{id} instead. See Sessions.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.