# Control plane reference

Every route a simulator's control plane serves, with its methods, feature, parameters and responses, generated from the released CLI.

`rystic help control-plane` prints this reference for the CLI you have installed. [Control plane](https://www.rystic.ai/docs/control-plane.md) covers what the routes are for and the features that unlock them. The text below is the CLI's own, so a "world document" is a [scenario file](https://www.rystic.ai/docs/concepts/worlds.md#two-shapes).

<!-- BEGIN generated: control-plane-reference (python3 scripts/sync-cli-reference.py --binary <rystic>; do not edit) -->

Every simulator serves its service's API plus this control plane under
`/_rystic/`, on the same origin. `GET /_rystic/version` reports this
document's version as `contract`.

**Features.** Each route is listed with the feature of the simulator's
grant it needs, or as open on every plan when it needs none. A metered simulator
learns its grant from its lease (`GET /_rystic/version`,
`lease.entitlements`); a licensed one serves every route. A route the grant
lacks answers `403` with `{message, feature, upgrade_url}` before it reads
the method or the body. Two routes take no feature: `/_rystic/activity` is
same machine only, answering only a caller on the same machine, and
`/_rystic/files/{path}` takes a signed URL, its signature gating it in place
of a feature.

**Routes some simulators lack.** A few routes need a capability not every
simulator has, such as an evaluation engine, a console-set webhook or a
tape player; a simulator without it answers `404` there.

**Methods.** A route that changes state takes only the methods listed; any
other answers `405` with an `Allow` header, so a browser prefetch of
`GET /_rystic/reset` wipes nothing. Read routes take any method; `GET` is
documented. A path no route names answers `404`
`unknown control endpoint <path>`.

**Admin token.** A simulator started with `RYSTIC_ADMIN_TOKEN` requires it on
every control route except `/_rystic/version` and the signed file URLs, as the
`X-Rystic-Admin-Token` header or the `rystic_admin_token` cookie: `401`
without it, `403` with a wrong one (plain text). With `RYSTIC_EVAL_WORKER_TOKEN`
set, an evaluation worker's `X-Rystic-Worker-Token` reaches only
`/_rystic/eval/claim`, `/_rystic/eval/complete` and `/_rystic/eval/snapshot`.

**Browser pages and the simulator's own routes.** The browser pages, listed
under Browser pages, serve HTML. Routes a simulator adds under
`/_` beside this control plane are listed in its API explorer,
`/_rystic/swagger`.

Request bodies are JSON. Errors are `{"message": "..."}` unless noted.

This page describes control-plane contract `27`.

## Routes

| Route | Methods | Feature | Summary |
|---|---|---|---|
| [`/_feed`](#feed) | `GET` | every plan | Messages the inject schedule has fired |
| [`/_rystic/actions`](#rystic-actions) | `GET` | `state_view` | Page of the action log |
| [`/_rystic/activity`](#rystic-activity) | `GET` | same machine only | Activity counters |
| [`/_rystic/calls`](#rystic-calls) | `GET` | `state_view` | Every vendor API request since reset |
| [`/_rystic/clock`](#rystic-clock) | `POST` | `state_set` | Set or advance the simulator clock |
| [`/_rystic/config`](#rystic-config) | `GET` | `state_view` | The configuration a run used |
| [`/_rystic/credentials`](#rystic-credentials) | `POST` | `state_set` | Bind presented credentials to actors |
| [`/_rystic/docs`](#rystic-docs) | `GET` | every plan | This build's docs, by section |
| [`/_rystic/docs/changelog`](#rystic-docs-changelog) | `GET` | every plan | Release notes up to this build |
| [`/_rystic/docs/features/{feature}`](#rystic-docs-features-feature) | `GET` | every plan | One feature's page |
| [`/_rystic/docs/{section}`](#rystic-docs-section) | `GET` | every plan | One section of the docs |
| [`/_rystic/duplicate-delivery`](#rystic-duplicate-delivery) | `GET`, `POST` | `faults` | Duplicate delivery state |
| [`/_rystic/egress`](#rystic-egress) | `GET` | `state_view` | Webhooks and WebSocket frames the simulator produced |
| [`/_rystic/egress-retain`](#rystic-egress-retain) | `POST` | `state_set` | Bound the egress and call logs |
| [`/_rystic/eval`](#rystic-eval) | `GET` | `state_view` | Evaluation status |
| [`/_rystic/eval/advance`](#rystic-eval-advance) | `POST` | `state_set` | Publish ready evaluation results |
| [`/_rystic/eval/calibrations`](#rystic-eval-calibrations) | `GET`, `POST` | `state_set` | A calibration artifact |
| [`/_rystic/eval/claim`](#rystic-eval-claim) | `POST` | `state_set` | Claim dispatched evaluation work |
| [`/_rystic/eval/complete`](#rystic-eval-complete) | `POST` | `state_set` | Complete a dispatch |
| [`/_rystic/eval/episode`](#rystic-eval-episode) | `GET`, `POST` | `state_set` | The running episode's configuration |
| [`/_rystic/eval/evaluators`](#rystic-eval-evaluators) | `GET`, `POST` | `state_set` | Registered evaluator manifests |
| [`/_rystic/eval/evidence`](#rystic-eval-evidence) | `GET` | `state_view` | The task's evidence |
| [`/_rystic/eval/ledger`](#rystic-eval-ledger) | `GET` | `state_view` | Evaluation ledger |
| [`/_rystic/eval/policy`](#rystic-eval-policy) | `GET`, `POST` | `state_set` | The evaluation policy |
| [`/_rystic/eval/reference`](#rystic-eval-reference) | `GET`, `POST` | `state_set` | Harness-owned reference entries |
| [`/_rystic/eval/results`](#rystic-eval-results) | `GET` | `state_view` | Evaluation requests and their current results |
| [`/_rystic/eval/snapshot`](#rystic-eval-snapshot) | `GET` | `state_view` | A snapshot archive for an evaluation worker |
| [`/_rystic/events`](#rystic-events) | `GET` | `state_view` | Server-Sent Events stream of activity |
| [`/_rystic/files/{path}`](#rystic-files-path) | `GET`, `HEAD` | a signed URL | Download a stored file by signed URL |
| [`/_rystic/inject`](#rystic-inject) | `GET`, `POST` | `state_set` | The event schedule and what fired |
| [`/_rystic/inspect`](#rystic-inspect) | `GET` | `state_view` | The entire state |
| [`/_rystic/network`](#rystic-network) | `GET`, `POST` | `faults` | Network shaping state |
| [`/_rystic/page-cap`](#rystic-page-cap) | `GET`, `POST` | `faults` | Page caps |
| [`/_rystic/registers`](#rystic-registers) | `GET` | `state_view` | Every seedable register |
| [`/_rystic/replies`](#rystic-replies) | `GET`, `POST`, `DELETE` | `state_set` | The scripted replies left and the ones taken |
| [`/_rystic/reset`](#rystic-reset) | `POST` | every plan | Reset state |
| [`/_rystic/revoke`](#rystic-revoke) | `GET`, `POST` | `faults` | Revoked credentials |
| [`/_rystic/routes`](#rystic-routes) | `GET` | every plan | Vendor API routes this build serves |
| [`/_rystic/scenario`](#rystic-scenario) | `POST` | `scenarios` | Reset, then load a named scenario |
| [`/_rystic/scenarios`](#rystic-scenarios) | `GET` | `scenarios` | Named scenarios built into the simulator |
| [`/_rystic/seed`](#rystic-seed) | `POST` | `state_set` | Merge records into state, or land a world document |
| [`/_rystic/seed-rng`](#rystic-seed-rng) | `POST` | `state_set` | Pin the random stream |
| [`/_rystic/settle`](#rystic-settle) | `GET` | `state_view` | Wait until the app under test is done reacting |
| [`/_rystic/version`](#rystic-version) | `GET` | every plan | Identity and health |
| [`/_rystic/webhooks`](#rystic-webhooks) | `GET`, `POST`, `DELETE` | `state_set` | The operator-set webhook URL |
| [`/_rystic/ws`](#rystic-ws) | `GET` | `state_view` | Open WebSocket connections and their subscriptions |
| [`/_rystic/ws-drop`](#rystic-ws-drop) | `POST` | `faults` | Cut WebSocket connections |

<a id="feed"></a>

### `/_feed`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` Messages the inject schedule has fired

Served only while an inject schedule is loaded (`POST /_rystic/inject` or
boot `--inject`); otherwise the path belongs to the vendor API.

**Parameters**

- `since` (query, integer, at least 0): Return the messages after this sequence number. Default `0`.

**Responses**

- `200` The fired messages, oldest first. Fields:
  - `events` (array of object):
    - `body` (any): The message as scheduled.
    - `channel` (string)
    - `seq` (integer)
    - `time` (string, date-time)
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

<a id="rystic-actions"></a>

### `/_rystic/actions`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Page of the action log

This process's action-log lines with `seq` above `since`, as NDJSON. Lines
never carry a credential. Needs `RYSTIC_ACTION_LOG` set.

**Parameters**

- `since` (query, integer, at least 0, default `0`)
- `limit` (query, integer, at least 1, at most 10000, default `1000`)

**Responses**

- `200` NDJSON lines. (`application/x-ndjson`) String.
  - header `X-Rystic-More` (boolean): Whether another line follows.
  - header `X-Rystic-Next-Since` (integer): The last `seq` served; pass it as `since` for the next page.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` The action log is off. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-activity"></a>

### `/_rystic/activity`

**Methods:** `GET`. **Feature:** same machine only.

#### `GET` Activity counters

Counters for the CLI on the same machine. Never gated by plan; any non-loopback caller gets `403`.

**Responses**

- `200` Counters. [`ActivityCounters`](#schema-activitycounters): `boot` (string), `requests` (integer), `subscriptions_closed` (integer), `subscriptions_open` (integer), `subscriptions_opened` (integer), `tape_starts` (integer), `tape_stops` (integer), `tapes_playing` (integer), `webhooks` (integer).
- `403` Not a loopback caller. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-calls"></a>

### `/_rystic/calls`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Every vendor API request since reset

**Responses**

- `200` Requests, oldest first. `calls` (array of [`Call`](#schema-call), required), `total` (integer, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-clock"></a>

### `/_rystic/clock`

**Methods:** `POST`. **Feature:** `state_set`.

#### `POST` Set or advance the simulator clock

At least one field is required. `time` applies before `advance_seconds`.

**Request body** (`application/json`, required)

Object, at least 1 field:

- `advance_seconds` (integer): Seconds to move the clock.
- `time` (string, date-time): RFC 3339 time to set the clock to.
- `wall_offset_seconds` (integer): Offset applied to wall-clock reads; `0` removes it.

**Responses**

- `204` Clock moved.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-config"></a>

### `/_rystic/config`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` The configuration a run used

**Responses**

- `200` Configuration. [`Config`](#schema-config):
  - `action_log` (boolean, required)
  - `admin_token` (boolean, required): Whether an admin token is required.
  - `commit` (string, required)
  - `contract` (integer, required)
  - `edition` (string, required)
  - `initial_clock` (string, date-time, required)
  - `inject` (one of null, or an object, required):
    - null
    - an object:
      - `events` (integer)
      - `sha256` (string)
  - `model_hash` (string, required)
  - `product` (string, required)
  - `replay` (one of null, map of string to any, required)
  - `rng_seed` (integer, required)
  - `scenario` (string or null, required)
  - `seed` (object, required): Seeds applied since reset and their chained digest.
    - `applied` (integer)
    - `sha256` (string)
  - `version` (string, required)
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-credentials"></a>

### `/_rystic/credentials`

**Methods:** `POST`. **Feature:** `state_set`.

#### `POST` Bind presented credentials to actors

Replaces which actor each presented credential acts as, or with `keep` adds only the credentials no binding holds yet. Write-only. `reset` clears the bindings.

**Request body** (`application/json`, required)

- `bindings` (map of string to string, required): Presented credential to actor label.
- `keep` (boolean): Keep every current binding, its actor and grants, and add only the credentials not bound yet.

**Responses**

- `204` Bound.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-docs"></a>

### `/_rystic/docs`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` This build's docs, by section

The index of the docs built into this simulator, always JSON: each
section's `id`, `title` and `first_sentence`, and each feature's `id`,
`title` and `summary`. A build with no docs answers 404.

**Responses**

- `200` The docs index. [`DocsIndex`](#schema-docsindex):
  - `features` (array of object, required):
    - `id` (string, required)
    - `summary` (string, required)
    - `title` (string, required)
  - `product` (string, required)
  - `sections` (array of object, required):
    - `first_sentence` (string, required)
    - `id` (string, required)
    - `title` (string, required)
  - `version` (string, required)
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-docs-changelog"></a>

### `/_rystic/docs/changelog`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` Release notes up to this build

Every release up to this build, newest first, as Markdown; with
`Accept: application/json`, the list of entries.

**Responses**

- `200` The Markdown text, or under `Accept: application/json` the matching entry of the docs.
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-docs-features-feature"></a>

### `/_rystic/docs/features/{feature}`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` One feature's page

The feature's page as Markdown; with `Accept: application/json`, its
entry in the docs. `/_rystic/docs/features` alone is the Features
section.

**Parameters**

- `feature` (path, string, required)

**Responses**

- `200` The Markdown text, or under `Accept: application/json` the matching entry of the docs.
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `404` No such id; `ids` lists every id under /_rystic/docs/ that answers. `ids` (array of string, required), `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-docs-section"></a>

### `/_rystic/docs/{section}`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` One section of the docs

The section's text as Markdown; with `Accept: application/json`, the
section's entry in the docs. An unknown id answers 404 with `ids`,
every id that exists.

**Parameters**

- `section` (path, string, required)

**Responses**

- `200` The Markdown text, or under `Accept: application/json` the matching entry of the docs.
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `404` No such id; `ids` lists every id under /_rystic/docs/ that answers. `ids` (array of string, required), `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-duplicate-delivery"></a>

### `/_rystic/duplicate-delivery`

**Methods:** `GET`, `POST`. **Feature:** `faults`.

#### `GET` Duplicate delivery state

**Responses**

- `200` The copies armed and how many have been queued since the last reset. `copies` (integer, required), `duplicated` (integer, int64, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

#### `POST` Deliver each event more than once

Each event's first delivery attempt goes out `copies` more times, byte for byte with the same id and signature, each marked `duplicate` in `GET /_rystic/egress`'s delivery results and never retried. 0 disarms it, as a reset does.

**Request body** (`application/json`, required)

- `copies` (integer, at least 0, at most 10, required)

**Responses**

- `204` Armed.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-egress"></a>

### `/_rystic/egress`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Webhooks and WebSocket frames the simulator produced

**Parameters**

- `tail` (query, integer, at least 0): Return only the last N events.
- `raw` (query, string: `"1"`): `1` keeps the service's own hosts in URLs instead of rewriting them to the caller's origin.

**Responses**

- `200` The egress log, oldest first. Fields:
  - `dropped` (integer, required): Events dropped by the `egress-retain` bound.
  - `events` (array of [`EgressEvent`](#schema-egressevent), required)
  - `total` (integer, required)
  - `delivery` (map of string to any): Delivery state, present when webhook delivery is on.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-egress-retain"></a>

### `/_rystic/egress-retain`

**Methods:** `POST`. **Feature:** `state_set`.

#### `POST` Bound the egress and call logs

`0` is unbounded. Entries already dropped stay dropped.

**Request body** (`application/json`, required)

- `retain` (integer, at least 0, required)

**Responses**

- `204` Set.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval"></a>

### `/_rystic/eval`

**Methods:** `GET`. **Feature:** `state_view`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` Evaluation status

**Responses**

- `200` Episode status and counts. [`EvalStatus`](#schema-evalstatus): `dispatched` (integer), `dropped_late` (integer), `evaluators` (array of string), `generation` (integer), `policy_ref` (string), `published` (integer), `ready` (integer), `reference_digest` (string), `running` (integer), `spent_cost_units` (integer), `waiting` (integer).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-advance"></a>

### `/_rystic/eval/advance`

**Methods:** `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `POST` Publish ready evaluation results

`settled` (the default) waits for every dispatched request, lands answers in
request order and publishes; at the timeout it ends what is still out and
answers `expired: true`. `step` publishes what is ready now.

**Request body** (`application/json`, required)

- `timeout_ms` (integer): Wait bound; default 30000, capped at 600000.
- `until` (string: `"settled"`, `"step"`, default `"settled"`)

**Responses**

- `200` Advanced. `episode_generation` (integer, required), `expired` (boolean, required), `outstanding` (integer, required), `published` (integer, required), `settled` (boolean, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The episode changed (for example a reset) during the advance. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-calibrations"></a>

### `/_rystic/eval/calibrations`

**Methods:** `GET`, `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` A calibration artifact

**Parameters**

- `id` (query, string)

**Responses**

- `200` The calibration artifact. [`OpenObject`](#schema-openobject), map of string to any.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` No evaluation engine, or no such calibration. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

#### `POST` Calibrate an evaluator version on held-out reference examples

**Request body** (`application/json`, required)

- `evaluator_id` (string, required)
- `evaluator_version` (string, required)
- `holdout_fraction` (number)
- `max_examples` (integer)
- `seed` (string)

**Responses**

- `200` Started. `id` (string, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-claim"></a>

### `/_rystic/eval/claim`

**Methods:** `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `POST` Claim dispatched evaluation work

**Request body** (`application/json`, required)

- `max` (integer): Most dispatches to claim; `1` when absent or not positive.
- `worker` (string)

**Responses**

- `200` Claimed dispatches. Fields:
  - `dispatches` (array of object, required):
    - `dispatch` ([`OpenObject`](#schema-openobject), required)
    - `snapshot_url` (string, required): Relative `/_rystic/eval/snapshot?ref=...` URL.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-complete"></a>

### `/_rystic/eval/complete`

**Methods:** `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `POST` Complete a dispatch

An answer after its deadline is accepted and lands as a timeout.

**Request body** (`application/json`, required)

- `dispatch_id` (string, required)
- `episode_generation` (integer, required)
- `outcome` ([`EvalOutcome`](#schema-evaloutcome), required)
- `request_id` (string, required)

**Responses**

- `204` Accepted.
- `400` Malformed, for another dispatch, or the request already ended. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` An answer from an earlier episode, or the reference could not load. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-episode"></a>

### `/_rystic/eval/episode`

**Methods:** `GET`, `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` The running episode's configuration

**Responses**

- `200` Episode configuration. [`OpenObject`](#schema-openobject), map of string to any.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` No evaluation engine, or no episode running. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

#### `POST` Freeze an episode

Takes the policy, permitted evaluator versions, pinned calibrations, an
optional `reference_digest`, `task.scope` and the task's requirements.

**Request body** (`application/json`, required)

[`OpenObject`](#schema-openobject), map of string to any.

**Responses**

- `200` Started. `episode_generation` (integer, required), `policy_ref` (string, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-evaluators"></a>

### `/_rystic/eval/evaluators`

**Methods:** `GET`, `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` Registered evaluator manifests

**Responses**

- `200` Manifests. `evaluators` (array of [`OpenObject`](#schema-openobject), required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

#### `POST` Register an evaluator manifest

Versions are immutable.

**Request body** (`application/json`, required)

[`OpenObject`](#schema-openobject), map of string to any.

**Responses**

- `204` Registered.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-evidence"></a>

### `/_rystic/eval/evidence`

**Methods:** `GET`. **Feature:** `state_view`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` The task's evidence

One item per requirement, `satisfied`, `failed` or `unevaluated`, naming the current submission and snapshot.

**Responses**

- `200` Evidence. [`OpenObject`](#schema-openobject), map of string to any.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-ledger"></a>

### `/_rystic/eval/ledger`

**Methods:** `GET`. **Feature:** `state_view`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` Evaluation ledger

Requests, submissions (including empty ones) and published results, as NDJSON.

**Responses**

- `200` NDJSON lines. (`application/x-ndjson`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-policy"></a>

### `/_rystic/eval/policy`

**Methods:** `GET`, `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` The evaluation policy

**Responses**

- `200` Policy and its reference. `policy` ([`OpenObject`](#schema-openobject), required), `policy_ref` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

#### `POST` Set the evaluation policy

Setting a policy begins an episode.

**Request body** (`application/json`, required)

[`OpenObject`](#schema-openobject), map of string to any.

**Responses**

- `200` Set. `generation` (integer, required), `policy_ref` (string, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-reference"></a>

### `/_rystic/eval/reference`

**Methods:** `GET`, `POST`. **Feature:** `state_set`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` Harness-owned reference entries

**Responses**

- `200` Entries. `entries` (array of [`ReferenceEntry`](#schema-referenceentry), required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

#### `POST` Replace the reference entries

Load before starting an episode; the episode freezes this digest and refuses a different set.

**Request body** (`application/json`, required)

- `entries` (array of [`ReferenceEntry`](#schema-referenceentry), required)

**Responses**

- `200` Loaded. `reference_digest` (string, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` This simulator has no evaluation engine. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-results"></a>

### `/_rystic/eval/results`

**Methods:** `GET`. **Feature:** `state_view`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` Evaluation requests and their current results

**Parameters**

- `id` (query, string): One request's publication instead of the list.
- `set` (query, string): Filter the list to one set.

**Responses**

- `200` `{results: [...]}`, or one `{request, result}` with `id`. One of an object, or [`EvalPublication`](#schema-evalpublication):
  - an object:
    - `results` (array of [`EvalPublication`](#schema-evalpublication), required)
  - [`EvalPublication`](#schema-evalpublication)
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` No evaluation engine, or no request with that `id`. [`Error`](#schema-error): `message` (string, required).
- `409` The evaluation reference could not load. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-eval-snapshot"></a>

### `/_rystic/eval/snapshot`

**Methods:** `GET`. **Feature:** `state_view`.

Served only by a simulator with an evaluation engine; elsewhere `404`.

#### `GET` A snapshot archive for an evaluation worker

A tar archive of the snapshot a dispatch names. At most two are served at once.

**Parameters**

- `ref` (query, string, required)

**Responses**

- `200` The snapshot. (`application/x-tar`) String, `application/x-tar` content.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` No evaluation engine, or no such snapshot. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).
- `507` The archive is larger than the simulator serves (`RYSTIC_EVAL_SNAPSHOT_MAX`, default 256 MiB). [`Error`](#schema-error): `message` (string, required).

<a id="rystic-events"></a>

### `/_rystic/events`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Server-Sent Events stream of activity

A long-lived stream of requests, control calls and effects. Each event's
`id:` is its `seq`; resume with `Last-Event-ID` or `?cursor=`.

**Parameters**

- `cursor` (query, integer): Send only events with a greater `seq`. `Last-Event-ID` wins when both are set.
- `Last-Event-ID` (header, string)

**Responses**

- `200` An event stream; each `data:` line is an `ActivityEvent`. (`text/event-stream`) String, content [`ActivityEvent`](#schema-activityevent). `id: <seq>` and `data: <ActivityEvent JSON>` per event.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-files-path"></a>

### `/_rystic/files/{path}`

**Methods:** `GET`, `HEAD`. **Feature:** a signed URL.

#### `GET` Download a stored file by signed URL

Use the URL exactly as the simulator returned it. No admin token needed.

**Parameters**

- `path` (path, string, required): Any path; the query string identifies the file.
- `resource` (query, string, required)
- `key` (query, string, required)
- `owner` (query, string)
- `expires` (query, integer, required): Expiry in Unix milliseconds, on the simulator clock.
- `signature` (query, string, required)

**Responses**

- `200` The file, under its stored content type. (`*/*`) String.
- `403` Signature mismatch or expired URL. [`Error`](#schema-error): `message` (string, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

#### `HEAD` File headers by signed URL

**Parameters**

- `path` (path, string, required): Any path; the query string identifies the file.
- `resource` (query, string, required)
- `key` (query, string, required)
- `owner` (query, string)
- `expires` (query, integer, required): Expiry in Unix milliseconds, on the simulator clock.
- `signature` (query, string, required)

**Responses**

- `200` Headers only.
- `403` Signature mismatch or expired URL.
- `404` File not found.
- `405` Method not allowed.

<a id="rystic-inject"></a>

### `/_rystic/inject`

**Methods:** `GET`, `POST`. **Feature:** `state_set`.

#### `GET` The event schedule and what fired

**Responses**

- `200` The schedule. [`InjectView`](#schema-injectview):
  - `events` (array of object, required):
    - `at` (string, required)
    - `due` (string, date-time, required)
    - `id` (string, required)
    - `as` (string)
    - `bind` (map of string to string)
    - `bound` (map of string to any)
    - `call` (map of string to any)
    - `error` (string)
    - `fired_at` (string)
    - `message` (map of string to any)
    - `seed` ([`RegisterMap`](#schema-registermap))
    - `status` (integer)
  - `fired` (integer, required)
  - `pending` (integer, required)
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

#### `POST` Replace the event schedule

Replaces the schedule. `at` resolves once against the clock when the
schedule loads, so a `reset` replays the same instants; an event already due
fires at once. The whole schedule is refused for an unknown route or an `as`
the scenario cannot act as. Unknown keys are refused.

**Request body** (`application/json`, required)

- `events` (array of [`InjectEvent`](#schema-injectevent), required)

**Responses**

- `204` Loaded.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-inspect"></a>

### `/_rystic/inspect`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` The entire state

Every register as `{"<register>": {"<key>": {record}}}`. The registers and
record fields differ per simulator (see `GET /_rystic/registers`).
`X-Rystic-Activity-Seq` carries the `/_rystic/events` cursor at the read.

**Parameters**

- `view` (query, string): A named view instead of the dump: `identities`; `sequences`, every id counter's last value as `{"sequences": {"<scope>": N}}`, which a world document's `sequences` takes back; or a view the simulator declares. An unknown name answers `404` listing the ones it serves.
- `etag` (query, string: `"1"`): `1` adds an `ETag` header to pass as `If-Match` on `POST /_rystic/seed`.

**Responses**

- `200` The state dump, or the named view. Its shape is per simulator. Map of string to any.
  - header `ETag` (string): Present with `?etag=1`.
  - header `X-Rystic-Activity-Seq` (integer)
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-network"></a>

### `/_rystic/network`

**Methods:** `GET`, `POST`. **Feature:** `faults`.

#### `GET` Network shaping state

**Responses**

- `200` Shaping state. Fields:
  - `enabled` (boolean, required)
  - `profile` (one of [`NetworkProfile`](#schema-networkprofile), null, required)
  - `source` (string, required): Where the profile came from (`none` when none is installed).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

#### `POST` Install a network profile and/or switch shaping

Shapes vendor API requests only, never the control plane. `{"enabled": true}`
alone needs a profile already installed. A profile set here outlasts `reset`.

**Request body** (`application/json`, required)

[`NetworkInput`](#schema-networkinput), object, at least 1 field:

- `enabled` (boolean)
- `profile` ([`NetworkProfile`](#schema-networkprofile))

**Responses**

- `204` Applied.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-page-cap"></a>

### `/_rystic/page-cap`

**Methods:** `GET`, `POST`. **Feature:** `faults`.

#### `GET` Page caps

**Responses**

- `200` The capped routes and how many requests a cap has changed since the last reset. Fields:
  - `clamped` (integer, int64, required)
  - `routes` (map of string to object, required):
    - `max` (integer)
    - `param` (string)
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

#### `POST` Serve smaller pages than asked

On each `"METHOD /template"` route, the query param `param` is lowered to `max` when the caller asks more, and set to `max` when it asks nothing, so the caller must follow the route's own continuation. `{"routes": {}}` clears every cap, as a reset does.

**Request body** (`application/json`, required)

- `routes` (map of string to object, required):
  - `max` (integer, at least 1, required)
  - `param` (string, required)

**Responses**

- `204` Armed.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-registers"></a>

### `/_rystic/registers`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Every seedable register

**Responses**

- `200` The registers. `contract` (integer, required), `model_hash` (string, required), `registers` (array of [`RegisterInfo`](#schema-registerinfo), required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` The build carries no seed manifest. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-replies"></a>

### `/_rystic/replies`

**Methods:** `GET`, `POST`, `DELETE`. **Feature:** `state_set`.

Served only by a simulator with an operation that answers a scripted reply, such as a model's messages; elsewhere `404`.

#### `GET` The scripted replies left and the ones taken

**Responses**

- `200` The script. [`ScriptedReplies`](#schema-scriptedreplies):
  - `left` (array of [`ScriptedReply`](#schema-scriptedreply), required)
  - `operations` (array of string, required): The routes that take replies.
  - `taken` (array of object, required):
    - `operation` (string, required)
    - `reply` (integer, required): The reply's index in the script.
    - `refused` (string): Why the call was refused, when the reply called a tool the request did not declare.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

#### `POST` Script what the model answers, turn by turn

Replaces the script. Each call to an operation that takes replies answers the
next one, in the service's own shape, and the operation's default answer once
none is left. A reply calling a tool the request does not declare is refused
and reported under `taken`. A `reset` starts the script over; a world document
or scenario can carry `replies` too.

**Request body** (`application/json`, required)

- `replies` (array of [`ScriptedReply`](#schema-scriptedreply), required)

**Responses**

- `204` Set.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

#### `DELETE` Empty the script

**Responses**

- `204` Emptied.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

<a id="rystic-reset"></a>

### `/_rystic/reset`

**Methods:** `POST`. **Feature:** every plan.

#### `POST` Reset state

Clears state and logs, restarts id sequences, returns the clock to
`initial_clock`, reseeds the RNG and reseeds the simulator's built-in records.
It does not re-apply a boot scenario. A scenario's credential bindings, events and
network profile leave with it; a schedule loaded through `/_rystic/inject`
stays and replays. Ends any evaluation episode.

**Responses**

- `204` Reset.
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

<a id="rystic-revoke"></a>

### `/_rystic/revoke`

**Methods:** `GET`, `POST`. **Feature:** `faults`.

#### `GET` Revoked credentials

Each revoked credential by its SHA-256, never its value, with how many requests it has been refused and the call seq and simulator time of the first.

**Responses**

- `200` The revocations armed since the last reset. Fields:
  - `revoked` (array of object, required):
    - `after_calls` (integer)
    - `credential_sha256` (string)
    - `from_seq` (integer, int64)
    - `refused` (integer)
    - `revoked` (boolean)
    - `revoked_at` (string)
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

#### `POST` Revoke credentials mid-run

Each listed credential is refused before any handler once `after_calls` more requests have presented it, with `refusal` when given, else the simulator's own refusal of an unrecognized or dead credential; a simulator declaring none needs `refusal`. An empty list clears every revocation, as a reset does.

**Request body** (`application/json`, required)

- `credentials` (array of string, required)
- `after_calls` (integer, at least 0)
- `refusal` (object):
  - `status` (integer, required)
  - `body` (any)
  - `headers` (map of string to string)

**Responses**

- `204` Armed.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-routes"></a>

### `/_rystic/routes`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` Vendor API routes this build serves

Every vendor API route, sorted by template then method. The control plane is not in it.

**Responses**

- `200` Route list. `routes` (array of [`RouteInfo`](#schema-routeinfo), required).
- `401` An admin token is required and was not presented. (`text/plain`) String.

<a id="rystic-scenario"></a>

### `/_rystic/scenario`

**Methods:** `POST`. **Feature:** `scenarios`.

#### `POST` Reset, then load a named scenario

**Request body** (`application/json`, required)

- `name` (string, required)
- `params` (map of string to one of string, number): A generated scenario's parameters, each a string or a number. The name may carry them too, as `name?size=large`.

**Responses**

- `204` Loaded.
- `400` A malformed body, or parameters the scenario does not take. [`Error`](#schema-error): `message` (string, required).
- `403` The plan lacks the `scenarios` feature, or one the scenario needs (such as `faults` for its network profile). [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Unknown scenario; the message lists the ones that exist. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-scenarios"></a>

### `/_rystic/scenarios`

**Methods:** `GET`. **Feature:** `scenarios`.

#### `GET` Named scenarios built into the simulator

**Responses**

- `200` Scenarios. `scenarios` (array of [`ScenarioInfo`](#schema-scenarioinfo), required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-seed"></a>

### `/_rystic/seed`

**Methods:** `POST`. **Feature:** `state_set`.

#### `POST` Merge records into state, or land a world document

Takes either a register map, merged into state, or a world document marked
by its top-level `seed` object, which lands whole or not at all: the clock
and RNG, then the seed and its id counters, the network profile, the
credential bindings and the events. Either way, a uuid or event id the
simulator minted, or an integer record id, that the seed holds is not minted
again; a world document's
`sequences` carries the counters behind every other id. A world document's other keys are ignored, except `tape` and `loop`,
which answer `400`. Its `network` needs the `faults` feature.

**Parameters**

- `If-Match` (header, string): An `ETag` from `GET /_rystic/inspect?etag=1`; `412` if state changed since.

**Request body** (`application/json`, required)

One of [`RegisterMap`](#schema-registermap), [`WorldDocument`](#schema-worlddocument).

**Responses**

- `204` Landed.
- `400` Invalid, incomplete or misplaced records, or a malformed body. [`SeedRejection`](#schema-seedrejection): `message` (string, required), `errors` (array of [`SeedError`](#schema-seederror)).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `412` State changed since the `ETag` was read. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-seed-rng"></a>

### `/_rystic/seed-rng`

**Methods:** `POST`. **Feature:** `state_set`.

#### `POST` Pin the random stream

Ids and draws repeat run to run under the same seed.

**Request body** (`application/json`, required)

- `seed` (integer, int64, required)

**Responses**

- `204` Seeded.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="rystic-settle"></a>

### `/_rystic/settle`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Wait until the app under test is done reacting

A long-poll a driver calls after acting. It answers `settled: true` once
no event or interaction delivery is open (a Socket Mode envelope awaiting
its ack, a request-URL delivery queued or in flight, a retry due by now)
and no watched call has landed for `quiet_ms` of wall time, counted from
the later of the last watched call and the last ack or reset. A watched
call is any vendor API call except one carrying `X-Rystic-Surface: client`
or `X-Rystic-Source: ui`, or a timeline event's; `principal` narrows them.
Quiet is wall time because the app runs in real time while the
simulator's clock may be pinned or jumped. At `timeout_ms` it answers
`settled: false` with `pending`, what is still open. A retry due later
never holds it and is listed in `scheduled`. `last_call_seq` is the last
`seq` in `GET /_rystic/calls`: settle before acting and after, and the
calls between the two seqs are the app's turn.

**Parameters**

- `quiet_ms` (query, integer, at least 0, at most 60000, default `500`): Wall milliseconds with no watched call, 0 to 60000.
- `timeout_ms` (query, integer, at least 0, at most 60000, default `30000`): Wall milliseconds to wait before answering unsettled, 0 to 60000.
- `principal` (query, string): Watch only calls whose `actor` or `principal` this names; repeat it or separate names with commas.

**Responses**

- `200` Settled, or the timeout with what is still open. [`Settle`](#schema-settle):
  - `idle_ms` (integer, required): Wall milliseconds since the quiet window opened.
  - `last_call_seq` (integer, required): The last `seq` in `GET /_rystic/calls`, 0 since a reset with no call.
  - `settled` (boolean, required)
  - `pending` (array of [`SettleEntry`](#schema-settleentry)): Only when unsettled. What is still open, and the call, ack or reset the quiet window counts from while it is shorter than `quiet_ms`.
  - `scheduled` (array of [`SettleEntry`](#schema-settleentry)): Retries due later, which settle never waits on.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-version"></a>

### `/_rystic/version`

**Methods:** `GET`. **Feature:** every plan.

#### `GET` Identity and health

Identity and health. Open even when an admin token is set, so it serves as a
readiness check. A stopping simulator keeps answering here with `stopping`.

**Responses**

- `200` Simulator identity. [`Version`](#schema-version):
  - `clock` (string, date-time, required)
  - `commit` (string, required)
  - `contract` (integer, required)
  - `edition` (string, required): `metered` on a simulator that runs on a lease, empty on one that runs on a license file.
  - `initial_clock` (string, date-time, required)
  - `model_hash` (string, required)
  - `network` (object, required):
    - `enabled` (boolean)
    - `endpoints` (integer)
    - `profile_digest` (string)
  - `product` (string, required)
  - `version` (string, required)
  - `api_base_paths` (array of string)
  - `auth_scheme` (string)
  - `blob_cap_bytes` (integer)
  - `body_accepted` (array of string)
  - `body_ct_selects` (boolean)
  - `compat_auth` (boolean)
  - `compat_body_encoding` (boolean)
  - `credential_required` (integer)
  - `egress_retain` (integer)
  - `env` (map of string to any)
  - `explorer` (array of string): Explorer tools served, each at `/_rystic/<tool>`.
  - `lease` (object): Set only on a simulator that runs on a lease.
    - `entitlements` (map of string to any)
    - `expires_at` (any)
    - `notice` (any)
    - `offline` (boolean)
    - `offline_until` (integer)
    - `session_id` (string)
  - `license_expires` (any): Set only on a simulator that runs on a license file.
  - `license_features` (array of string)
  - `licensee` (string): Set only on a simulator that runs on a license file.
  - `preserve_service_urls` (boolean)
  - `service_hosts` (array of string)
  - `stopping` (any): Present while the simulator is stopping, with the reason.
  - `watermark` (string)

<a id="rystic-webhooks"></a>

### `/_rystic/webhooks`

**Methods:** `GET`, `POST`, `DELETE`. **Feature:** `state_set`.

Served only by a simulator whose service has a webhook set in its console; elsewhere `404`.

#### `GET` The operator-set webhook URL

Never returns the secret.

**Responses**

- `200` Webhook state. [`OperatorWebhook`](#schema-operatorwebhook): `active` (boolean, required), `delivery` (string: `"on"`, `"off"`, required), `notifications` (integer, required), `pending` (integer, required), `url` (string or null, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

#### `POST` Set the operator webhook URL

Makes the vendor's challenge handshake with the URL first, where the vendor
has one, and sets it only when it passes. A `reset` keeps the URL.

**Request body** (`application/json`, required)

- `url` (string, uri, required): Absolute http(s) URL without userinfo or fragment.
- `secret` (string): Signing secret; required unless the vendor's signature takes none.
- `settings` (map of string to string): Every setting the simulator declares, and no other.

**Responses**

- `200` Set. [`OperatorWebhook`](#schema-operatorwebhook): `active` (boolean, required), `delivery` (string: `"on"`, `"off"`, required), `notifications` (integer, required), `pending` (integer, required), `url` (string or null, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` Webhook delivery is off; boot with `RYSTIC_WEBHOOK_DELIVERY=on`. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).
- `422` The URL failed the handshake; the previous URL stays. [`Error`](#schema-error): `message` (string, required).

#### `DELETE` Clear the operator webhook URL

**Responses**

- `204` Cleared.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

<a id="rystic-ws"></a>

### `/_rystic/ws`

**Methods:** `GET`. **Feature:** `state_view`.

#### `GET` Open WebSocket connections and their subscriptions

**Responses**

- `200` Connections. `connections` (array of map of string to any, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="rystic-ws-drop"></a>

### `/_rystic/ws-drop`

**Methods:** `POST`. **Feature:** `faults`.

#### `POST` Cut WebSocket connections

Set `all`, or name `connections` or `tickers`. `graceful` sends a 1001 close frame first.

**Request body** (`application/json`, required)

- `all` (boolean)
- `connections` (array of integer, int64)
- `graceful` (boolean)
- `tickers` (array of string)

**Responses**

- `200` The connection ids cut (`[]` when none matched). `dropped` (array of integer, int64, required).
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

## Tape player routes

Served only by a simulator with a tape player; elsewhere `404`.

| Route | Methods | Feature | Summary |
|---|---|---|---|
| [`/_replay/feed`](#replay-feed) | `POST` | `replay` | Push live frames |
| [`/_replay/markets`](#replay-markets) | `GET` | `replay` | Recorded markets this simulator can replay |
| [`/_replay/pause`](#replay-pause) | `POST` | `replay` | Pause playback |
| [`/_replay/play`](#replay-play) | `POST` | `replay` | Resume, change speed or play to a stop |
| [`/_replay/prepare`](#replay-prepare) | `POST` | `replay` | Load a recording ahead of start |
| [`/_replay/seek`](#replay-seek) | `POST` | `replay` | Jump to a recorded position |
| [`/_replay/start`](#replay-start) | `POST` | `replay` | Start a replay or a live session |
| [`/_replay/status`](#replay-status) | `GET` | `replay` | The session's play head |
| [`/_replay/stream`](#replay-stream) | `GET` | `replay` | A recording as NDJSON |
| [`/_replay/watch`](#replay-watch) | `GET`, `POST` | `replay` | The live session's market lists |

<a id="replay-feed"></a>

### `/_replay/feed`

**Methods:** `POST`. **Feature:** `replay`.

#### `POST` Push live frames

Applies one batch of live frames, in order, each at its own exchange
timestamp. The body is NDJSON in the recording's own line format;
blank lines are skipped. Frames for markets outside the session's
list are dropped as `unknown_market`; a paused session drops the
whole batch as `paused`. A failed frame pauses the session with its
error.

**Request body** (`application/x-ndjson`, required)

String.

**Responses**

- `200` Batch applied. Fields:
  - `accepted` (integer, required): Frames applied.
  - `dropped` (integer, required): Lines dropped, the sum of `drops`.
  - `drops` (map of string to integer, required): Lines dropped, by reason.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` No live session. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="replay-markets"></a>

### `/_replay/markets`

**Methods:** `GET`. **Feature:** `replay`.

#### `GET` Recorded markets this simulator can replay

The recorded markets the simulator holds. `spans` covers only the
recordings this process has already loaded; it is absent when none
has. During a live session, `live` lists the markets the feed has
carried, and the listing answers even with no recordings at all.
Any method is accepted; `GET` is documented.

**Responses**

- `200` Recorded markets. Fields:
  - `markets` (array of string, required)
  - `live` (array of string): Present only during a live session.
  - `spans` (array of [`ReplayGroupMember`](#schema-replaygroupmember))
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="replay-pause"></a>

### `/_replay/pause`

**Methods:** `POST`. **Feature:** `replay`.

#### `POST` Pause playback

Stops the play head where it stands. A live session drops the frames fed while paused.

**Responses**

- `204` Paused.
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="replay-play"></a>

### `/_replay/play`

**Methods:** `POST`. **Feature:** `replay`.

#### `POST` Resume, change speed or play to a stop

Resumes a paused session. A finished recording plays again from the
top. An empty body is a plain resume. Give at most one stop:
`until_ms`, `advance_ms` or `until`. A live session takes no stop and
speed `1` only.

**Request body** (`application/json`)

- `advance_ms` (integer, int64, above 0): Pause this many recorded milliseconds past the play head.
- `speed` (number, above 0): New playback speed. Absent keeps the current one.
- `until` (string: `"next_event"`): Pause at the next pending scheduled event (`status` `next_event_ms`).
- `until_ms` (integer, int64): Pause after every event through this absolute recorded time (`status` `start_ms` + `position_ms`). Not behind the play head.

**Responses**

- `204` Playing.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` Nothing is loaded; a stop was asked of a live session; `until_ms` is behind the play head; a step was asked of a finished recording; or `until: next_event` has no pending event. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="replay-prepare"></a>

### `/_replay/prepare`

**Methods:** `POST`. **Feature:** `replay`.

#### `POST` Load a recording ahead of start

Loads the named market, or a synchronized group, and holds it for the
next `start` with the same selector, so that start does not wait for
the load. A later prepare replaces the held one.

**Request body** (`application/json`, required)

[`ReplaySelector`](#schema-replayselector):

- `copies` (integer, at least 0)
- `market` (string)
- `markets` (array of [`ReplayMember`](#schema-replaymember))

**Responses**

- `204` Loaded.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The group's recordings do not overlap in time. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).
- `501` An `as` rename was asked of a simulator that cannot play a recording under another ticker. [`Error`](#schema-error): `message` (string, required).

<a id="replay-seek"></a>

### `/_replay/seek`

**Methods:** `POST`. **Feature:** `replay`.

#### `POST` Jump to a recorded position

Sets the simulator's state to what it was after the event at
`position` and moves the play head there, playing or paused. A
position past either end is clamped to it. Checkpoints before the new
head are dropped. A sequence or a live session cannot seek.

**Request body** (`application/json`, required)

- `position` (integer): Event index, 0-based (`status` `position`, below `total`).

**Responses**

- `204` Moved.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` Nothing is loaded, the session is live or a sequence, or the session changed during the seek. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).
- `500` Error. [`Error`](#schema-error): `message` (string, required).

<a id="replay-start"></a>

### `/_replay/start`

**Methods:** `POST`. **Feature:** `replay`.

#### `POST` Start a replay or a live session

Ends any running session and plays from the top. Name one market, a
`markets` group (merged and trimmed to the window all of them cover),
or a `sequence` of games played back to back on one session clock.
`{"mode": "live"}` instead opens a live session fed through
`/_replay/feed`; `["*"]` as its markets admits any market the feed
carries.

**Request body** (`application/json`, required)

All of [`ReplaySelector`](#schema-replayselector), plus:

- `checkpoints` (array of integer, int64): Pause after every event through each of these times, in absolute recorded epoch milliseconds (the axis of `status` `start_ms`). Not with `live`.
- `gap_ms` (integer, int64): Session milliseconds between one game's end and the next game's start. Sequence only.
- `mode` (string): `live` opens a live session; any other value plays a recording.
- `rebase` (boolean): Shift the simulator clock so the first recorded event lands at the current wall time.
- `sequence` (array of one of [`ReplayMember`](#schema-replaymember), array of [`ReplayMember`](#schema-replaymember)): Games in play order, each a market, a `{tape, as}` entry or a list of them played as one group. Names no `market`, `markets` or `copies` beside it, and no `live` mode.
- `session_start_ms` (integer, int64): Epoch milliseconds the first game starts at on the session clock. Sequence only.
- `speed` (number): Playback speed; `1` is recorded time. Absent or not above 0 plays at `1`.

**Responses**

- `204` Started.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` The group's recordings do not overlap in time. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).
- `501` An `as` rename was asked of a simulator that cannot play a recording under another ticker, or `live` was asked of one that cannot be fed. [`Error`](#schema-error): `message` (string, required).

<a id="replay-status"></a>

### `/_replay/status`

**Methods:** `GET`. **Feature:** `replay`.

#### `GET` The session's play head

Where the session stands. `position_ms` and `duration_ms` are relative
to `start_ms`; `start_ms` and `end_ms` are absolute recorded time,
before any rebase. Any method is accepted; `GET` is documented.

**Responses**

- `200` Session status. [`ReplayStatus`](#schema-replaystatus):
  - `done` (boolean, required)
  - `duration_ms` (integer, int64, required)
  - `playing` (boolean, required)
  - `position` (integer, required)
  - `position_ms` (integer, int64, required)
  - `rebase_offset_ms` (integer, int64, required): The clock shift `rebase` chose at start; `0` without it.
  - `total` (integer, required)
  - `at_checkpoint_ms` (integer, int64): The stop the session is paused at.
  - `checkpoints` (array of integer, int64): Pending stops, absolute recorded milliseconds.
  - `current_game` (integer): The 1-based game the play head last entered.
  - `dropped` (integer, int64): Live frames dropped.
  - `end_ms` (integer, int64)
  - `error` (string)
  - `frames` (integer, int64): Live frames applied.
  - `lag_ms` (integer, int64): Wall time now minus `last_ms`.
  - `last_ms` (integer, int64): Exchange timestamp of the last live frame applied.
  - `market` (string): The market, a comma-joined group, or `*` for a live session admitting any market.
  - `markets` (array of [`ReplayGroupMember`](#schema-replaygroupmember))
  - `mode` (string: `"apply"`, `"seed"`, `"live"`): `apply` drives each event through the simulator's own state and messages; `seed` replaces state with each event's snapshot; `live` is fed through `/_replay/feed`.
  - `next_event_ms` (integer, int64): Recorded time of the next pending scheduled event.
  - `sequence` (array of [`ReplaySequenceGame`](#schema-replaysequencegame))
  - `speed` (number)
  - `start_ms` (integer, int64)
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).

<a id="replay-stream"></a>

### `/_replay/stream`

**Methods:** `GET`. **Feature:** `replay`.

#### `GET` A recording as NDJSON

Every recorded event of `market`, one JSON object per line, from a
fresh load: the session and its play head are untouched. The fields
of each line are the simulator's own. A body that ends early means the
recording failed partway. Any method is accepted; `GET` is documented.

**Parameters**

- `market` (query, string, required)

**Responses**

- `200` One event per line. (`application/x-ndjson`) Map of string to any.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `404` Not found, or not served by this simulator. [`Error`](#schema-error): `message` (string, required).
- `501` This simulator's recordings do not stream. [`Error`](#schema-error): `message` (string, required).

<a id="replay-watch"></a>

### `/_replay/watch`

**Methods:** `GET`, `POST`. **Feature:** `replay`.

#### `GET` The live session's market lists

Outside a live session every list is empty.

**Responses**

- `200` Market lists. Fields:
  - `available` (array of string, required): Every market the feeder can subscribe to.
  - `seen` (array of string, required): The markets frames have arrived for.
  - `watch` (array of string, required): The markets chosen to carry. Empty means all of `available`.
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)

#### `POST` Set the live session's market lists

Sets each list the body names and leaves the other as it is, so the
feeder (`available`) and a viewer (`markets`) never overwrite each
other. Lists are stored sorted.

**Request body** (`application/json`, required)

Object, at least 1 field:

- `available` (array of string): Every market the feeder can subscribe to.
- `markets` (array of string): The markets to carry. Empty carries all of `available`.

**Responses**

- `204` Set.
- `400` The body or query is malformed; the message names the expected shape. [`Error`](#schema-error): `message` (string, required).
- `401` An admin token is required and was not presented. (`text/plain`) String.
- `403` The plan does not include this route's feature. [`Unentitled`](#schema-unentitled): `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required), `message` (string, required), `upgrade_url` (string, uri, required).
- `405` Method not allowed; `Allow` lists the methods the route takes. [`Error`](#schema-error): `message` (string, required).
  - header `Allow` (string)
- `409` No live session. [`Error`](#schema-error): `message` (string, required).
- `413` Request body too large. [`Error`](#schema-error): `message` (string, required).

<a id="browser-pages"></a>

## Browser pages

<a id="rystic-ui"></a>

### `/_rystic/ui`

**Feature:** `ui`. Served as HTML.

The state UI, the simulator's state in the browser.

<a id="rystic-swagger"></a>

### `/_rystic/swagger`

**Feature:** `ui`. Served as HTML.

The API explorer, Swagger UI, for this control plane and, where the simulator has one, its own API's OpenAPI document.

<a id="rystic-graphiql"></a>

### `/_rystic/graphiql`

**Feature:** `ui`. Served as HTML.

The GraphQL explorer, GraphiQL, for the simulator's GraphQL API.

Served only by a simulator whose API has a GraphQL schema; elsewhere `404`.

## Schemas

The types the routes above name, by name.

<a id="schema-activitycounters"></a>

### `ActivityCounters`

- `boot` (string)
- `requests` (integer)
- `subscriptions_closed` (integer)
- `subscriptions_open` (integer)
- `subscriptions_opened` (integer)
- `tape_starts` (integer)
- `tape_stops` (integer)
- `tapes_playing` (integer)
- `webhooks` (integer)

<a id="schema-activityevent"></a>

### `ActivityEvent`

- `at` (string, required)
- `kind` (string: `"request"`, `"effect"`, `"control"`, required)
- `seq` (integer, required)
- `data` (map of string to any)

<a id="schema-call"></a>

### `Call`

One vendor API request. Further fields vary.

- `actor` (string)
- `at` (string)
- `duration_ms` (number)
- `method` (string)
- `path` (string)
- `principal` (string)
- `seq` (integer)
- `status` (integer)
- `surface` (string: `"client"`): Present on a call that carried `X-Rystic-Surface: client`, standing in for the vendor's own client.
- `template` (string)
- any other key (any)

<a id="schema-config"></a>

### `Config`

- `action_log` (boolean, required)
- `admin_token` (boolean, required): Whether an admin token is required.
- `commit` (string, required)
- `contract` (integer, required)
- `edition` (string, required)
- `initial_clock` (string, date-time, required)
- `inject` (one of null, or an object, required):
  - null
  - an object:
    - `events` (integer)
    - `sha256` (string)
- `model_hash` (string, required)
- `product` (string, required)
- `replay` (one of null, map of string to any, required)
- `rng_seed` (integer, required)
- `scenario` (string or null, required)
- `seed` (object, required): Seeds applied since reset and their chained digest.
  - `applied` (integer)
  - `sha256` (string)
- `version` (string, required)

<a id="schema-delayspec"></a>

### `DelaySpec`

Base, jitter and correlation, or a quantile table, which wins when present.

- `base_ms` (number, at least 0)
- `correlation` (number, at least 0, below 1)
- `jitter_ms` (number, at least 0)
- `quantiles_ms` (map of string to number): `p50` (required), `p90`, `p99`.

<a id="schema-docsindex"></a>

### `DocsIndex`

- `features` (array of object, required):
  - `id` (string, required)
  - `summary` (string, required)
  - `title` (string, required)
- `product` (string, required)
- `sections` (array of object, required):
  - `first_sentence` (string, required)
  - `id` (string, required)
  - `title` (string, required)
- `version` (string, required)

<a id="schema-egressevent"></a>

### `EgressEvent`

- `effect` (string, required)
- `seq` (integer, required)
- `type` (string, required)
- `owner` (string)
- `payload` (map of string to any): Per simulator.

<a id="schema-endpointshape"></a>

### `EndpointShape`

- `in` ([`DelaySpec`](#schema-delayspec))
- `out` ([`DelaySpec`](#schema-delayspec))
- `reject` ([`RejectSpec`](#schema-rejectspec))

<a id="schema-error"></a>

### `Error`

- `message` (string, required)

<a id="schema-evaloutcome"></a>

### `EvalOutcome`

An evaluated outcome has a verdict; a skip or error has a reason.

- `evaluation` (string, required)
- `evidence` ([`OpenObject`](#schema-openobject))
- `outputs` (map of string to string)
- `reason` (string)
- `verdict` (string)

<a id="schema-evalpublication"></a>

### `EvalPublication`

- `request` ([`OpenObject`](#schema-openobject), required)
- `result` ([`OpenObject`](#schema-openobject), required)

<a id="schema-evalstatus"></a>

### `EvalStatus`

- `dispatched` (integer)
- `dropped_late` (integer)
- `evaluators` (array of string)
- `generation` (integer)
- `policy_ref` (string)
- `published` (integer)
- `ready` (integer)
- `reference_digest` (string)
- `running` (integer)
- `spent_cost_units` (integer)
- `waiting` (integer)

<a id="schema-injectevent"></a>

### `InjectEvent`

Exactly one of `message`, `seed` or `call`.

- `at` (string, required): RFC 3339 simulator time, or `+<duration>` from the clock at load.
- `id` (string, required): Unique within the schedule.
- `as` (string): Actor a vendor-route call acts as; `member` when absent.
- `bind` (map of string to string): Name to JSON Pointer into the call's answer, usable as `${name}` by later events.
- `call` (object): At most one of `body`, `body_text`, `body_base64`.
  - `method` (string, required)
  - `path` (string, required): A vendor route, or one of the simulator's own `/_` routes outside `/_rystic/` and `/_feed`.
  - `body` (any): JSON object, or array where the route reads one.
  - `body_base64` (string, `base64` encoded)
  - `body_text` (string)
  - `headers` (map of string to string): May not set `Authorization`, `Content-Type` or other headers the simulator sets.
- `message` (object): Lands on `GET /_feed`.
  - `channel` (string, required)
  - `body` (any)
- `seed` ([`RegisterMap`](#schema-registermap))

<a id="schema-injectview"></a>

### `InjectView`

- `events` (array of object, required):
  - `at` (string, required)
  - `due` (string, date-time, required)
  - `id` (string, required)
  - `as` (string)
  - `bind` (map of string to string)
  - `bound` (map of string to any)
  - `call` (map of string to any)
  - `error` (string)
  - `fired_at` (string)
  - `message` (map of string to any)
  - `seed` ([`RegisterMap`](#schema-registermap))
  - `status` (integer)
- `fired` (integer, required)
- `pending` (integer, required)

<a id="schema-networkinput"></a>

### `NetworkInput`

Set `enabled`, `profile` or both.

Object, at least 1 field:

- `enabled` (boolean)
- `profile` ([`NetworkProfile`](#schema-networkprofile))

<a id="schema-networkprofile"></a>

### `NetworkProfile`

- `endpoints` (map of string to [`EndpointShape`](#schema-endpointshape), required): Keyed `METHOD /template` exactly as `GET /_rystic/routes` lists them.
- `id` (string, required)
- `default` ([`EndpointShape`](#schema-endpointshape))
- `effects` (map of string to [`DelaySpec`](#schema-delayspec))
- `enabled_by_default` (boolean)

<a id="schema-openobject"></a>

### `OpenObject`

Map of string to any.

<a id="schema-operatorwebhook"></a>

### `OperatorWebhook`

- `active` (boolean, required)
- `delivery` (string: `"on"`, `"off"`, required)
- `notifications` (integer, required)
- `pending` (integer, required)
- `url` (string or null, required)

<a id="schema-referenceentry"></a>

### `ReferenceEntry`

- `job` ([`OpenObject`](#schema-openobject), required)
- `snapshot` ([`OpenObject`](#schema-openobject), required)
- `verdict` (string, required)
- `context` (map of string to string)
- `evidence` ([`OpenObject`](#schema-openobject))
- `outputs` (map of string to string)
- `qualifier` (string)

<a id="schema-registerinfo"></a>

### `RegisterInfo`

- `id` (string, required)
- `keyed` (boolean, required)
- `kind` (string, required)
- `seed_via` (string, required)
- `fields` (array of object):
  - `name` (string, required)
  - `default` (any)
  - `format` (string)
  - `nullable` (boolean)
  - `ref` (string)
  - `required` (boolean)
  - `type` (string)
- `key_fields` (array of string)
- `namespace` (map of string to any)
- `owner_field` (string)
- `tombstones` (boolean)

<a id="schema-registermap"></a>

### `RegisterMap`

`{"<register>": {"<key>": {"<field>": value}}}`. Registers and fields are per simulator; see `GET /_rystic/registers`. A partial record merges into the one at its key. `GET /_rystic/inspect` is in this shape and seeds back as it reads: a per-caller register's key `<owner>\u0000<key>` lands in that owner's records.

Map of string to map of string to map of string to any.

<a id="schema-rejectspec"></a>

### `RejectSpec`

Answers a fraction of requests, and the exact calls `on_calls` numbers, without running them. `body` and `body_text` are exclusive.

- `status` (integer, required)
- `body` (any): JSON body, written verbatim.
- `body_text` (string)
- `content_type` (string)
- `headers` (map of string to string)
- `on_calls` (array of integer, at least 1, unique items): 1-based call numbers to reject, counted per endpoint over every call since reset or profile install, shaped or not.
- `rate` (number, at least 0, at most 1): Per-request rejection probability; absent is 0.

<a id="schema-replaygroupmember"></a>

### `ReplayGroupMember`

- `end_ms` (integer, int64, required)
- `market` (string, required)
- `start_ms` (integer, int64, required)
- `total` (integer, required): Events kept after the group's overlap trim.
- `game` (integer): The member's 1-based game in a sequence.
- `tape` (string): The recording played, when it differs from `market`.

<a id="schema-replaymember"></a>

### `ReplayMember`

A recording name, or `{tape, as}` to play the recording under another ticker.

One of string, or an object:

- string
- an object:
  - `tape` (string, required)
  - `as` (string)

<a id="schema-replayselector"></a>

### `ReplaySelector`

`market` or `markets`, not both. Market names must be distinct after renames. `copies` above 1 plays each member that many times, copy `k` as `<market>-<k>`; tape replay only.

- `copies` (integer, at least 0)
- `market` (string)
- `markets` (array of [`ReplayMember`](#schema-replaymember))

<a id="schema-replaysequencegame"></a>

### `ReplaySequenceGame`

One game of a sequence. Session time `t` maps to recorded time `t - offset_ms`.

- `end_ms` (integer, int64, required)
- `game` (integer, required)
- `markets` (array of string, required)
- `offset_ms` (integer, int64, required)
- `recorded_end_ms` (integer, int64, required)
- `recorded_start_ms` (integer, int64, required)
- `start_ms` (integer, int64, required)

<a id="schema-replaystatus"></a>

### `ReplayStatus`

- `done` (boolean, required)
- `duration_ms` (integer, int64, required)
- `playing` (boolean, required)
- `position` (integer, required)
- `position_ms` (integer, int64, required)
- `rebase_offset_ms` (integer, int64, required): The clock shift `rebase` chose at start; `0` without it.
- `total` (integer, required)
- `at_checkpoint_ms` (integer, int64): The stop the session is paused at.
- `checkpoints` (array of integer, int64): Pending stops, absolute recorded milliseconds.
- `current_game` (integer): The 1-based game the play head last entered.
- `dropped` (integer, int64): Live frames dropped.
- `end_ms` (integer, int64)
- `error` (string)
- `frames` (integer, int64): Live frames applied.
- `lag_ms` (integer, int64): Wall time now minus `last_ms`.
- `last_ms` (integer, int64): Exchange timestamp of the last live frame applied.
- `market` (string): The market, a comma-joined group, or `*` for a live session admitting any market.
- `markets` (array of [`ReplayGroupMember`](#schema-replaygroupmember))
- `mode` (string: `"apply"`, `"seed"`, `"live"`): `apply` drives each event through the simulator's own state and messages; `seed` replaces state with each event's snapshot; `live` is fed through `/_replay/feed`.
- `next_event_ms` (integer, int64): Recorded time of the next pending scheduled event.
- `sequence` (array of [`ReplaySequenceGame`](#schema-replaysequencegame))
- `speed` (number)
- `start_ms` (integer, int64)

<a id="schema-routeinfo"></a>

### `RouteInfo`

- `method` (string, required)
- `template` (string, required)
- `target` (string): The route an alias row stands for.
- `target_method` (string)

<a id="schema-scenarioinfo"></a>

### `ScenarioInfo`

- `frames` (integer, required)
- `name` (string, required)
- `summary` (string, required)
- `loop` (boolean)
- `params` (array of object): A generated scenario's parameters, each with a default; `clock` and `rng_seed` come last.
  - `kind` (string: `"enum"`, `"int"`, `"string"`, `"time"`, required)
  - `name` (string, required)
  - `default` (string)
  - `enum` (array of string)
  - `max` (integer)
  - `min` (integer)
  - `summary` (string)

<a id="schema-scriptedreplies"></a>

### `ScriptedReplies`

- `left` (array of [`ScriptedReply`](#schema-scriptedreply), required)
- `operations` (array of string, required): The routes that take replies.
- `taken` (array of object, required):
  - `operation` (string, required)
  - `reply` (integer, required): The reply's index in the script.
  - `refused` (string): Why the call was refused, when the reply called a tool the request did not declare.

<a id="schema-scriptedreply"></a>

### `ScriptedReply`

- `content` (array of object, required):
  - `type` (string: `"text"`, `"tool_use"`, required)
  - `id` (string): The call id to answer; absent, the simulator mints one.
  - `input` (object): A tool_use block's input.
  - `name` (string): The tool a tool_use block calls; the request must declare it.
  - `text` (string): A text block's text.
- `stop_reason` (string): Why the reply stopped; absent, the simulator answers the service's own for the content.

<a id="schema-seederror"></a>

### `SeedError`

- `error` (string, required)
- `register` (string, required)
- `key` (string)

<a id="schema-seedrejection"></a>

### `SeedRejection`

- `message` (string, required)
- `errors` (array of [`SeedError`](#schema-seederror))

<a id="schema-settle"></a>

### `Settle`

- `idle_ms` (integer, required): Wall milliseconds since the quiet window opened.
- `last_call_seq` (integer, required): The last `seq` in `GET /_rystic/calls`, 0 since a reset with no call.
- `settled` (boolean, required)
- `pending` (array of [`SettleEntry`](#schema-settleentry)): Only when unsettled. What is still open, and the call, ack or reset the quiet window counts from while it is shorter than `quiet_ms`.
- `scheduled` (array of [`SettleEntry`](#schema-settleentry)): Retries due later, which settle never waits on.

<a id="schema-settleentry"></a>

### `SettleEntry`

- `kind` (string: `"socket_mode_envelope"`, `"socket_mode_interaction"`, `"delivery"`, `"interaction"`, `"call"`, `"ack"`, `"reset"`, `"socket_mode_retry"`, `"delivery_retry"`, required)
- `app` (string): The app a Socket Mode envelope or interaction goes to; absent for the default app.
- `attempt` (integer): The attempt, absent for the first.
- `call_seq` (integer): For `call`, the watched call's `seq` in `GET /_rystic/calls`.
- `due_at` (string, date-time): The wall instant a retry fires.
- `due_clock` (string, date-time): The simulator-clock instant a request-URL retry fires, if the clock reaches it first.
- `endpoint` (string): The registered endpoint a delivery goes to.
- `event_seq` (integer): The delivered event's `seq`, as `GET /_rystic/egress` names it.
- `id` (string): A Socket Mode envelope's `envelope_id`.
- `idle_ms` (integer)
- `state` (string: `"queued"`, `"in_flight"`, `"due"`)

<a id="schema-unentitled"></a>

### `Unentitled`

Example: `{"feature":"faults","message":"/_rystic/network needs the \"faults\" feature, which this plan does not include; upgrade at https://www.rystic.ai/pricing","upgrade_url":"https://www.rystic.ai/pricing"}`.

- `feature` (string: `"state_view"`, `"state_set"`, `"scenarios"`, `"faults"`, `"ui"`, `"replay"`, required)
- `message` (string, required)
- `upgrade_url` (string, uri, required)

<a id="schema-version"></a>

### `Version`

- `clock` (string, date-time, required)
- `commit` (string, required)
- `contract` (integer, required)
- `edition` (string, required): `metered` on a simulator that runs on a lease, empty on one that runs on a license file.
- `initial_clock` (string, date-time, required)
- `model_hash` (string, required)
- `network` (object, required):
  - `enabled` (boolean)
  - `endpoints` (integer)
  - `profile_digest` (string)
- `product` (string, required)
- `version` (string, required)
- `api_base_paths` (array of string)
- `auth_scheme` (string)
- `blob_cap_bytes` (integer)
- `body_accepted` (array of string)
- `body_ct_selects` (boolean)
- `compat_auth` (boolean)
- `compat_body_encoding` (boolean)
- `credential_required` (integer)
- `egress_retain` (integer)
- `env` (map of string to any)
- `explorer` (array of string): Explorer tools served, each at `/_rystic/<tool>`.
- `lease` (object): Set only on a simulator that runs on a lease.
  - `entitlements` (map of string to any)
  - `expires_at` (any)
  - `notice` (any)
  - `offline` (boolean)
  - `offline_until` (integer)
  - `session_id` (string)
- `license_expires` (any): Set only on a simulator that runs on a license file.
- `license_features` (array of string)
- `licensee` (string): Set only on a simulator that runs on a license file.
- `preserve_service_urls` (boolean)
- `service_hosts` (array of string)
- `stopping` (any): Present while the simulator is stopping, with the reason.
- `watermark` (string)

<a id="schema-worlddocument"></a>

### `WorldDocument`

A scenario file. Other keys are ignored, except `tape` and `loop`, which are refused.

- `seed` ([`RegisterMap`](#schema-registermap), required)
- `clock` (string, date-time)
- `credentials` (map of string to string): Presented credential to actor label.
- `events` (array of [`InjectEvent`](#schema-injectevent)): The scenario's own schedule; offsets resolve against `clock`.
- `network` ([`NetworkInput`](#schema-networkinput))
- `rng_seed` (integer, int64)
- `sequences` (map of string to integer, int64, at least 0): Id counter to the last value it reached, as `GET /_rystic/inspect?view=sequences` serves them. Each counter rises to at least its value once the seed lands, so the next record created gets an id the seed does not hold.

<!-- END generated: control-plane-reference -->

---

Source: https://www.rystic.ai/docs/control-plane-reference · Markdown: https://www.rystic.ai/docs/control-plane-reference.md
