All error responses use a standard JSON envelope:
"code" : " snake_case_string " ,
"message" : " Human-readable message " ,
"timestamp" : " 2026-05-12T10:30:00Z " ,
Always present: code, message, timestamp, trace_id. details is
omitted unless the error includes structured context.
Build your error-handling logic on code — message text can change.
Summary Table
Category Codes HTTP Validation validation_error, bad_request, invalid_json, invalid_query, invalid_storage_key400 Auth unauthorized401 Credits payment_required402 Authorization forbidden403 Not Found not_found404 Conflict conflict, request_in_progress, duplicate_request409 Rate Limit rate_limit_anonymous_abuse, rate_limit_requests_exceeded, rate_limit_tokens_exceeded, rate_limit_cost_exceeded, rate_limit429 Internal internal_error, database_error, serialization_error500 External external_service_error502 Unavailable service_unavailable503
400 — Validation
Code Message Notes validation_errorValidation error: <message>details: { "field": "<name>" }validation_errorRequest validation faileddetails: { "errors": { "<field>": [...] } } — per-field arraysinvalid_jsonInvalid JSON in request bodydetails: { "error": "<rejection>" }invalid_queryInvalid query parametersdetails: { "error": "<rejection>" }bad_requestMESSAGE_REQUIREDAgent execute — no user message bad_requestEMPTY_MESSAGEWhitespace-only message bad_requestclient_tools_not_accepted: ...No AI node accepts client tools bad_requesttoo_many_client_tools: <N> exceeds limit of <M>Client tool count exceeded bad_requestclient_tool_name_collision: '<name>' ...Tool name conflicts with server tool validation_errorinvalid_tool_name: '<name>' must match ^[a-z][a-z0-9_]{0,63}$field: "client_tools"validation_errorpayload size <N> bytes exceeds limit of <M> bytesfield: "variables". Default 16 KiBbad_requestMissing X-Workspace-Id header— bad_requestX-Workspace-Id must be a UUIDv7— bad_requestX-Workspace-Id contains invalid charactersOnly [A-Za-z0-9_-] invalid_storage_keymissing namespace: <key>— invalid_storage_keyunknown namespace: <ns>Only attachments, media, thumbnails
401 — Unauthorized
Code Message Notes unauthorizedMissing authorization header— unauthorizedInvalid tokenJWT verify failed unauthorizedInvalid or expired API keywak_ or eak_ key check failedunauthorizedInvalid credentialsLogin — wrong password unauthorizedRefresh token has been revokedRotation/logout consumed unauthorizedToken verification failed: <error>Firebase JWT verify failed unauthorizedMissing webhook signature headersFal.ai webhook unauthorizedWebhook timestamp expiredFal.ai — 5-min tolerance unauthorizedInvalid webhook tokenRevenueCat webhook
402 — Payment Required
Code Message Notes payment_requiredinsufficient_creditsCredit balance depleted. No details.
403 — Forbidden
Code Message Notes forbiddenAPI key does not have agent:execute scope— forbiddenThis agent does not allow API key accessEnable API Enabled in agent settings forbiddenAPI key does not have access to this agentAgent allowlist mismatch forbiddenExternal auth not enabledWorkspace feature flag off forbiddenCustomer identity requiredEphemeral key missing external_customer_id forbiddenRole '<Role>' cannot <action>Insufficient workspace role forbiddenRevenueCat webhook secret not configuredWorkspace setting missing
404 — Not Found
Code Message not_foundResource not found: Agent not foundnot_foundResource not found: Execution not foundnot_foundResource not found: Workspace not foundnot_foundResource not found: No checkpoint found for execution <id>not_foundResource not found: Conversation not foundnot_foundResource not found: Customer not foundnot_foundResource not found: No rate limit configured for this scope
409 — Conflict
Code Message Notes conflictexecution_already_finished: cannot cancel execution in <status> statedetails: { "status", "execution_id" }conflictexecution_awaiting_client_tool: use /tool-result to fulfilUse the tool-result endpoint conflictexecution_terminal: cannot resume execution with status <status>Start a new turn conflicttool_call_already_fulfilled: '<id>' has already been fulfilled— conflictEmail already in use— request_in_progressA request with this Idempotency-Key is currently being processed— duplicate_requestDuplicate request: this Idempotency-Key has already been used—
429 — Rate Limit
The V2 rate limiter emits four granular codes. Key off code, not message text.
Code Message Details rate_limit_anonymous_abuseSuspicious activity detected. Try again in <N> seconds.{ "reset_in_seconds": N } — no limit disclosedrate_limit_requests_exceededRequest limit of <N> exceeded. Try again in <M> seconds.{ "limit_type": "requests", "scope_type": "...", "limit": N, "reset_in_seconds": M }rate_limit_tokens_exceededToken quota of <N> exceeded. Resets in <M> seconds.{ "limit_type": "tokens", "scope_type": "...", "limit": N, "reset_in_seconds": M }rate_limit_cost_exceededSpend cap reached. Resets in <N> seconds.{ "limit_type": "cost", "scope_type": "...", "reset_in_seconds": N } — limit omittedrate_limitRate limit exceeded. Try again in 60 seconds{ "retry_after": N } — login throttle, token exchange
scope_type values: agent, customer, user, workspace.
Headers on 429:
Header Present for Retry-After: <seconds>Always X-RateLimit-Limit: <N>requests and tokens limit types only
SSE stream rate limit (different shape, emitted mid-stream):
"retry_after_seconds" : 30 ,
"error_type" : " rate_limited " ,
"termination_reason" : " failed "
Note: code: "rate_limited" (not rate_limit_*) is used only in SSE streams.
500 — Internal Error
Code Message Notes internal_errorAn internal error occurred. Please try again later.Real cause logged only database_errorA database error occurred. Please try again later.MVCC conflicts — retry the request serialization_errorA serialization error occurred. Please try again later.—
502 — External Service Error
Code Message external_service_errorAn external service error occurred. Please try again later.
Sources: LLM provider failures, Firebase JWKS fetch, Action node HTTP calls.
The real upstream error is logged but not returned to clients.
503 — Service Unavailable
Code Message Cause service_unavailableCredit service not configuredCredits not enabled on workspace service_unavailableToken exchange not configuredExternal auth not enabled service_unavailableFile storage not configuredS3 not configured on workspace
Special Cases
MISSING_WORKSPACE non-standard envelope — extractors.rs can return
{ "code": "bad_request", "message": "MISSING_WORKSPACE" } without
timestamp/trace_id. Programming error — should not reach production.
WebSocket upgrades — raw status, no body — 401/403/503 from the
WebSocket upgrade endpoint appear as connection failures in the browser with
no JSON body.
SSE stream errors — use the "rate_limited" code (not rate_limit_*).
Always check termination_reason.
database_error (500) MVCC — classified as expected internally. Retry once.