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

# Versions and releases

> Publish engine versions, pin runs to them, and upgrade without surprises.

A version is an immutable release of an engine's configuration, numbered `major.minor` (for example `1.0`, `1.3`, `2.0`). Each version points at one archived configuration and at the request and response schemas derived from it. Runs choose a version at submission and keep it for their whole life. Versions let you change an engine while production callers keep a fixed contract until they choose to move.

## How it works

1. You save changes to the draft configuration. Runs that do not pin `draft` execute as before. Three settings, `expose_warnings`, `notify_on_supersede` and `quotas.requests_per_minute`, are read from the saved configuration. See [Configuration](/engines/configuration#settings-read-from-the-saved-configuration).
2. You publish. Nexio validates the configuration, runs the evaluation gate, derives the request and response schemas, decides the version number, and records the release.
3. Each run submitted through the API resolves its `engine_version` pin at submission and is stamped with the resolved version and configuration hash.
4. The worker executes the stamped configuration. A later publish does not move a run that already exists.

A new engine has no released version. Until you publish one, a run that does not pin a version fails with `500 engine_version_none_released`. From a sandbox key you can run the draft with `engine_version: "draft"` before you publish.

## Pins

Send `engine_version` in the run request body.

| Pin                      | Resolves to                            | Allowed                                                                                                                                                                                      |
| ------------------------ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| omitted                  | The latest released version            | On engines with the default pin policy. Refused with `400 engine_version_required` on `exact_required` engines.                                                                              |
| `N.M`, for example `1.3` | Exactly that release                   | Always, if it exists. Otherwise `400 engine_version_not_found`.                                                                                                                              |
| `N.x`, for example `1.x` | The latest released minor of major `N` | On engines with the default pin policy, if major `N` has a release (otherwise `400 engine_version_not_found`). Refused with `400 engine_version_exact_required` on `exact_required` engines. |
| `draft`                  | The current draft configuration        | Sandbox environments only. Otherwise `400 engine_version_draft_requires_sandbox_key`. The run is stamped `"draft"`.                                                                          |

Anything else, including a bare major (`1`) or a three-part version (`1.0.0`), is refused with `400 engine_version_invalid_format`.

### Pin policies

Each engine has one pin policy.

| Policy                  | Meaning                                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------ |
| `latest_release_legacy` | The default. Omitted pins and `N.x` pins are allowed.                                                  |
| `exact_required`        | Every run must pin an exact `N.M`. Nexio sets this policy on an engine; it is not a configuration key. |

### What is stamped on a run

`GET /api/v1/runs/{run_id}` and webhook payloads return `engine_version` (the resolved `N.M`, or `draft`) and `engine_config_version_hash` (the 16-character hash of the configuration the run executed). Store both with your own record of the result.

### What a version freezes

A version freezes the configuration bytes and the request and response schemas. It does not freeze model output, external data, provider behavior or latency. Two runs on the same version with the same input can differ where a model or an external source is involved.

## Publish a version

`POST /api/v1/engines/{engine_slug}/versions` releases the current draft. The body is optional.

| Field                             | Meaning                                                                                                                                                                |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `changelog`                       | Text recorded on the version.                                                                                                                                          |
| `version`                         | On `exact_required` engines, the version you expect. If it differs from the version Nexio computes, the publish is refused with `409`. On other engines it is ignored. |
| `is_breaking_from_previous_major` | Ignored. The platform computes it from the version bump.                                                                                                               |

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST https://api.usenexio.com/api/v1/engines/vendor-review/versions \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"changelog": "Tighten the wording of security recommendations."}'
  ```

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

  import requests

  resp = requests.post(
      "https://api.usenexio.com/api/v1/engines/vendor-review/versions",
      headers={"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]},
      json={"changelog": "Tighten the wording of security recommendations."},
      timeout=30,
  )
  print(resp.status_code, resp.json())
  ```

  ```typescript TypeScript theme={null}
  const resp = await fetch("https://api.usenexio.com/api/v1/engines/vendor-review/versions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ changelog: "Tighten the wording of security recommendations." }),
  });
  console.log(resp.status, await resp.json());
  ```
</CodeGroup>

Response `200`:

```json theme={null}
{
  "version": "1.1",
  "released_at": "2026-09-23T16:20:05Z",
  "changelog": "Tighten the wording of security recommendations.",
  "is_breaking_from_previous_major": false,
  "bump": "minor",
  "published_by": "api_key:9c1e4b7a-2d6f-4a83-b5e0-7f2c1d9a8e36",
  "gate_verified": false
}
```

Publishing an unchanged draft again is not an error. It returns the current latest version with `already_released: true` and creates nothing.

Publishing from the portal (the version control in the top bar on an engine's pages) is done by Nexio today. It shows a preview of the version number and the breaking-change reasons before Nexio confirms. Your integration publishes through `POST /api/v1/engines/{engine_slug}/versions`, and both paths pass the same evaluation gate.

### Version numbers

The first release is `1.0`. After that, Nexio compares the new schemas with the latest release and bumps the major or the minor.

On engines with the default pin policy, a change is **major** when:

* an output field is removed, or its type changes;
* a new input field is required;
* an input field's type changes;
* an optional input field becomes required.

Everything else is **minor**: added output fields, new optional input fields, removed input fields, required fields that become optional, and changes to behavior with no schema change.

On `exact_required` engines, any change to the request schema or the response schema is major, with reasons `request_schema_changed` and `response_schema_changed`. A configuration change that leaves both schemas identical is minor.

`is_breaking_from_previous_major` is `true` only on an `N.0` release that follows an earlier major. `reasons` lists what made a major bump.

### The evaluation gate

Publishing runs the evaluation gate against the candidate configuration:

* An engine with no evaluation set publishes without a gate.
* Every release needs a pass on each evaluation set that declares a tolerance, that is, a limit on how many labeled cases may disagree with their labels.
* The first release of an engine that has evaluation sets, none of which declares a tolerance, needs a completed evaluation of the candidate with no case errors.
* Later releases are not blocked by sets without a tolerance.

`gate_verified` is `true` only on an engine's first release, and only when the passing evaluation compared the candidate against labeled cases. Later releases read `false`, even when they passed the gate. `cold_start_eval_set_id` names that set. Nexio manages evaluation sets; see [Evaluation](/engines/evaluation).

## List and read versions

<CodeGroup>
  ```bash curl theme={null}
  curl -s https://api.usenexio.com/api/v1/engines/vendor-review/versions \
    -H "Authorization: Bearer $NEXIO_API_KEY"
  ```

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

  import requests

  resp = requests.get(
      "https://api.usenexio.com/api/v1/engines/vendor-review/versions",
      headers={"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]},
      timeout=30,
  )
  for v in resp.json()["versions"]:
      print(v["version"], v["released_at"], v["changelog"])
  ```

  ```typescript TypeScript theme={null}
  const resp = await fetch("https://api.usenexio.com/api/v1/engines/vendor-review/versions", {
    headers: { Authorization: `Bearer ${process.env.NEXIO_API_KEY}` },
  });
  const { versions } = await resp.json();
  for (const v of versions) console.log(v.version, v.released_at, v.changelog);
  ```
</CodeGroup>

Response `200`, newest first:

```json theme={null}
{
  "versions": [
    {
      "version": "1.1",
      "released_at": "2026-09-23T16:20:05Z",
      "changelog": "Tighten the wording of security recommendations.",
      "request_schema_hash": "3b7f2c91d04e8a6f5c1b9e27d8a04f63b5c2e19d7a8f04b6c3e1d95a7f2b08c4",
      "response_schema_hash": "c8e14a7b2f9d03e6a5b18c47f2d90e3b6a7c15d8e4f29b0a3c6d71e85f4a2b90",
      "is_breaking_from_previous_major": false,
      "gate_verified": false,
      "published_by": "api_key:9c1e4b7a-2d6f-4a83-b5e0-7f2c1d9a8e36"
    },
    {
      "version": "1.0",
      "released_at": "2026-09-23T13:58:40Z",
      "changelog": "Initial release: security and financial health review.",
      "request_schema_hash": "3b7f2c91d04e8a6f5c1b9e27d8a04f63b5c2e19d7a8f04b6c3e1d95a7f2b08c4",
      "response_schema_hash": "c8e14a7b2f9d03e6a5b18c47f2d90e3b6a7c15d8e4f29b0a3c6d71e85f4a2b90",
      "is_breaking_from_previous_major": false,
      "gate_verified": false,
      "published_by": "api_key:9c1e4b7a-2d6f-4a83-b5e0-7f2c1d9a8e36"
    }
  ]
}
```

`GET /api/v1/engines/{engine_slug}/versions/{version}` returns one entry in the same shape. The route returns schema hashes, not schema bodies. For the generic fixture engine, the schema bodies are published in the [supported contract versions](/reference/versioning) registry; for your own engines, read the schema on the Contract page or in the downloaded OpenAPI file.

## Upgrade a pinned integration

<Steps>
  <Step title="Record the baseline">
    Store the exact version your live requests pin and its two schema hashes.
  </Step>

  <Step title="Review the target">
    Read the target version with `GET /api/v1/engines/{engine_slug}/versions/{version}`: its `changelog`, `is_breaking_from_previous_major` and schema hashes. Compare the hashes with your baseline. Equal hashes mean the contract did not change. The `bump` and `reasons` fields appear only in the publish response, not in later reads, so keep that response if you need them.
  </Step>

  <Step title="Test in a sandbox">
    Submit representative requests pinned to the target `N.M` from a sandbox key. Check parsing, polling and webhook handling against the results.
  </Step>

  <Step title="Change the pin">
    Deploy your integration with the new exact pin. On `exact_required` engines, never send `N.x`.
  </Step>

  <Step title="Keep the evidence">
    Store the resolved `engine_version` and `engine_config_version_hash` from each run with your records.
  </Step>
</Steps>

Rolling back means pinning the earlier version again. Behavior that a version does not freeze (model output, external data) is not restored by a rollback.

## Errors

Resolving a pin at run submission:

| Status | Code                                                                                                  | Cause                                                                                       |
| ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| 400    | `engine_version_required`                                                                             | `exact_required` engine and no pin.                                                         |
| 400    | `engine_version_exact_required`                                                                       | `exact_required` engine and an `N.x` pin.                                                   |
| 400    | `engine_version_not_found`                                                                            | No release matches the pin.                                                                 |
| 400    | `engine_version_invalid_format`                                                                       | Not `N.M`, `N.x` or `draft`.                                                                |
| 400    | `engine_version_draft_requires_sandbox_key`                                                           | `draft` from a live key.                                                                    |
| 500    | `engine_version_none_released`                                                                        | No pin, and the engine has no release.                                                      |
| 422    | `engine_release_unservable`                                                                           | Engines of the `matching` type only: the resolved version's configuration cannot be served. |
| 500    | `engine_version_resolve_failed`, `engine_config_version_load_failed`, `engine_config_version_invalid` | Nexio could not read the version. Retry.                                                    |
| 503    | `engine_config_version_unavailable`                                                                   | The archived configuration is temporarily unreadable. Retry.                                |

Publishing:

| Status | Code                                                                                            | Cause                                                                                                                                                                                       |
| ------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`                                                                               | The body is not valid JSON.                                                                                                                                                                 |
| 403    | `insufficient_capability`                                                                       | A scoped key. Publishing needs the organization key.                                                                                                                                        |
| 403    | `engine_archived`                                                                               | The engine is archived.                                                                                                                                                                     |
| 404    | `engine_not_found`                                                                              | No engine with that slug.                                                                                                                                                                   |
| 409    | `engine_config_changed`                                                                         | The draft was saved again while the gate ran. Publish again.                                                                                                                                |
| 409    | `engine_version_publish_mismatch`                                                               | `exact_required` engine: the `version` you sent is not the computed one. `details` has `requested_version` and `required_version`.                                                          |
| 409    | `engine_version_schema_change_requires_major`                                                   | `exact_required` engine: the schemas changed, so the release must be a major. `details` adds `reasons`.                                                                                     |
| 422    | `invalid_engine_config`                                                                         | The draft cannot be parsed to derive schemas.                                                                                                                                               |
| 422    | `provider_not_approved`, `egress_manifest_version_required`, `egress_manifest_version_mismatch` | The configured model's provider or the egress pin is not acceptable.                                                                                                                        |
| 422    | `cold_start_gate_not_met`, `cold_start_gate_failed`, `cold_start_gate_regressed`                | The evaluation gate blocked the release: no passing evaluation, an evaluation error, or a regression against labels. The `cold_start_` prefix applies to every release, not only the first. |

Reading versions: `400 engine_version_invalid_format` for a malformed `{version}`, `404 engine_version_not_found`, `404 engine_not_found`, `403 engine_archived` for an archived engine.

## Credentials

Listing and reading versions accept the organization key or a scoped key with `engines:read` bound to the engine. Publishing accepts only the organization key. See [Authentication and access](/authentication).

<CardGroup cols={2}>
  <Card title="Runs" href="/engines/runs">
    How a pinned run executes and reports.
  </Card>

  <Card title="Evaluation" href="/engines/evaluation">
    Evaluation sets and the release gate.
  </Card>

  <Card title="Publish engine version" href="/api-reference/engines/versions/publish-engine-version">
    Endpoint reference.
  </Card>

  <Card title="Supported contract versions" href="/reference/versioning">
    The fixture registry and API versioning.
  </Card>
</CardGroup>
