> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usenexio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Runs and statuses

> Submit a run, follow it to a terminal status, retry safely, cancel it, and review the result.

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](/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](/engines/outcomes-and-annotations).

```text theme={null}
POST /runs ──202──▶ queued ──▶ processing ──┬──▶ completed   (output served, run.completed)
                      │                     ├──▶ degraded    (output served, run.completed)
                      ├─ not queued, stale ─┼──▶ failed      (no output,     run.failed)
                      └──── cancel ─────────┴──▶ cancelled   (no output,     run.cancelled)
```

## Submit a run

`POST /api/v1/engines/{engine_slug}/runs` accepts these body fields and refuses any other with `400 invalid_request`:

| Field            | Required | Meaning                                                                                                                                                                                            |
| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input`          | Yes      | The engine's input object. Its shape comes from the engine's type and configuration; read it on the engine's Contract page or in `request_schema` from `GET /api/v1/engines/{engine_slug}/config`. |
| `offerings`      | No       | Alternatives for the types that take them (`comparison`, and optionally `matching`).                                                                                                               |
| `engine_version` | No       | The version pin. See [Versions and releases](/engines/versions#pins).                                                                                                                              |
| `test_scenario`  | No       | A fixed fixture result. See [Test fixtures](#test-fixtures).                                                                                                                                       |
| `submitted_by`   | No       | The acting person. Accepted only when `X-Nexio-Acting-Principal` is also sent with the same value.                                                                                                 |

Send an `Idempotency-Key` header so a retry never creates a second run.

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST https://api.usenexio.com/api/v1/engines/vendor-review/runs \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: harbor-vendor-0042" \
    -d '{
      "input": {
        "request_id": "harbor-vendor-0042",
        "vendor": {
          "name": "Example Logistics",
          "country": "US",
          "annual_spend_usd": 480000,
          "certifications": ["ISO 9001"]
        }
      }
    }'
  ```

  ```python Python theme={null}
  import os

  import requests

  body = {
      "input": {
          "request_id": "harbor-vendor-0042",
          "vendor": {
              "name": "Example Logistics",
              "country": "US",
              "annual_spend_usd": 480000,
              "certifications": ["ISO 9001"],
          },
      }
  }
  resp = requests.post(
      "https://api.usenexio.com/api/v1/engines/vendor-review/runs",
      headers={
          "Authorization": "Bearer " + os.environ["NEXIO_API_KEY"],
          "Idempotency-Key": "harbor-vendor-0042",
      },
      json=body,
      timeout=30,
  )
  print(resp.status_code, resp.json())
  ```

  ```typescript TypeScript theme={null}
  const body = {
    input: {
      request_id: "harbor-vendor-0042",
      vendor: {
        name: "Example Logistics",
        country: "US",
        annual_spend_usd: 480000,
        certifications: ["ISO 9001"],
      },
    },
  };
  const resp = await fetch("https://api.usenexio.com/api/v1/engines/vendor-review/runs", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "harbor-vendor-0042",
    },
    body: JSON.stringify(body),
  });
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

The first call returns `202`:

```json theme={null}
{
  "run_id": "3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57",
  "status": "queued"
}
```

An identical retry returns the same `run_id` with the run's current status.

### Idempotency

| Rule                      | Detail                                                                                                                                                                                                              |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Length                    | At most 255 characters after trimming whitespace, counted as Unicode characters, not bytes. Longer keys get `400 invalid_idempotency_key`.                                                                          |
| Scope                     | Your organization, the key's environment, the submitter (the acting principal when you send one, otherwise the API key), and the key value.                                                                         |
| Same key, same request    | Nexio returns the original `run_id` with `202`. No new run is created and no usage is counted.                                                                                                                      |
| Same key, changed request | `409 idempotency_key_reused`, with the original run in `details.run_id`. The request hash covers the engine, environment, resolved version, `test_scenario`, `input` and `offerings`.                               |
| Lifetime                  | The key has no timer. It stays bound for as long as the run exists. Runs are removed by the daily 90-day retention job, except a run Nexio flags to be kept indefinitely (no API or portal control sets that flag). |

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

| Status       | Terminal | Meaning                                                                        | What to do next                                                                                 |
| ------------ | -------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| `queued`     | No       | The run exists and waits for a worker.                                         | Keep polling.                                                                                   |
| `processing` | No       | A worker is executing the run.                                                 | Keep polling. No output is served yet.                                                          |
| `completed`  | Yes      | The run finished and produced its answer.                                      | Read `output`. Stop polling.                                                                    |
| `degraded`   | Yes      | The run finished with a usable answer, but part of the work could not be done. | Read `output`, then read `output.degradation_reason` or the type's partial block. Stop polling. |
| `failed`     | Yes      | The run could not produce an answer.                                           | Read `error` and `error_details`. Stop polling.                                                 |
| `cancelled`  | Yes      | A cancel request stopped the run before it finished.                           | Stop polling. No output is served.                                                              |

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

| Terminal status | Webhook event   |
| --------------- | --------------- |
| `completed`     | `run.completed` |
| `degraded`      | `run.completed` |
| `failed`        | `run.failed`    |
| `cancelled`     | `run.cancelled` |

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](/events/webhook-events#run-superseded)). After a missed, duplicate or late delivery, poll the run with the `run_id` you stored. See [Webhooks](/api-reference/webhooks/overview) and [Webhook events](/events/webhook-events).

## What each status carries

| Field                                                         | Present when                                                                                                                                                                                                                                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `run_id`, `status`, `environment`, `attempt`, `created_at`    | Always.                                                                                                                                                                                                                                                                                                |
| `engine_type`, `engine_version`, `engine_config_version_hash` | When the run is stamped with them. Every run submitted through the public API is stamped at submission. See [Versions and releases](/engines/versions).                                                                                                                                                |
| `stage`                                                       | When a worker has recorded a pipeline stage. It is a progress hint, not a status.                                                                                                                                                                                                                      |
| `started_at`                                                  | Once a worker has started the run.                                                                                                                                                                                                                                                                     |
| `output`, `output_phase`                                      | Only on `completed` and `degraded` runs. `output_phase` is always `final`.                                                                                                                                                                                                                             |
| `solutions`                                                   | Only on `completed` and `degraded` runs of the `comparison` and `matching` types, when the run produced at least one ranked result.                                                                                                                                                                    |
| `completed_at`, `total_duration_ms`                           | Once the run is terminal.                                                                                                                                                                                                                                                                              |
| `duration_ms`                                                 | On `completed` and `degraded` runs, and on sandbox fixture runs.                                                                                                                                                                                                                                       |
| `error`                                                       | On `failed` runs.                                                                                                                                                                                                                                                                                      |
| `error_details`                                               | On `failed` runs, except a run that failed because it could not be queued. Also on a `processing` run that was restarted after its worker was lost (`type: worker_interrupted`, `retryable: true`), and on some `cancelled` runs.                                                                      |
| `completed_deterministic_at`                                  | On runs of the `comparison` type that record a deterministic phase, once that phase is done. See below.                                                                                                                                                                                                |
| `warnings`                                                    | When the run recorded warnings and the engine's current saved configuration sets `expose_warnings: true` at the time you read the run. The setting is read on each request, not from the release the run used. See [Configuration](/engines/configuration#settings-read-from-the-saved-configuration). |
| `trace_id`                                                    | When the request carried or generated a trace ID. Give it to support.                                                                                                                                                                                                                                  |
| `parked_until`, `park_reason`, `last_parked_until`            | When the run waited for a connected data source. See [Parked runs](#parked-runs).                                                                                                                                                                                                                      |
| `computed_at_head`, `served_head`, `stale`                    | On runs of engines Nexio operates, when the run is recorded as a result in a connected system of record.                                                                                                                                                                                               |
| `work_items`                                                  | On runs that track work items. A compact rollup of required and completed items.                                                                                                                                                                                                                       |
| `input`                                                       | Only when you ask for it with `include=input`.                                                                                                                                                                                                                                                         |

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](/data-services/access).

### `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

| Field               | Meaning                                                                                                              |
| ------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `attempt`           | The execution attempt on which the run reached its terminal status. `1` means the run finished on its first attempt. |
| `duration_ms`       | Duration of the final attempt only, in milliseconds.                                                                 |
| `total_duration_ms` | Wall-clock time from `created_at` to `completed_at`, in milliseconds.                                                |

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.

| Engine type                                | When a run is `degraded`                                                                                                                                                                                                                                                                                                                                                                         | Where the cause is                                                              |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| `entity_analysis`                          | A warning-or-higher diagnostic was recorded that is not exempt. Exempt, and keeping the run `completed` as diagnostics: an outage of the analysis model, and from a registered enrichment source an outage (`ENRICHMENT_DEGRADED`, `ENRICHMENT_DEGRADED_CIRCUIT_OPEN`), a geocode too coarse to use (`ENRICHMENT_PRECISION_BELOW_FLOOR`) or conflicting results (`AMBIGUOUS_ENRICHMENT_RESULT`). | `output.degradation_reason`, and `output.diagnostics`                           |
| `matching`                                 | The answer is partial: part of the candidate search or composition could not be done.                                                                                                                                                                                                                                                                                                            | `output.partial`, `output.composition_failure`, and `warnings` when exposed     |
| `entity_analysis` with a declared contract | Never. A declared-contract run does not end `degraded`.                                                                                                                                                                                                                                                                                                                                          | Not applicable. See [Declared-contract engines](/engines/guides/contract-mode). |
| `comparison`                               | A run that finds nothing to rank ends `completed`, not `degraded`, with `solutions_count: 0`.                                                                                                                                                                                                                                                                                                    | `output.degradation_reason` and `output.diagnostic` on that completed run       |

### `output.degradation_reason`

A stable value you can switch on instead of parsing text.

| Value                 | Meaning                                                                                                                              | Status it appears on            |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| `no_requirements`     | The submission named no requirement categories.                                                                                      | `completed` (`comparison` type) |
| `no_offerings`        | Every offering was filtered out before scoring.                                                                                      | `completed` (`comparison` type) |
| `no_combinations`     | No valid combination of offerings could be built.                                                                                    | `completed` (`comparison` type) |
| `input_quality`       | An input problem you can fix (`MISSING_FIELD`, `MALFORMED_FIELD`, `OUT_OF_RANGE`, `FIELD_CONFLICT`).                                 | `degraded`                      |
| `llm_degraded`        | A model-degraded diagnostic from a source other than the analysis model.                                                             | `degraded`                      |
| `enrichment_degraded` | An enrichment-degraded diagnostic from a source that is not a registered enrichment source.                                          | `degraded`                      |
| `other`               | A warning that matches no known family.                                                                                              | `degraded`                      |
| `scoring_rule_failed` | Defined, but no engine emits it today.                                                                                               | none                            |
| `mixed`               | Defined for forward compatibility. Not emitted today.                                                                                | none                            |
| `insufficient_corpus` | A decline gate on the `comparison` type found no data to rank against. It is emitted only when a platform setting enables that gate. | `completed` (`comparison` type) |

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](/api-reference/webhooks/overview) 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`.

<CodeGroup>
  ```bash curl theme={null}
  curl -s https://api.usenexio.com/api/v1/runs/3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57 \
    -H "Authorization: Bearer $NEXIO_API_KEY"
  ```

  ```python Python theme={null}
  import os
  import time

  import requests

  TERMINAL = {"completed", "degraded", "failed", "cancelled"}


  def wait_for_run(run_id: str) -> dict:
      headers = {"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]}
      delay = 2.0
      while True:
          resp = requests.get(
              f"https://api.usenexio.com/api/v1/runs/{run_id}",
              headers=headers,
              timeout=30,
          )
          if resp.status_code == 429:
              time.sleep(int(resp.headers.get("Retry-After", "1")))
              continue
          resp.raise_for_status()
          run = resp.json()
          if run["status"] in TERMINAL:
              return run
          time.sleep(delay)
          delay = min(delay * 1.5, 30.0)


  run = wait_for_run("3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57")
  print(run["status"])
  ```

  ```typescript TypeScript theme={null}
  const TERMINAL = new Set(["completed", "degraded", "failed", "cancelled"]);

  async function waitForRun(runId: string): Promise<Record<string, unknown>> {
    const headers = { Authorization: `Bearer ${process.env.NEXIO_API_KEY}` };
    let delay = 2000;
    for (;;) {
      const resp = await fetch(`https://api.usenexio.com/api/v1/runs/${runId}`, { headers });
      if (resp.status === 429) {
        const wait = Number(resp.headers.get("Retry-After") ?? "1");
        await new Promise((r) => setTimeout(r, wait * 1000));
        continue;
      }
      if (!resp.ok) throw new Error(`poll failed: ${resp.status} ${await resp.text()}`);
      const run = (await resp.json()) as { status: string };
      if (TERMINAL.has(run.status)) return run;
      await new Promise((r) => setTimeout(r, delay));
      delay = Math.min(delay * 1.5, 30000);
    }
  }

  const run = await waitForRun("3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57");
  console.log(run.status);
  ```
</CodeGroup>

A run that is still working:

```json theme={null}
{
  "run_id": "3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57",
  "engine_type": "entity_analysis",
  "engine_version": "1.0",
  "engine_config_version_hash": "5c1e9a7b3d2f4e60",
  "status": "processing",
  "environment": "test",
  "attempt": 1,
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "created_at": "2026-09-23T14:02:11Z",
  "started_at": "2026-09-23T14:02:12Z"
}
```

A failed run:

```json theme={null}
{
  "run_id": "b7e2d9c4-1a3f-4e6b-8c5d-2f9a0e7b6c13",
  "engine_type": "entity_analysis",
  "engine_version": "1.0",
  "engine_config_version_hash": "5d20b8e4c1f7a693",
  "status": "failed",
  "environment": "test",
  "stage": "FAILED",
  "total_duration_ms": 1807,
  "attempt": 1,
  "error": "required input vendor.name is missing or empty",
  "error_details": {
    "type": "validation_error",
    "message": "required input vendor.name is missing or empty",
    "stage": "INTAKE",
    "step": "parse_submission",
    "attempt": 1,
    "max_attempts": 3,
    "retryable": false
  },
  "trace_id": "0af7651916cd43dd8448eb211c80319c",
  "created_at": "2026-09-23T14:05:40Z",
  "started_at": "2026-09-23T14:05:41Z",
  "completed_at": "2026-09-23T14:05:42Z"
}
```

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](/engines/guides/contract-mode). A full `completed` response is in the [quickstart](/quickstart).

### Include options

`GET /api/v1/runs/{run_id}` takes an optional `include` query parameter, a comma-separated list:

| Value      | Effect                                                                                              |
| ---------- | --------------------------------------------------------------------------------------------------- |
| `input`    | Adds `input`: the submission exactly as the run admitted it.                                        |
| `operator` | Runs of the `matching` type only. Returns `output.operator` as the run stored it, without the trim. |

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.

| Run status when you cancel | Result                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `queued`                   | The run becomes `cancelled` at once. `202` with `status: "cancelled"`.                                                                                                                                                                                                                                                                                                                                                                  |
| `processing`               | Nexio records the request and the worker stops at its next cancellation check. `202` with `status: "processing"`. Poll until `cancelled`, or until another terminal status if the run finished first.                                                                                                                                                                                                                                   |
| Terminal                   | Nothing changes. `200` with the run's status fields and stored `output`. This is not the full `GET` response: it carries no `solutions`, `input`, `work_items`, `warnings`, `computed_at_head`, `served_head` or `stale`. Its `output` is the stored output as written: the `matching` type's operator block is not trimmed and no acting principal's field policy is applied. Read the served result with `GET /api/v1/runs/{run_id}`. |

The first cancel request's time and reason are kept. Repeated requests do not overwrite them.

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST https://api.usenexio.com/api/v1/runs/3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57/cancel \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"reason": "Client withdrew the request"}'
  ```

  ```python Python theme={null}
  import os

  import requests

  resp = requests.post(
      "https://api.usenexio.com/api/v1/runs/3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57/cancel",
      headers={"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]},
      json={"reason": "Client withdrew the request"},
      timeout=30,
  )
  print(resp.status_code, resp.json())
  ```

  ```typescript TypeScript theme={null}
  const resp = await fetch(
    "https://api.usenexio.com/api/v1/runs/3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57/cancel",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ reason: "Client withdrew the request" }),
    },
  );
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

Response for a run that was executing:

```json theme={null}
{
  "run_id": "3f6c2a8e-5b1d-4c7e-9a2f-8d4b6e1c0a57",
  "status": "processing",
  "cancel_requested_at": "2026-09-23T14:02:13.418204Z"
}
```

## Review a run

After a run is terminal, three things let people and systems review it:

| Task                                  | Route                                                          | Page                                                                      |
| ------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Record what happened after delivery   | `POST /api/v1/engines/{engine_slug}/runs/{run_id}/outcomes`    | [Outcomes and annotations](/engines/outcomes-and-annotations)             |
| Attach a person's rating and comment  | `POST /api/v1/engines/{engine_slug}/runs/{run_id}/annotations` | [Outcomes and annotations](/engines/outcomes-and-annotations#annotations) |
| Export the evidence behind the answer | `GET /api/v1/runs/{run_id}/defensibility-packet`               | [Defensibility and trace](/engines/defensibility)                         |

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.

| Field           | Values                            | Rules                                                                                                                                                                                                                                                                                                                       |
| --------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `test_scenario` | `completed`, `degraded`, `failed` | Returns a fixed fixture result with no provider calls. Sandbox environments only, and the run must pin an exact `N.M` version that is in the [supported contract versions](/reference/versioning) registry. Today that registry holds only the generic fixture engine. See [Sandbox fixtures](/reference/sandbox-fixtures). |

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

| Limit             | Value                                                                                                                                  | Error                                                                                              |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Request rate      | 60 requests per minute per organization by default. An engine's configuration can set its own limit with `quotas.requests_per_minute`. | `429 rate_limited` with `Retry-After` in whole seconds                                             |
| Monthly run cap   | Set per organization by Nexio. Live, sandbox and fixture runs all count. No row means no cap.                                          | `429 run_cap_exceeded`. Retrying does not help until the month rolls over or the cap is raised.    |
| Request body      | 1 MiB for every engine type except `triage` (16 MiB). Two engines Nexio operates accept larger bodies.                                 | `413 request_bound_exceeded`                                                                       |
| Request shape     | Per-engine request bounds (string length, array items, object fields, depth, total size)                                               | `400` or `413 request_bound_exceeded`. See [Configuration](/engines/configuration#request-bounds). |
| `Idempotency-Key` | 255 characters                                                                                                                         | `400 invalid_idempotency_key`                                                                      |

See [Limits](/reference/limits) for every platform limit.

## Credentials

| Route                                     | Organization API key | Scoped key                                           |
| ----------------------------------------- | -------------------- | ---------------------------------------------------- |
| `POST /api/v1/engines/{engine_slug}/runs` | Yes                  | Needs `runs:write` and a binding to the engine       |
| `GET /api/v1/runs/{run_id}`               | Yes                  | Needs `runs:read` and a binding to the run's engine  |
| `POST /api/v1/runs/{run_id}/cancel`       | Yes                  | Needs `runs:write` and a binding to the run's engine |

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](/authentication).

## Errors

Submission (`POST /api/v1/engines/{engine_slug}/runs`):

| Status    | Code                                                                                                                                                                 | Cause                                                                                                                                      | Fix                                                              |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| 400       | `invalid_request`                                                                                                                                                    | Body is not JSON, or has a field other than `input`, `offerings`, `engine_version`, `test_scenario`, `submitted_by`.                       | Send only documented fields.                                     |
| 400       | `missing_input`                                                                                                                                                      | No `input` object.                                                                                                                         | Send `input`.                                                    |
| 400       | `invalid_input`                                                                                                                                                      | A known input rule failed. `details` lists `{field, message}`.                                                                             | Fix the named fields.                                            |
| 400       | `invalid_offerings`                                                                                                                                                  | Offerings failed validation, or the request needs offerings and none that was sent can be ranked. `details` names each problem.            | Fix the offerings named in `details`.                            |
| 400       | `acting_principal_mismatch`                                                                                                                                          | Body `submitted_by` is set and the `X-Nexio-Acting-Principal` header is missing or differs.                                                | Send the header, and omit `submitted_by` or send the same value. |
| 400       | `run_requires_acting_principal`                                                                                                                                      | A run of a `matching` engine without an acting principal.                                                                                  | Send `X-Nexio-Acting-Principal`.                                 |
| 400       | `engine_version_required`, `engine_version_exact_required`, `engine_version_not_found`, `engine_version_invalid_format`, `engine_version_draft_requires_sandbox_key` | The version pin cannot be resolved.                                                                                                        | See [Versions and releases](/engines/versions#errors).           |
| 400       | `invalid_engine_type`                                                                                                                                                | The engine's stored type is not a registered type.                                                                                         | Contact support.                                                 |
| 400 / 413 | `request_bound_exceeded`                                                                                                                                             | The submission exceeds a request bound.                                                                                                    | Reduce the submission.                                           |
| 401       | `unauthorized`                                                                                                                                                       | Missing or invalid key.                                                                                                                    | Send a valid key.                                                |
| 403       | `insufficient_capability`, `engine_binding_forbidden`                                                                                                                | Scoped key lacks `runs:write` or the engine binding.                                                                                       | Use a key with the grant.                                        |
| 403       | `engine_archived`                                                                                                                                                    | The engine is archived.                                                                                                                    | Reactivate it with `PATCH /api/v1/engines/{engine_slug}`.        |
| 404       | `engine_not_found`                                                                                                                                                   | No engine with that slug in your organization.                                                                                             | Check the slug.                                                  |
| 409       | `idempotency_key_reused`                                                                                                                                             | Same key, different request.                                                                                                               | Use a new key, or resend the original request.                   |
| 422       | `engine_release_unservable`                                                                                                                                          | A `matching` engine's resolved release cannot be served.                                                                                   | Publish a servable version.                                      |
| 429       | `rate_limited`, `run_cap_exceeded`                                                                                                                                   | Rate limit or monthly cap.                                                                                                                 | Wait for `Retry-After`, or ask to raise the cap.                 |
| 500       | `engine_version_none_released`                                                                                                                                       | No version is released and the run did not pin one.                                                                                        | Publish a version, or pin `draft` in a sandbox.                  |
| 503       | `queue_unreachable`                                                                                                                                                  | The run was created but could not be queued, so Nexio marked it `failed`. A retry with the same `Idempotency-Key` returns that failed run. | Retry later with a new `Idempotency-Key`.                        |

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](/reference/errors).

<CardGroup cols={2}>
  <Card title="Outcomes and annotations" href="/engines/outcomes-and-annotations">
    Record what happened after a run.
  </Card>

  <Card title="Get run status" href="/api-reference/engines/get-run-status">
    The endpoint reference for polling.
  </Card>

  <Card title="Versions and releases" href="/engines/versions">
    How a run picks its engine version.
  </Card>

  <Card title="Webhooks" href="/api-reference/webhooks/overview">
    Receive terminal run events.
  </Card>
</CardGroup>
