Errors and retries
Error response format
An error always arrives in this envelope.
{
"error": {
"code": "RATE_LIMITED",
"message": "Request limit exceeded",
"retryable": true,
"request_id": "0b7b8f0e-0000-4000-8000-000000000010",
"details": {"reset_at": "2026-10-05T09:01:00+00:00"}
}
}| Field | Description |
|---|---|
code | A machine readable error code. Branch on this value, not on message. |
message | A human readable English description. It may change. |
retryable | The server’s hint on whether sending the same request again can succeed |
request_id | The ID of this request. Quote it when you contact us. |
details | Extra information that depends on the code, for example reset_at and state_url |
- Every response has an
X-Request-Idheader equal torequest_idin the body. The server issues a new one for each request. - 429 and some 503 responses have a
Retry-Afterheader (seconds). - Every response is
Cache-Control: no-store. - You can send
X-Correlation-Idfor tracing on your side. It is separate from the server generatedrequest_id. - A Turn or Run that was accepted (
202) and fails later is not an HTTP error. You read it fromerror.codein the detail response.
Retry principles
- Retry only errors whose
retryableistrueand network errors (dropped connection, timeout). - If
Retry-Afteris present, wait that long. Otherwise use exponential backoff plus random jitter. - When you retry a create, input, cancel, or upload request, send the same
Idempotency-Keyand the same body. If the request was already accepted, the same receipt comes back, so nothing runs twice. - An error with
retryableoffalsegives the same result if you resend it unchanged. Fix the request or the cause. - If you resend a request whose response you never received under a new key, the same work can run twice. Do not change the key when retrying.
- Put a cap on the number of retries and the total wait.
def post_with_retry(url: str, key: str, body: dict, attempts: int = 5) -> httpx.Response:
for attempt in range(attempts):
try:
response = httpx.post(url, headers={**AUTH, "Idempotency-Key": key}, json=body, timeout=30)
except httpx.TransportError:
response = None
if response is not None and response.status_code < 400:
return response
try:
error = response.json().get("error", {}) if response is not None else {}
except ValueError: # a proxy may answer 502/503/504 without the JSON envelope
error = {"retryable": response.status_code in (429, 502, 503, 504)}
if response is not None and not error.get("retryable"):
response.raise_for_status()
delay = float(response.headers.get("Retry-After", 0)) if response is not None else 0
time.sleep(delay or min(30, 0.5 * 2 ** attempt))
raise RuntimeError("gave up after retries")What each HTTP status means
| Status | Meaning |
|---|---|
| 200, 201 | Success. Reads and close are 200, file upload is 201 |
| 202 | Accepted. Read the result later. |
| 400 | A request error such as a malformed cursor or correlation ID |
| 401 | A key problem. Retrying a revoked key is pointless. |
| 402 | An SU balance or budget problem |
| 403 | Missing scope or permission |
| 404 | The resource does not exist or is outside your access. Existence is not revealed. |
| 409 | Conflict with the current state, for example already running or a key conflict |
| 410 | The content is gone because its retention period passed |
| 413, 415 | Size limit exceeded, unsupported format |
| 422 | A value is outside the allowed range or schema |
| 429 | Call rate or concurrent execution limit |
| 500, 503 | A server side problem. Follow retryable and Retry-After. |
Error code table
This table lists the public codes a caller can receive. A code with HTTP status - appears not as an HTTP response but only in error.code of a Turn or Run detail, and its retryable is false there. retryable describes resending the same request, so it is always false on a finished execution. Whether to create a new execution follows the “What to do after a failed execution” table below. A code used both as an HTTP response and in execution details, such as SESSION_SANDBOX_UNAVAILABLE, has one row: the status and retryable columns describe the HTTP response, and the meaning column also explains what it means in execution details. Codes used only internally are not listed. New codes may be added, so handle an unknown code by retryable and the HTTP status.
| Code | HTTP status | retryable | Meaning |
|---|---|---|---|
NOT_FOUND | 404 | false | The resource does not exist or is outside your access. For a Project that is not approved, every path returns this. |
HTTP_ERROR | 4xx | false | A routing level rejection, such as a path or method that does not match. |
INVALID_API_KEY | 401 | false | The key is missing, malformed, revoked, or expired. |
INSUFFICIENT_SCOPE | 403 | false | The key does not have the scope for this operation. |
ACTING_FORBIDDEN | 403 | false | Delegated access headers or parameters are not allowed. |
WORKFLOW_DATA_DENIED | 403 | false | The workflow tried to use data outside the data grant it received at publication. |
ACTION_NOT_RESOLVABLE_BY_CALLER | 403 | false | The key lacks explicit actions:approve delegation for this approval. For a work preparation scope confirmation, it lacks works:approve. |
ACTION_CONTENT_CHANGED | 409 | false | Review content changed. Fetch it again and ask for a new human decision. |
APPROVAL_CONTENT_UNAVAILABLE | 409 | false | Review content is unavailable or READ grants are insufficient. |
APPROVAL_CONTENT_TOO_LARGE | 413 | false | Review content exceeds 100 rows or 256 KiB. |
INVALID_INPUT | 400, 422 | false | The request body or input format is invalid. Undeclared fields also land here. |
LIMIT_OUT_OF_RANGE | 422 | false | A limits value is outside the allowed range. Values above the cap are not silently reduced. |
INVALID_CURSOR | 400 | false | after or Last-Event-ID is not a cursor of this resource, or they differ from each other. |
INVALID_CORRELATION_ID | 400 | false | The X-Correlation-Id format is invalid. |
INVALID_IDEMPOTENCY_KEY | 422 | false | Idempotency-Key is missing or is not 1 to 256 bytes. |
IDEMPOTENCY_CONFLICT | 409 | false | The same Idempotency-Key was used with a different body or file. Use a new key for a new request. |
INPUT_TOO_LARGE | 413 | false | The JSON request body exceeds 64KiB. |
FILE_TOO_LARGE | 413 | false | The uploaded file exceeds 20MiB. |
UNSUPPORTED_MEDIA_TYPE | 415 | false | The body is not JSON, or it is compressed. |
INVALID_FILENAME | 422 | false | The filename is empty, exceeds 255 bytes, or contains a path separator (slash or backslash) or a control character. As an upload response, fix the name and upload again. In error.code of a Turn or Run detail, it means a result file the AI employee made during the execution had a name that breaks this rule, so the execution failed, and retryable is false there. |
UNSUPPORTED_FILE_TYPE | 415 | false | The filename extension and MIME type are not an allowed pair, or the content is not that format or is unsafe (for example a document with macros, or an encrypted PDF). It comes on upload, or when a Turn input includes a file type the Deployment does not accept. The allowed types are in limits and data. In error.code of a Turn or Run detail, it means a result file made during the execution had a type that is not allowed, so the execution failed, and retryable is false there. |
OUTPUT_SCHEMA_MISMATCH | 422 | false | A value does not match the format or schema set at publication. As an HTTP response, the input of a Run creation request does not match the workflow’s input_schema. In error.code of a Turn or Run detail, it means a result did not match the output contract set at publication, so the execution failed, and retryable is false there. A schema mismatch in a JSON result is first handed back to the AI employee during the execution so it can fix it, and becomes this code only if the execution ends without a fix. |
OUTPUT_LIMIT_EXCEEDED | 422 | false | A value exceeded its size or count limit. As an HTTP response, the input of a Run creation request exceeded a JSON limit (depth 16, 10,000 array elements in total). In error.code of a Turn or Run detail, it means a result exceeded its size or count limit, so the execution failed, and retryable is false there. It is closed as a failure, not silently truncated. |
TOO_MANY_FILES | 422 | false | More than 20 input files, or the same file was listed twice. |
FILE_EXPIRED | 410 | false | The input file is not ready or has expired. Upload it again. |
ARTIFACT_EXPIRED | 410 | false | The retention period has passed, so the result file can no longer be fetched. In the result list, that item shows availability: EXPIRED with this error.code. |
PROJECT_STORAGE_LIMIT | 413 | false | The Project file storage (500MiB) or the organization’s remaining capacity was exceeded. |
RUN_STORAGE_LIMIT | 413 | false | The file total of one execution (100MiB) was exceeded. |
REVISION_CONFLICT | 409 | false | The work or preparation changed after you read it. Read it again and send against the current state. |
PREPARATION_IN_PROGRESS | 409 | false | A preparation for the same work is already in progress. Read the preparation in details.preparation_id. |
INVALID_TRANSITION | 409 | false | The preparation does not accept this request in its current state, for example because it ended or its confirmation deadline passed. |
QUESTION_NOT_FOUND | 409 | false | That preparation question is not open. Read the preparation again. |
PACKAGE_CHANGED | 409 | false | The setup the person reviewed is no longer the candidate. Show the new question and ask again. |
INVALID_SPEC | 422 | false | The work description breaks a rule. See details.reason. |
INVALID_REQUEST | 422 | false | The work preparation request breaks a rule. See details.reason. |
SESSION_BUSY | 409 | false | The Session already has a running Turn. Send after you confirm it ended. |
SESSION_CLOSED | 409 | false | A closed Session does not accept input or files. |
SESSION_EXPIRED | 410 | false | The retention period of the Session content has ended. |
SESSION_UPDATING | 503 | true | The Session is being updated. Resend with the same Idempotency-Key. |
SESSION_SANDBOX_UNAVAILABLE | 503 | true | The Session execution environment could not be prepared right now. As an HTTP response, try again shortly. In error.code of a Turn or Run detail, it means the execution environment preparation step did not finish in time before work started, so the Turn failed, and retryable is false there. When this happens in a workflow step, the Run usually ends with this code too. If a request sent during preparation could not be confirmed, the Run stays EXTERNAL_EFFECT_UNKNOWN until usage is confirmed. Running out of time while waiting for an earlier execution is still EXECUTION_TIMEOUT. The request itself is fine, so create a new execution later. |
DEPLOYMENT_UNAVAILABLE | 409 | false | This Deployment cannot accept this operation now. Check its status. |
WORKFLOW_UNAVAILABLE | 409 | false | The workflow is not published, is stopped, or cannot be started through the API (for example a workflow that only starts from mail), so Run creation (POST /v1/workflows/{workflow_id}/runs) is refused. GET /v1/workflows/{workflow_id} reports this state not as an error response but as available: false and error.code in a 200 body. If the workflow is stopped during a Run, the Run ends CANCELED with this value in error.code of the Run detail. Try again after the publisher publishes or turns on the workflow again. |
WORKFLOW_MANAGED_SESSION | 409 | false | This is an internal Session used by a workflow. Use the Run paths. |
RUN_TERMINAL | 409 | false | The Run already has a settled outcome. |
ACTION_EXPIRED | 409 | false | The answer deadline of the question has passed. |
ACTION_CANCELED | 409 | false | The execution no longer waits for this question. |
MINIMUM_SU_REQUIRED | 402 | false | File operations need an account balance of at least 50 SU. 50 SU is not charged. |
INSUFFICIENT_SU | 402 | false | There is not enough available SU. |
BUDGET_EXCEEDED | 402 | false | The budget of this execution is exhausted or closed. |
PROJECT_BUDGET_EXCEEDED | 402 | true | The Project daily or monthly spend limit was exceeded. It works again after the limit window passes. |
RATE_LIMITED | 429 | true | The call rate limit was exceeded. Wait for Retry-After. |
CONCURRENCY_LIMIT | 429 | true | The Project concurrent execution limit was exceeded. Try again after an execution ends. |
SOURCE_UNAVAILABLE | 503 | true | The organization data store used to check the workflow input cannot be reached right now, so Run creation (POST /v1/workflows/{workflow_id}/runs) was refused. No Run was created. Resend later with the same Idempotency-Key. If it continues, tell the publisher. |
BILLING_UNAVAILABLE | 503 | true | The billing account could not be checked right now. Try again shortly. |
ARTIFACT_NOT_READY | 409 | true | The file is not ready yet. Try again shortly. |
ARTIFACT_UNAVAILABLE | 503 | true | The stored file cannot be read right now. Try again. |
STORAGE_UNAVAILABLE | 503 | true | File storage is unavailable right now. Try again. |
EVENT_CURSOR_EXPIRED | 410 | false | The event cursor has expired. Recover with the addresses in details. |
OUTPUT_MANIFEST_UNAVAILABLE | 503 | true | The result list cannot be read right now. Try again. |
INTERNAL_ERROR | 500 | false | An internal server error. Contact us with the request_id. |
EXECUTION_TIMEOUT | - | false | The Turn failed because it exceeded the execution time limit. |
QUEUE_TIMEOUT | - | false | The execution did not start within the queue time limit and expired. |
RECOVERY_EXHAUSTED | - | false | The server tried several recoveries but could not finish. |
RUN_DEADLINE_EXCEEDED | - | false | The whole Run deadline was reached first and the Run expired. |
EXTERNAL_EFFECT_UNKNOWN | - | false | It stopped because the outcome of a write to an external system could not be confirmed. Check the outcome yourself. It is not rerun automatically. |
ACTION_REQUIRED_UNSUPPORTED | - | false | A human answer or approval became necessary, which the API does not support yet, so it failed. |
CAPABILITY_DENIED | - | false | It tried to use a tool or capability that was not allowed at publication. |
SESSION_RESOURCES_NOT_READY | - | false | It called a tool allowed by the publication that this stage does not yet support. |
ARTIFACT_PREPARATION_TIMEOUT | - | false | Result file preparation exceeded its deadline. |
ARTIFACT_PREPARATION_FAILED | - | false | A result file was not ready or had already expired when the execution ended, so it could not be returned. |
OUTPUT_URL_INVALID | - | false | A URL result is not a public HTTPS address. Internal network addresses and addresses that carry login details, signatures, or tokens are refused too. |
OUTPUT_CONTAINS_CREDENTIALS | - | false | A result (value, filename, file content, or URL) contains a credential pattern such as a key or password, so it was not returned. |
OUTPUT_PATH_DENIED | - | false | It tried to return a file from outside the Session attachments folder as a result. |
OUTPUT_MISSING | - | false | A result marked required at publication was not produced. |
EXECUTION_FAILED | - | false | The execution failed because of an unexpected error. |
CANCELED | - | false | The execution stopped because of a cancel request. |
AUTHORIZATION_REVOKED | - | false | The execution was canceled because the key was revoked or expired, or its scope, the Deployment, or an account connection the Deployment uses became unusable. |
SESSION_DELETED | - | false | The Session was deleted, so its running Turn was canceled. |
RUNTIME_CONTEXT_MISMATCH | - | false | The execution failed because the Session was not in a state that can run it. |
USAGE_PENDING | - | false | The execution could not continue because earlier usage could not be confirmed as settled. |
What to do after a failed execution
Once a Turn or Run ends as FAILED, CANCELED, or EXPIRED, that execution does not come back. To try again, create a new Turn or Run with a new Idempotency-Key. This differs from the retry above, which resends an unanswered request with the same key. Reusing the finished execution’s key returns that finished execution’s receipt again.
Follow the order below even when an AI tool makes the calls for you. A new execution costs money again.
| Decision | Codes | Next step |
|---|---|---|
| Once more, later | QUEUE_TIMEOUT, RECOVERY_EXHAUSTED, EXECUTION_FAILED, USAGE_PENDING, ARTIFACT_PREPARATION_TIMEOUT, ARTIFACT_PREPARATION_FAILED, SESSION_SANDBOX_UNAVAILABLE | Wait, then create one new execution with the same input. If it ends with the same code again, stop and contact us with the execution ID. |
| After changing conditions | EXECUTION_TIMEOUT, RUN_DEADLINE_EXCEEDED, BUDGET_EXCEEDED, INSUFFICIENT_SU | Shrink or split the input, and raise the deadline or budget within the range in limits and data or top up SU, then create a new execution. Do not resend right away under the same conditions. |
| Publishing settings | OUTPUT_SCHEMA_MISMATCH, OUTPUT_LIMIT_EXCEEDED, OUTPUT_MISSING, OUTPUT_PATH_DENIED, CAPABILITY_DENIED, SESSION_RESOURCES_NOT_READY, ACTION_REQUIRED_UNSUPPORTED, WORKFLOW_UNAVAILABLE | The caller often cannot fix these. Do not repeat the same input. Tell the person who published the Deployment or workflow. If your input made the result exceed a limit, shrink the input and create a new execution. For WORKFLOW_UNAVAILABLE, create a new Run only after the workflow is published or turned on again. |
| Check the result content | OUTPUT_URL_INVALID, OUTPUT_CONTAINS_CREDENTIALS, INVALID_FILENAME, UNSUPPORTED_FILE_TYPE | A result the AI employee made broke a public result rule. Check that your input did not ask for an internal address, a credential, or a file type or name that is not allowed in the result, then create one new execution with the corrected input. If it ends with the same code again, tell the person who published the Deployment or workflow. |
| Check permissions | AUTHORIZATION_REVOKED | Check the key, its scope, the Deployment status, and the publisher’s account connections. Create a new execution only after you receive a new key or permission or the connection is restored. |
| In a new Session | RUNTIME_CONTEXT_MISMATCH | Create a new Session and run there. If it repeats, contact us with the execution ID. |
| Do not run again | EXTERNAL_EFFECT_UNKNOWN | The external system may already have been written to. Do not create a new execution until you check the result in that system yourself. |
| Intended end | CANCELED, SESSION_DELETED | Whoever asked for the cancel or deletion decided this. Create a new execution only if that decision changes. |
For a code not in this table, do not run again. Stop and tell a person.
When a result the AI employee produces during an execution breaks a public result rule, the execution ends as a failure right there. Among result format errors, only a schema mismatch in a JSON result is handed back to the AI employee during the execution to fix. In a workflow that returns only some results instead of failing (PARTIALLY_SUCCEEDED), the same code stays in error.code of the failed result item.
A failed organization work preparation is not an HTTP error either. Its reason is in the preparation’s reason_codes, and what to do next is in organization works.