Skip to main content

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​

  1. Read the HTTP status, then error.code. The code is stable and for your program; error.message is for a person.
  2. On 429 and 503, wait Retry-After seconds and retry the same request; on POST …/runs with the same Idempotency-Key, so an identical replay answers 200 with the run that already exists.
  3. On 400 and 422, fix the request. Resend a refused POST …/runs with a new Idempotency-Key.
  4. 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.
  5. On 409, read the code. run_not_terminal, redaction_pending and idempotency_conflict while processing mean "not yet" and are retried; the rest mean "already done" or "fix in Studio".
  6. On 451, 500 and 502, quote error.request_id (also the X-Request-Id response header) to your administrator or support.

Per-key limits​

LimitDefaultMaximumWhen it is fullRetry-After
Requests per minute, every route, polling included6010,000429 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 UTC100100,000429 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 cancelled10100429 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​

ItemBound
Request body, every route1 MiB (1,048,576 bytes). A larger body answers 413 payload_too_large before it is read, and before the key is checked
Idempotency-Key1 to 128 printable ASCII characters; replays recognised for 24 hours
X-Request-Id1 to 128 characters from A-Z a-z 0-9 . _ - :
input_text20,000 characters
client_reference256 printable ASCII characters
labels10 keys; keys up to 64 and values up to 256 printable ASCII characters
reason on cancel and erase500 characters
q on GET /workflows200 characters
limit on GET /workflows and GET /runs1 to 100, default 20
limit on GET /runs/{run_id}/events1 to 500, default 200
Specific workflows per key50
One output download25 MB; larger files answer 413 use_bundle
Output bundle500 MB and 2,000 files; beyond that 413 bundle_too_large
result returned inline256 KB; larger files are fetched through result_json.download.path
PollingNo more than one GET /runs/{run_id} every 5 seconds per run
Per-request usage recordsKept 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​

HTTPcodeWhendetails
400invalid_requestMalformed 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>"}
400invalid_parametersThe run's parameters fail the version's contract[{name, reason}], one per problem
400invalid_filterA GET /runs filter is not valid (a date that is not ISO-8601, a bad client_reference)
400invalid_cursorA paging cursor the API did not issue
400policy_not_tighteningpii_policy_override loosens the workflow's policy{field}
400invalid_pii_policypii_policy_override is not a valid policy{field}
401unauthorizedKey or external id missing, malformed, unknown, wrong, revoked, expired, or bound to another deployment; always the same message
403forbiddenSource address outside the allow-list, or the route's scope is missing
403outside_time_windowOutside the key's allowed hours{valid_hours_utc: {start_hour, end_hour}}
404workflow_not_found, run_not_found, output_not_found, segment_not_foundNot found, or not within this key's reach; the two are indistinguishable
404pii_redaction_not_appliedGET /runs/{run_id}/pii-redaction on a run whose redaction policy was off
404not_foundA path that does not exist under /api/v1, or a run whose board or event log no longer exists on the runtime
405method_not_allowedThe path exists, the method does not
409idempotency_conflictThe same Idempotency-Key with a different body; or the first request is still being processed (Retry-After: 1)
409workflow_not_activeThe 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
409workflow_not_readyA 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]}
409run_not_terminalOutputs asked for, or erasure requested, while the run is still going{status}
409redaction_pendingThe run finished but the final redaction sweep of its outputs has not (Retry-After: 2){status}
409already_terminalCancel of a run that has already finished
409already_purgedErasure of a run already erased{data_purged_at}
413use_bundleOne output is over 25 MB{bundle}: the zip path
413bundle_too_largeThe zip would exceed 500 MB or 2,000 files
413payload_too_largeA request body over 1 MiB, refused before it is read (and before the key is checked)
416range_not_satisfiableThe Range header does not fit the file; Content-Range: bytes */<size> is sent
422version_not_foundThe version you asked for does not exist
429rate_limited, quota_exceeded, too_many_active_runsA per-key limit is full; honour Retry-After (table above)
451license_unavailableThe 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}
500internalUnexpected failure; "Internal error; quote request_id to support."
502runtime_errorThe deployment's runtime answered with an error
502runtime_unreachableThe deployment's runtime did not answer; "retry shortly"
503deployment_updatingProgrammatic 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.codeMeaning
input_materialisation_failedAn 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_failedA step failed; GET /runs/{run_id}/steps shows which and why
wall_clock_limit_reachedThe run reached the workflow's time limit (settings.max_run_hours)
invariant_blockedA step was blocked and the run could not continue
launch_failedThe run never reached the runtime
orphanedThe runtime lost track of the run
license_unavailableThe 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_exceeded every afternoon: the quota is too low for your volume. 429 too_many_active_runs with few runs of your own in flight: earlier runs are still going or waiting for an approval; GET /runs?status=running,waiting_for_approval shows them.
  • 503 deployment_updating for longer than a few minutes: not_governed is permanent for that deployment; not_enabled clears once a key is mirrored; keys_table_unreachable is 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, not failed, and has no error code: GET /runs/{run_id} and the run summaries carry the reason in warnings[] and set completed_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.