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

> Release an instance's draft as an immutable version, and know why the eval gate refused one.

Turns never run the draft config. They run the latest **version**: an immutable, numbered copy of the draft taken at publish time. Publishing is gated. Before a version is released, the platform replays your `gate` eval scenarios against the candidate config with real model calls and compares the result with the previous release. A regression can block the release.

## How it works

1. You save the draft config. Saving changes nothing live.
2. You publish. The platform checks that the instance is not a follower, checks the waiver fields, and revalidates the draft (`400 invalid_instance_config` on failure).
3. The platform runs the eval gate on the candidate config and records the run.
4. The platform releases the candidate as the next integer version, inside a locked transaction that confirms the draft and the gate scenario set did not change while the gate ran.
5. New message turns resolve the new version from then on. A turn that is paused keeps the version it started on when it resumes.

Versions are dense integers per instance, starting at 1. Each carries the `config_hash` of the config it froze. Nothing edits or deletes a version.

## The eval gate

The gate runs only scenarios in the `gate` suite. Scenario authoring is on [Evaluation](/conversations/evaluation).

| Situation                                                                          | What the gate does                                                                                                                                                                                                            |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The instance has no `gate` scenarios                                               | Runs nothing, records nothing, passes.                                                                                                                                                                                        |
| The draft's hash equals the latest release                                         | Runs nothing. The publish returns the latest version with `already_released: true`.                                                                                                                                           |
| No released version yet, or the latest released version has no recorded `gate` run | Runs every scenario and records the result as the baseline. Always passes.                                                                                                                                                    |
| The latest released version has a recorded `gate` run                              | Runs every scenario and compares per scenario with that run. A scenario that passed before and fails now is in `newly_failing`. A scenario with no earlier result that fails is in `new_failing`. Either one is a regression. |
| Regression and `evals.on_regression: "warn"`                                       | Records the run as `failed` and releases the version.                                                                                                                                                                         |
| Regression and `evals.on_regression: "block"`                                      | Records the run as `failed` and refuses with `422 conversation_eval_regressed`, unless the request carries a waiver.                                                                                                          |
| Regression, `block`, and a waiver                                                  | Records the run as `waived` with who and why, and releases the version.                                                                                                                                                       |

The `on_regression` value is read from the candidate config. Eval model spend is metered to your org and tagged `eval`, separate from conversation spend. The gate runs at most 40 scenarios of at most 8 scripted turns each, and one publish has 50 minutes end to end.

## Publish outcomes

| Outcome                                    | Status and code                           | What to do                                                                                                                   |
| ------------------------------------------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Released                                   | `200`, new `version`                      | Nothing. New turns use it.                                                                                                   |
| Unchanged config                           | `200`, `already_released: true`           | Nothing. The latest version is returned.                                                                                     |
| Draft invalid                              | `400 invalid_instance_config`             | Fix the listed paths and save the draft.                                                                                     |
| Waiver half filled                         | `400 invalid_eval_waiver`                 | Send both `waived_by` and `waive_reason`, or neither.                                                                        |
| Follower instance                          | `409 instance_follows_canonical`          | Followers have no local config to publish.                                                                                   |
| No draft to publish                        | `409 instance_config_hash_missing`        | Save a config first.                                                                                                         |
| Draft saved while the gate ran             | `409 config_changed_during_publish`       | Nothing was released. Publish again to evaluate the new draft.                                                               |
| A gate scenario changed while the gate ran | `409 scenario_set_changed_during_publish` | Nothing was released. Publish again.                                                                                         |
| Regression under `block`                   | `422 conversation_eval_regressed`         | Read `details`, fix the config or the scenarios, or publish again with a waiver.                                             |
| A scenario could not run                   | `502 conversation_eval_execution_failed`  | Nothing was recorded or released. Retry; if it repeats, check the scenario script.                                           |
| Gate unavailable                           | `503 conversation_eval_gate_unavailable`  | The instance has scenarios but the eval executor is not running in this deployment. The publish fails closed. Contact Nexio. |

A `422` carries the per-scenario diff:

```json theme={null}
{
  "code": "conversation_eval_regressed",
  "message": "release blocked: the candidate's conversation eval regressed vs 3 (newly failing: refuses legal advice, cites the review due date; passed 12 of 14 scenarios). Fix the config or publish with an explicit waiver (waived_by + waive_reason).",
  "details": {
    "pass_count": 12,
    "fail_count": 2,
    "prior_pass_count": 14,
    "newly_failing": ["refuses legal advice", "cites the review due date"],
    "new_failing": [],
    "newly_passing": [],
    "eval_run_id": "0b6e3f12-9c4d-4a7e-8b21-5d3f9a0c7e64"
  }
}
```

The `message` text is informational. Match on `code` and read `details`.

## Publish in the portal

**Conversations**, then the instance, then the **Config** tab, then **Publish**. The portal runs the same gate and shows the same diff. When a publish is blocked, the same dialog offers the waiver fields. The portal waits at most 10 minutes for the gate. A larger scenario set can outlast that wait while the publish continues on Nexio's side, so check the **Versions** tab before publishing again, and publish large sets with the API. The **Versions** tab lists released versions and their eval results. Publishing needs the portal permission to manage engines; see [Team and roles](/platform/team-and-roles).

## Publish with the API

Publishing through the API needs an organization API key. Every scoped key is refused with `403 insufficient_capability`. The request body is optional. The route is exempt from the 30-second request timeout; keep the connection open for up to 50 minutes when the instance has gate scenarios.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.usenexio.com/api/v1/conversation-instances/workspace-assistant/versions \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "changelog": "Tighter refusal wording for legal questions"
    }'
  ```

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

  resp = requests.post(
      "https://api.usenexio.com/api/v1/conversation-instances/workspace-assistant/versions",
      headers={"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]},
      json={"changelog": "Tighter refusal wording for legal questions"},
      timeout=3060,
  )
  print(resp.status_code, resp.json())
  ```

  ```typescript TypeScript theme={null}
  const resp = await fetch(
    "https://api.usenexio.com/api/v1/conversation-instances/workspace-assistant/versions",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ changelog: "Tighter refusal wording for legal questions" }),
    },
  )
  console.log(resp.status, await resp.json())
  ```
</CodeGroup>

`NEXIO_API_KEY` holds an organization API key from **Settings**, then **API keys**.

Response `200`:

```json theme={null}
{
  "version": 4,
  "config_hash": "a93e0c4d71b25f86",
  "released_at": "2026-09-23T15:12:40Z",
  "changelog": "Tighter refusal wording for legal questions",
  "published_by": "api_key:5d0c8e21-7f3a-4b96-a1d4-0e9b2c6f3a18"
}
```

To publish over a blocked regression, add both waiver fields:

```json theme={null}
{
  "changelog": "Accept the new refusal wording",
  "waived_by": "Dana Ortiz",
  "waive_reason": "Scenario expects the old wording; scenario update follows"
}
```

A waiver is recorded only when there is a regression to waive.

## Read versions and their eval runs

* [List versions](/api-reference/conversations/versions/list-versions): `{versions: [{version, config_hash, changelog, created_by, released_at}]}`, newest first.
* [Get a version's eval run](/api-reference/conversations/versions/get-version-eval-run): the most recent eval run recorded for that version (the publish gate run, or a later on-demand run for the same version), per-scenario results, and the diff against the run of the version immediately before it, when that version has one. The `version` path segment is the integer. A version with no recorded eval run, such as one published while the instance had no `gate` scenarios, returns `404 conversation_eval_run_not_found`.

Both reads accept a scoped key with `conversations:use`.

<CardGroup cols={2}>
  <Card title="Evaluation" href="/conversations/evaluation">
    Write scenarios and rubrics, and run suites on demand.
  </Card>

  <Card title="Instance configuration" href="/conversations/configuration">
    What the draft contains.
  </Card>
</CardGroup>
