Skip to main content
A run is one execution of one engine version over one input. You submit it with POST /api/v1/engines/{engine_slug}/runs, get a run_id back at once, and follow it by polling GET /api/v1/runs/{run_id} or by receiving a webhook. The lifecycle is the same for every engine type: submit, follow, cancel if needed, then review. This page is the one place that defines run statuses, what each status carries, and how retries, cancellation and limits work. Other pages link here. The examples use vendor-review, the fictional entity_analysis engine from the quickstart.

How it works

  1. You send input (and offerings, for the types that take them) to an engine by its slug.
  2. Nexio checks the request synchronously: credentials, body shape, version pin, request bounds, rate limit and monthly run cap. If any check fails, you get an error and no run exists.
  3. Nexio creates the run with status queued and returns 202 Accepted with the run_id.
  4. A worker picks the run up. Its status becomes processing.
  5. The run ends in exactly one terminal status: completed, degraded, failed or cancelled. Nexio emits one webhook event for it.
  6. You read output from GET /api/v1/runs/{run_id}, then record what happened next as outcomes and annotations.

Submit a run

POST /api/v1/engines/{engine_slug}/runs accepts these body fields and refuses any other with 400 invalid_request: Send an Idempotency-Key header so a retry never creates a second run.
The first call returns 202:
An identical retry returns the same run_id with the run’s current status.

Idempotency

On a retry that returns an existing run, status in the 202 body is that run’s current status, not always queued. A run that is executing reads running in this one response. Treat any value as “the run exists” and poll it.

Run statuses

Rules that hold for every run:
  • A run moves from queued to processing when a worker claims it.
  • A run reaches a terminal status only from queued or processing. A terminal status never changes. If two terminal outcomes race, the first one recorded wins.
  • A queued run can go straight to cancelled (on cancel) or to failed (it could not be queued, or it was recovered as stale) without passing through processing.
  • The status values above are the only values GET /api/v1/runs/{run_id} returns.

Webhook event per terminal status

A degraded run sends run.completed, so read data.run.status in the payload to tell them apart. Webhooks are notifications. GET /api/v1/runs/{run_id} is the source of truth for a run; if a webhook and the run disagree, the run wins. The one exception is a correction: GET keeps returning the original output, and in the per-correction delivery setup the corrected output arrives only in a full run.superseded delivery (see run.superseded). After a missed, duplicate or late delivery, poll the run with the run_id you stored. See Webhooks and Webhook events.

What each status carries

A failed or cancelled run never carries output or solutions, even if it did some work first. A queued or processing run never carries them either. When you send X-Nexio-Acting-Principal on GET /api/v1/runs/{run_id}, output is filtered for that person. Every key in output that belongs to a field class the person’s role policy denies is removed, at any depth. If Nexio cannot resolve the person’s policy, the keys of every class the filter covers are removed. The filter applies to output only; solutions is returned as stored. The field classes are applied by the access plane; see Access plane.

completed_deterministic_at

A run of the comparison type can score its deterministic dimensions before its model-scored dimensions. When it does, the platform stamps completed_deterministic_at at that moment, and the field can appear while the run is still processing. It is a timing fact only. It does not mean an answer is available: the answer is served only when the run reaches completed or degraded. Other engine types do not set it.

attempt and durations

Timestamps on the run are RFC 3339 in UTC at second precision.

Degraded runs

degraded means the run finished with an answer you can use, but it lost part of its work. What was lost depends on the engine type.

output.degradation_reason

A stable value you can switch on instead of parsing text. When several warnings are present on a degraded run, the most actionable one wins, in this order: input_quality, llm_degraded, enrichment_degraded, other.

Poll a run

There is no streaming read of a run. Follow it in one of two ways: poll GET /api/v1/runs/{run_id}, or register a webhook endpoint and receive the terminal event. A webhook is a notification; the run you read with GET is the source of truth. Poll until status is one of the four terminal values. Start with a 2 second delay, multiply it by 1.5 after each poll, and cap it at 30 seconds. Honor Retry-After on a 429.
A run that is still working:
A failed run:
Except on a sandbox fixture failure, error_details.type on a failed run is one of validation_error, pipeline_error, dependency_error, persistence_error, pipeline_timeout, cancelled or worker_interrupted, and error_details.retryable says whether submitting the same request again can succeed. A validation_error with retryable: false means the request must change. Optional fields stage, step, code, attempt and max_attempts narrow the cause. A sandbox fixture failure carries only error_details.code. The failed run above is from the vendor-intake engine in Declared-contract engines. A full completed response is in the quickstart.

Include options

GET /api/v1/runs/{run_id} takes an optional include query parameter, a comma-separated list: The response also carries a Server-Timing header with millisecond durations for the server’s own read steps. Use it for diagnosis only.

Parked runs

A run that reads a connected system of record can wait while that source’s data is rebuilt. While it waits, status stays processing and the response carries parked_until (when the run resumes) and park_reason. After it resumes, last_parked_until stays on the run so you can extend your polling budget. Keep polling past parked_until.

Cancel a run

POST /api/v1/runs/{run_id}/cancel asks Nexio to stop a run. The body is optional: {"reason": "..."}, at most 16 KiB, no other fields. The first cancel request’s time and reason are kept. Repeated requests do not overwrite them.
Response for a run that was executing:

Review a run

After a run is terminal, three things let people and systems review it: Recording an outcome or an annotation never changes a run’s output.

Test fixtures

One optional request field changes how a run is treated. It needs a scoped key that holds runs:test. An organization API key can never use it. Errors: 403 test_scenario_forbidden without runs:test; 400 test_scenario_sandbox_only, 400 test_scenario_exact_version_required, 400 test_scenario_version_not_supported, 400 invalid_test_scenario.

Limits

See Limits for every platform limit.

Credentials

A run is visible only in the environment it was submitted in. A run in another organization or environment returns 404 run_not_found. A scoped key that is not bound to the run’s engine gets 403 engine_binding_forbidden. See Authentication and access.

Errors

Submission (POST /api/v1/engines/{engine_slug}/runs): Polling and cancel errors include 400 invalid_run_id (not a UUID), 404 run_not_found, 403 engine_binding_forbidden, 500 load_run_failed, 500 cancel_run_failed, and 400 invalid_request for a cancel body that is not JSON, is over 16 KiB, or has unknown fields. The full error envelope is on Errors.

Outcomes and annotations

Record what happened after a run.

Get run status

The endpoint reference for polling.

Versions and releases

How a run picks its engine version.

Webhooks

Receive terminal run events.
Last modified on September 25, 2026