API Reference
camelRun errors: HTTP codes, failed runs and tools
camelRun's HTTP errors, failed runs, and tool calls that didn't complete: what each means and what to do about it.
View as Markdown · Docs index for coding agents
There are three kinds of failure:
- The API refused a request. An HTTP status and a JSON body. The SDKs throw
AgentErrorwithstatusandcode. - A run failed. The request was accepted, and the run ended badly. The
SDKs'
run()throwsRunErrorwith the run, unlessthrowOnError: false(Pythonthrow_on_error=False). - A tool call didn't complete within a run that otherwise went on. The
model was told, and the run lists it in
toolErrors. Nothing throws.
HTTP errors
Error bodies are {"error": "<message for people>", "code": "<CODE>"}. The
message may change and the code won't, so switch on code. New codes may be
added; treat an unknown one by its status. A conflict about an input also
carries the input as it now is (input), and a rate limit's 429 the limit it
hit (limit). Every 429 and 503 has Retry-After (and a per-account rate
limit's 429 its X-RateLimit-* headers; see Limits), and
the SDKs honor it and retry.
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | INVALID_REQUEST | Malformed or invalid; the message says how | Fix the request |
| 400 | INVALID_HISTORY | An imported initialMessages has a message it can't take | Fix the named message |
| 400 | FORK_POINT_INVALID | A fork's atMessage is no index in the history, nor a request id in it | Check the index or id |
| 400 | TRANSCRIPTION_UNAVAILABLE, REALTIME_UNAVAILABLE, IMAGE_UNAVAILABLE | Audio was to be transcribed, a voice session needs a realtime provider, or an image was to be made, and the account has no key for it (nor the platform's) | Add the key the message names |
| 400 | IMAGE_REFUSED | OpenAI's safety system refused the request (stage: "input") or the image (stage: "output"); categories says why. Nothing was charged | Change the prompt |
| 401 | UNAUTHORIZED | No valid token, or an expired browser token | Send a valid key; mint a new browser token |
| 402 | SPEND_LIMIT | The agent's spend limit, or the account's monthly cap; or a message's audio, or an image, would cost more than the limit has left | Raise the limit |
| 402 | INSUFFICIENT_CREDIT | The account's prepaid credit is spent (or a payment for it is disputed): no runs start and no files are stored | Add credit in the console |
| 402 | END_USER_CREDIT | With end-user credits on, the call's end user may not spend more in its bucket. The body has endUser, bucket and reason (below) | Grant them credit, raise the limit, or wait for tomorrow's allowance. See When a person runs out |
| 403 | FORBIDDEN | The token may not do this, such as a browser token outside its agent | Use your API key, server-side |
| 404 | NOT_FOUND | No such agent, request, input or other resource in your account | Check the id |
| 409 | IDEMPOTENCY_CONFLICT | The Idempotency-Key or request id was used for another request | Use a new key for a new request |
| 409 | IDEMPOTENCY_IN_PROGRESS | The first request with this key is still running | Retry shortly |
| 409 | INSUFFICIENT_BALANCE | An end user's refund would return more than their balance not held by calls in flight; refundable is the most it may return now | Refund less, or post an adjustment. See End-user credits |
| 409 | CARD_CREDIT_UNAVAILABLE | Card-verification starting credit isn't offered | Buy credit instead |
| 409 | VOICE_SESSION_EXISTS, VOICE_SESSION_ENDED, VOICE_SPEAK_DISABLED | The bot already has a voice session in this server (session), the session ended, or it only listens | Update or end it; start a new one; don't ask a listening session to speak |
| 409 | APPLICATION_CONNECTED | Another process serves this agent's tools | Close it, pass takeover: true, or connect with attach: false |
| 409 | APPLICATION_NOT_CONNECTED | The agent's tools need their process and none is connected | Start it, serve tools over HTTP, or pass allowDisconnected: true |
| 409 | FORK_POINT_RUNNING | A fork's atMessage is in a turn that hasn't ended | Fork at an earlier message, or once it ends |
| 409 | REPLAY_GAP | The Last-Event-ID is behind the replay buffer | Reconnect with ?snapshot=1 and read /state |
| 409 | CONFLICT | Another conflict, named in the message | See below |
| 410 | GONE | The agent was deleted or expired | Upsert the key again for a fresh agent |
| 413 | TOO_LARGE | The request body is too large | Upload large files first and attach them by path |
| 413 | HISTORY_TOO_LARGE | An imported history is over 16 MB | Send recent messages after a compactionSummary |
| 413 | AUDIO_TOO_LARGE, AUDIO_TOO_LONG | Audio to transcribe is over 25 MB or 30 minutes (a message's: 5 files or 30 minutes in all) | Split it |
| 413 | IMAGE_TOO_LARGE | An image to edit is over 25 MB or 8,000 pixels a side, or a request's images are over 50 MB in all | Send smaller images |
| 415 | UNSUPPORTED_AUDIO, UNSUPPORTED_IMAGE | Audio in a format transcription doesn't take, or an image to edit that isn't PNG, JPEG or WebP | Convert it |
| 429 | BUSY_AGENT_LIMIT | As many agents are busy as your usage tier allows; busyAgents has the limit and the next tier | Retry after Retry-After, or when one of its runs ends |
| 429 | VOICE_SESSION_LIMIT | As many live voice sessions as the account may have (limit) | End one first |
| 429 | RATE_LIMITED | A rate limit (limit names it: {name, scope, max, windowSeconds}), or too many agents, requests or subscribers at once | Retry after Retry-After |
| 502 | TRANSCRIPTION_FAILED, IMAGE_FAILED | The provider failed or refused the key; nothing was charged | Retry, or check the key |
| 503 | UNAVAILABLE | camelRun is restarting or deploying, or an agent is moving | Retry after Retry-After; nothing happened |
| 503 | DATABASE_RETRY | The database refused the request for now | Retry after Retry-After |
| 507 | STORAGE_LIMIT | The account's storage limit would be passed; the body has limit and used, in bytes | Delete files or volumes, or buy credit (free credit stores 1 GB) |
| 500 | INTERNAL | A bug | Retry, and report it with the request id |
Common CONFLICT cases:
- An existing agent's subject, context, definition or mounts can't change.
An upsert of an existing key changed a field that's set only at creation.
Delete the agent or use another key. For mounts, send
remount: truewith the upsert instead (see Volumes). - This input is already answered. Someone answered first. The body's
inputsays how it settled. Answering again the same way is a 200. - The definition is at revision N, not M. A definition update with a stale
revision. Read it again and retry.
Run failures
RunError.code | Meaning |
|---|---|
model_key_missing | No key is set for the agent's model. The error names the model and the remedy: set a key with PUT /v1/providers/{provider}/key (or the key scope's entry), or move the agent to a model you can use (GET /v1/models?available=true) |
model_key_invalid | The provider refused the key (HTTP 401). The error says the same remedy, then the provider's own words |
model_error | The model provider refused or failed after 3 retries for transient failures: a bad request, refused content, or a context overflow compaction couldn't fix |
output_missing | A run sent with output ended without the model calling final_output: it answered in prose twice, the second time after a reminder. text has what it said. Send the prompt again, make the instructions say what the answer is for, or use a stronger model |
output_invalid | (SDKs only) The output fit the JSON Schema camelRun checked, but not the zod or pydantic schema the SDK parsed it with |
model_stream_stalled | The model stopped answering mid-request, past every retry |
spend_limit | A spend limit stopped the turn after the response that crossed it, or refused the run as it left the queue. result.limit says which: run, agent, tenant (the monthly cap) or credit (prepaid credit spent). Raise that limit and send the next message |
turn_limit | The run made as many model responses (1,000 by default) or ran as long (2 hours) as its run limits allow. Send another message to continue, or raise the agent's runLimits |
end_user_credit | The run's end user may not spend more (limit: "end_user", with reason, bucket and endUser). Not retried. Grant them credit and send the next message. See When a person runs out |
aborted, cancelled | The agent was stopped while the run went, or a stop cancelled the run before it began |
resume_limit | The run was interrupted again after two resumes. The agent takes new messages |
runtime_error | camelRun couldn't carry out the run: for example, the agent was deleted |
Over REST, an ended request record (GET /v1/agents/{id}/requests/{requestId})
has status on top: completed, input_required or failed, as the SDKs' runs
say it. Its state says only whether it ended (running, completed), so a
failed run is state: "completed", status: "failed", with error saying why.
A run with uncertain: true was cut short by a restart where it couldn't
resume. Its tool calls may or may not have taken effect, so check before
retrying.
A run interrupted mid-turn (camelRun restarted under it) resumes (turn_resumed), with
any tool call whose answer was lost marked "outcome unknown" for the model.
Tool calls are never sent twice. After two such resumes, another interruption fails the
run with resume_limit. A deploy loses nothing: the turn moves at a step
boundary.
End-user credit reasons
A 402 END_USER_CREDIT, a run's end_user_credit, the run.failed webhook
and voice.session.ended's endUserReason all carry reason, bucket and
endUser. reason is one of:
reason | Meaning | What the end user can do |
|---|---|---|
balance | Today's free allowance in the bucket is used up, and their paid balance has nothing left | Add credit (a grant), or wait for tomorrow's allowance |
daily_limit | Their own daily limit on paid spend is reached | Wait for tomorrow, or have the limit raised |
bucket_limit | The bucket's daily limit on paid spend is reached. A bucket limited to 0 spends only its free allowance | Wait for tomorrow, or have the bucket's limit raised |
More reasons may be added: treat one you don't know as a credit refusal of no particular kind.
Model failures
When a model call failed the run (model_error, model_stream_stalled,
model_key_invalid and the like), its error says how, so you needn't parse
message:
{ "code": "model_error", "message": "402 This request requires more credits…",
"errorClass": "billing", "providerStatus": 402, "retryable": false, "model": "openrouter/anthropic/claude-sonnet-5" }| Field | Meaning |
|---|---|
errorClass | billing (the provider's 402, or a quota used up), rate_limit, server (5xx, overloaded, unreachable), refusal (the model declined, or a safety filter stopped it), auth (401, 403, no key), timeout, other. Treat an unknown class as other |
providerStatus | The provider's HTTP status, when its error gives one |
retryable | Whether camelRun retries this kind of failure itself (3 times, with backoff). On a failed run, true means it was transient and the retries ran out, so trying later or with another model may work. A model on your own endpoint isn't retried, except for a stalled stream, so its failures are false |
model | The model that failed, as provider/model-id |
The same fields are on the run.failed webhook. A run's servedModel is the
model that made its last response.
Tool errors
A run lists each tool call that didn't complete as
toolErrors: [{tool, toolCallId?, code, outcomeUnknown?, message}]. The model
got the same message and carried on.
code | Meaning | Outcome unknown |
|---|---|---|
timeout | No answer or progress within the tool's deadline | Yes |
connection_lost | The connection closed before the answer arrived | Yes |
not_connected | No process served the agent's attached tools, so the call didn't run. Typical for REST, schedules and channels driving an agent whose tools live in a process that isn't running: serve them over HTTP instead | No |
source_unavailable | Its MCP server couldn't be reached, listed or authenticated with, or its credential exchange gave no credential the server takes | No |
failed | Anything else that kept it from running | No |
A tool that ran and reported its own failure (threw, or answered isError) is
not a tool error: that's the tool's answer, which the model reads.
SDK errors
| TypeScript | Python | |
|---|---|---|
| Any failure | AgentError: message, status, code, requestId, uncertain, retryAfterMs | AgentError: status, code, request_id, uncertain, retry_after |
| A failed run | RunError extends AgentError, with run | RunError, with run |
| An end user's credit refused | reason, bucket, endUser; endUserCredit(errorOrRun) | reason, bucket, end_user; end_user_credit(...) |
| Another process took the agent's tools | code: "APPLICATION_REPLACED", through onError | The same, through on_error |
| A served tool's token check failed | RuntimeTokenError | RuntimeTokenError |
A run's failure is not an HTTP response, so RunError.status is always 0, in
both SDKs: it does not mean the network failed. Branch on code
(model_key_missing, spend_limit, …). An AgentError with status 0 and no
code is the connection's failure.
Stopping a wait (signal, or Python timeout) doesn't stop the run. Wait for
it again with the same idempotencyKey.