Limits, errors and codes
Every limit the API enforces, every Retry-After it sends, the one error shape, and every status and error code, for whoever writes the caller's retry and error handling or reads an error it logged. The three per-key limits are your administrator's, shown on GET /me; every other limit is fixed by the product.
Steps
- Read the HTTP status, then
error.code. The code is stable and for your program;error.messageis for a person. - On 429 and 503, wait
Retry-Afterseconds and retry the same request; onPOST …/runswith the sameIdempotency-Key, so an identical replay answers 200 with the run that already exists. - On 400 and 422, fix the request. Resend a refused
POST …/runswith a newIdempotency-Key. - On 401 and 403, stop and alert your operators. Retrying does not help; the key, its allow-list, its window or its scopes have to change.
- On 409, read the code.
run_not_terminal,redaction_pendingandidempotency_conflictwhile processing mean "not yet" and are retried; the rest mean "already done" or "fix in Studio". - On 451, 500 and 502, quote
error.request_id(also theX-Request-Idresponse header) to your administrator or support.
Per-key limits
| Limit | Default | Maximum | When it is full | Retry-After |
|---|---|---|---|---|
| Requests per minute, every route, polling included | 60 | 10,000 | 429 rate_limited, "Rate limit of N requests per minute reached; retry after S s." | Seconds to the next minute |
| Runs started per day; resets at 00:00 UTC | 100 | 100,000 | 429 quota_exceeded, "Daily run quota of N reached; resets at 00:00 UTC." | Seconds to the next UTC day |
| Runs in flight; a run holds its slot until it finishes or is cancelled | 10 | 100 | 429 too_many_active_runs, "This key already has N runs in flight; wait for one to finish." | 5 |
On POST …/runs the slot is taken before the quota is counted, so a too_many_active_runs refusal does not use a quota unit.
Request and payload bounds
| Item | Bound |
|---|---|
| Request body, every route | 1 MiB (1,048,576 bytes). A larger body answers 413 payload_too_large before it is read, and before the key is checked |
Idempotency-Key | 1 to 128 printable ASCII characters; replays recognised for 24 hours |
X-Request-Id | 1 to 128 characters from A-Z a-z 0-9 . _ - : |
input_text | 20,000 characters |
client_reference | 256 printable ASCII characters |
labels | 10 keys; keys up to 64 and values up to 256 printable ASCII characters |
reason on cancel and erase | 500 characters |
q on GET /workflows | 200 characters |
limit on GET /workflows and GET /runs | 1 to 100, default 20 |
limit on GET /runs/{run_id}/events | 1 to 500, default 200 |
| Specific workflows per key | 50 |
| One output download | 25 MB; larger files answer 413 use_bundle |
| Output bundle | 500 MB and 2,000 files; beyond that 413 bundle_too_large |
result returned inline | 256 KB; larger files are fetched through result_json.download.path |
| Polling | No more than one GET /runs/{run_id} every 5 seconds per run |
| Per-request usage records | Kept in the deployment for 90 days, visible to your administrator |
The error envelope
{
"error": {
"code": "invalid_parameters",
"message": "One or more parameters are invalid.",
"request_id": "<request_id>",
"details": [{"name": "applicant_id", "reason": "required"}]
}
}
code is stable and machine-readable; message is for a person; request_id is echoed in X-Request-Id; details appears when there is something structured to add, in the shape noted per code below. A 401 also carries WWW-Authenticate: Bearer; a 503 always carries details.reason.
Status codes and error codes
| HTTP | code | When | details |
|---|---|---|---|
| 400 | invalid_request | Malformed JSON, or a missing or malformed Idempotency-Key | [{name, reason}] for a body that fails validation; for malformed JSON one entry, {"name": "body", "reason": "JSON decode error at byte <n>: <why>"} |
| 400 | invalid_parameters | The run's parameters fail the version's contract | [{name, reason}], one per problem |
| 400 | invalid_filter | A GET /runs filter is not valid (a date that is not ISO-8601, a bad client_reference) | |
| 400 | invalid_cursor | A paging cursor the API did not issue | |
| 400 | policy_not_tightening | pii_policy_override loosens the workflow's policy | {field} |
| 400 | invalid_pii_policy | pii_policy_override is not a valid policy | {field} |
| 401 | unauthorized | Key or external id missing, malformed, unknown, wrong, revoked, expired, or bound to another deployment; always the same message | |
| 403 | forbidden | Source address outside the allow-list, or the route's scope is missing | |
| 403 | outside_time_window | Outside the key's allowed hours | {valid_hours_utc: {start_hour, end_hour}} |
| 404 | workflow_not_found, run_not_found, output_not_found, segment_not_found | Not found, or not within this key's reach; the two are indistinguishable | |
| 404 | pii_redaction_not_applied | GET /runs/{run_id}/pii-redaction on a run whose redaction policy was off | |
| 404 | not_found | A path that does not exist under /api/v1, or a run whose board or event log no longer exists on the runtime | |
| 405 | method_not_allowed | The path exists, the method does not | |
| 409 | idempotency_conflict | The same Idempotency-Key with a different body; or the first request is still being processed (Retry-After: 1) | |
| 409 | workflow_not_active | The workflow exists in the deployment but is a draft or inactive. A key with All active workflows access gets this on GET /workflows/{workflow_id}, its /parameters and POST …/runs alike (not a 404: the workflow is real, its status bars it). A key with an explicit workflow list reads a listed draft (200, readiness.reasons says why) and gets this only at POST …/runs | |
| 409 | workflow_not_ready | A step has no agent, a connector is missing, needs attention or has no recorded passing Test connection on any version (the test at an AWS connector's create or update counts; the reason says to press Test connection on its page), or an environment is not active | {reasons: [string]} |
| 409 | run_not_terminal | Outputs asked for, or erasure requested, while the run is still going | {status} |
| 409 | redaction_pending | The run finished but the final redaction sweep of its outputs has not (Retry-After: 2) | {status} |
| 409 | already_terminal | Cancel of a run that has already finished | |
| 409 | already_purged | Erasure of a run already erased | {data_purged_at} |
| 413 | use_bundle | One output is over 25 MB | {bundle}: the zip path |
| 413 | bundle_too_large | The zip would exceed 500 MB or 2,000 files | |
| 413 | payload_too_large | A request body over 1 MiB, refused before it is read (and before the key is checked) | |
| 416 | range_not_satisfiable | The Range header does not fit the file; Content-Range: bytes */<size> is sent | |
| 422 | version_not_found | The version you asked for does not exist | |
| 429 | rate_limited, quota_exceeded, too_many_active_runs | A per-key limit is full; honour Retry-After (table above) | |
| 451 | license_unavailable | The deployment's licence is on hold, so its runtime refuses work; the message is the one your administrator sees in the Organisations console | {state, reason, enforcement_status, license_id} |
| 500 | internal | Unexpected failure; "Internal error; quote request_id to support." | |
| 502 | runtime_error | The deployment's runtime answered with an error | |
| 502 | runtime_unreachable | The deployment's runtime did not answer; "retry shortly" | |
| 503 | deployment_updating | Programmatic access is not being served: not_governed (a standard deployment; permanent), not_enabled (no key has ever been mirrored to the deployment) or keys_table_unreachable (the key store could not be read). Retry-After: 60 | {reason} |
Failure codes on a failed run
Not HTTP errors: the error.code on GET /runs/{run_id} when status is failed.
error.code | Meaning |
|---|---|
input_materialisation_failed | An s3_prefix input could not be copied in before any step ran: the connector's role was denied, the prefix was empty, or a cap was exceeded |
node_failed | A step failed; GET /runs/{run_id}/steps shows which and why |
wall_clock_limit_reached | The run reached the workflow's time limit (settings.max_run_hours) |
invariant_blocked | A step was blocked and the run could not continue |
launch_failed | The run never reached the runtime |
orphaned | The runtime lost track of the run |
license_unavailable | The runtime refused the launch because the deployment's licence is on hold; the same code is a live 451 on the other routes |
The OpenAPI file
An OpenAPI 3.1 document titled "AlphaAgent Studio - Programmatic Workflow API", version 2026-09-24 (the api_version on GET /health): Download openapi-v1.yaml. It declares all 17 routes, the two security schemes (ApiKey bearer and the X-AlphaAgent-Key-External-Id header, required on every path but /health), every request and response shape, the ErrorEnvelope and the codes by status under x-error-codes (the same table as above). Load it into your API client or code generator and set its app_domain server variable to your deployment's domain.
What you should see
A rate-limited call, as a caller sees it:
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-Request-Id: <request_id>
{"error": {"code": "rate_limited",
"message": "Rate limit of 60 requests per minute reached; retry after 37 s.",
"request_id": "<request_id>"}}
Notes
- The limits here are the API's own; Studio's limits on what a person can build are on Limits and quotas and do not change what a key may do.
- Frequent
429 rate_limited: poll less often (at most every 5 seconds per run), spread calls, or have the rate limit raised.429 quota_exceededevery afternoon: the quota is too low for your volume.429 too_many_active_runswith few runs of your own in flight: earlier runs are still going or waiting for an approval;GET /runs?status=running,waiting_for_approvalshows them. 503 deployment_updatingfor longer than a few minutes:not_governedis permanent for that deployment;not_enabledclears once a key is mirrored;keys_table_unreachableis the deployment's own fault and your administrator sees it on the deployment's health.451 license_unavailable: only your administrator can lift the hold.- The API keeps serving through a rolling upgrade of the deployment (the update screen is the web app's alone); the upgrade restarts every service, so a run in flight at that moment is interrupted rather than resumed (What a roll does).
- A run that finished although a stage did not land (a knowledge-graph publish still building when its wait expired) is
completed, notfailed, and has no error code:GET /runs/{run_id}and the run summaries carry the reason inwarnings[]and setcompleted_with_warnings: true; outputs are available as for any completed run. - An error body that is not the envelope means you reached something other than the API: a sign-in redirect from a path outside
/api/v1, or an edge in front of the deployment that refused the address. Check the base URL and your allow-list.
Related
- Authentication: the order in which 503, 401, 403 and 429 are decided.
- Run a workflow from your system: the routes these codes come from.
- API keys
- Failure codes: the Organisations job failure codes alongside these API codes.
- Support