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

# Runs

> A run is one POST /v1/runs request and one stored record. Its kinds, its states, every field of the response, replays with Idempotency-Key, and stored runs.

A run is the unit of work in OpenType: one `POST /v1/runs` request and one stored record. A decision run makes exactly one model call. This page explains what a run returns, the states it moves through, what happens when you send the same request twice, and how a stored run differs from the live response. Read it before you write retry logic or store run results in your own database.

## Kinds

The `kind` field picks what a run does.

| `kind`     | You send                                 | You get                                                  | Served today                         |
| ---------- | ---------------------------------------- | -------------------------------------------------------- | ------------------------------------ |
| `decision` | a `state` and typed `questions`          | `decision`: a probability for every answer you defined   | Yes, by Neon 1.1                     |
| `verdict`  | `messages` and a JSON Schema in `schema` | `verdict`: a JSON document validated against your schema | No. Answers `503 no_route_available` |

`verdict` is the default when `kind` is absent, so **always send `"kind": "decision"`**. Neon 1.1 serves decision runs only; see [Models and pricing](/getting-started/models-and-pricing).

Fields that belong to the other kind are refused with `400 invalid_body`, and the message names the fix. For example, `system` on a decision run gives "system is only valid on a verdict run; use instructions". Unknown fields are refused too. `max_output_tokens` is required on every run and must be greater than zero.

## States

| `state`     | Meaning                                                                                               |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| `pending`   | The run was admitted and has not finished.                                                            |
| `completed` | The model answered. The run carries `decision` (or `verdict`) and its cost.                           |
| `failed`    | The model was called and the run did not produce an answer. It is charged for what the call consumed. |

`POST /v1/runs` is synchronous. It admits the run as `pending`, calls the model, settles the run, and only then answers `200` with the final record. You do not need to poll a fresh request.

A run that fails **before** any model served it (for example `503 decision_unavailable` or `504 deadline_exceeded`) is not charged: the hold on your credit is released. Its record stays `pending`, so retry that request with a **new** `Idempotency-Key`; see [Replays](#replays).

## The response

A live response to a completed decision run:

```json theme={"system"}
{
  "run_id": "run_a4314b6cc08f4bd8814099a613abeb44",
  "kind": "decision",
  "state": "completed",
  "input_digest": "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6",
  "output_digest": "18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4",
  "decision": {
    "answers": {
      "urgent": {"answered_within_labels": true, "label_mass": 0.991, "probability": 0.83, "type": "noul"}
    },
    "draws": 1,
    "read": "slot_constrained",
    "model": "neon-1.1",
    "stages": [["urgent"]]
  },
  "usage": {"input_tokens": 412, "output_tokens": 23},
  "cost_micros": 19,
  "cost_basis": "provider_reported",
  "replayed": false
}
```

| Field                | Type       | Present                      | Meaning                                                                                                                                                                                               |
| -------------------- | ---------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `run_id`             | string     | always                       | `run_` followed by 32 lowercase hex characters. Use it to read the run back.                                                                                                                          |
| `kind`               | string     | always                       | `decision` or `verdict`.                                                                                                                                                                              |
| `state`              | string     | always                       | `pending`, `completed` or `failed`.                                                                                                                                                                   |
| `input_digest`       | string     | always                       | Hex SHA-256 (64 characters) of the normalized input and contract. It identifies the request for replays.                                                                                              |
| `output_digest`      | string     | when the run produced output | Hex SHA-256 of the answer.                                                                                                                                                                            |
| `decision`           | object     | completed decision runs      | The answers, keyed by your question ids. See [Decision questions](/getting-started/decision-questions#answer-shapes).                                                                                 |
| `verdict`            | JSON value | completed verdict runs       | The validated document. A run carries `decision` or `verdict`, never both.                                                                                                                            |
| `usage`              | object     | when known                   | `input_tokens` and `output_tokens`, both integers.                                                                                                                                                    |
| `cost_micros`        | integer    | when known                   | What the run cost, in micro-USD (1,000,000 = \$1).                                                                                                                                                    |
| `cost_basis`         | string     | live responses only          | `provider_reported` when the cost comes from the reported token counts. `estimated` when it is a pre-call estimate, which is always the case when `draws` is greater than 1 or no usage was reported. |
| `schema_enforcement` | string     | live verdict responses only  | How the schema bound the answer: `unconstrained`, `requested`, `forced` or `enforced`. Every returned verdict passed validation whatever this says.                                                   |
| `replayed`           | boolean    | always                       | `false` for a fresh model call. `true` whenever the body came from storage: a replay, a `GET`, or a list row.                                                                                         |

Absent optional fields are omitted from run responses, not sent as `null`.

## Replays

`POST /v1/runs` requires an `Idempotency-Key` header of 1 to 255 bytes. The key is unique within your organization and never expires. OpenType compares the new request with the run already stored under that key:

| Request                                     | Status | You get                                                                            |
| ------------------------------------------- | ------ | ---------------------------------------------------------------------------------- |
| A key not used before                       | `200`  | A fresh run.                                                                       |
| Same key, same body, stored run `completed` | `200`  | The stored run with `replayed: true`. No model call, no charge.                    |
| Same key, same body, stored run `failed`    | `200`  | The stored run with `state: "failed"` and `replayed: true`. It does not run again. |
| Same key, same body, stored run `pending`   | `202`  | The stored `pending` run, as is.                                                   |
| Same key, different body                    | `409`  | [`idempotency_conflict`](/problems/idempotency_conflict).                          |

"The same body" means the same input (`state`, or `system` and `messages`), the same `kind`, and the same contract: `instructions`, `questions` in order, `draws` and `think_tokens` (or `schema`). Changing `max_output_tokens` or `deadline_ms` alone does not make it a different request.

A replay is never charged, and it is never refused for credit or quota. Two rules follow:

* **Retry a network failure with the same key.** If the first request completed, you get its result without paying twice.
* **Retry a `503` or `504` with a new key.** A run that failed before a model served it stays `pending`, and the old key answers `202` with that run on every replay.

This sends a run, then sends the same request again with the same key and gets the stored run back:

<CodeGroup>
  ```bash cURL theme={"system"}
  body='{"kind":"decision","state":"Refund today or I dispute the charge.","questions":{"urgent":{"type":"noul","instructions":"reply within the hour?"}},"max_output_tokens":16}'
  for attempt in 1 2; do
    curl -sS https://api.opentype.dev/v1/runs \
      -H "Authorization: Bearer $OPENTYPE_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: ticket-4823-urgent" \
      -d "$body" | jq '{run_id, state, replayed}'
  done
  ```

  ```ts TypeScript theme={"system"}
  const send = () =>
    fetch("https://api.opentype.dev/v1/runs", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENTYPE_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": "ticket-4823-urgent",
      },
      body: JSON.stringify({
        kind: "decision",
        state: "Refund today or I dispute the charge.",
        questions: { urgent: { type: "noul", instructions: "reply within the hour?" } },
        max_output_tokens: 16,
      }),
    }).then((res) => res.json());

  const first = await send();
  const second = await send();
  console.log(first.run_id === second.run_id, first.replayed, second.replayed); // true false true
  ```

  ```python Python theme={"system"}
  import os, requests

  def send():
      return requests.post(
          "https://api.opentype.dev/v1/runs",
          headers={
              "Authorization": f"Bearer {os.environ['OPENTYPE_API_KEY']}",
              "Idempotency-Key": "ticket-4823-urgent",
          },
          json={
              "kind": "decision",
              "state": "Refund today or I dispute the charge.",
              "questions": {"urgent": {"type": "noul", "instructions": "reply within the hour?"}},
              "max_output_tokens": 16,
          },
          timeout=160,
      ).json()

  first, second = send(), send()
  print(first["run_id"] == second["run_id"], first["replayed"], second["replayed"])  # True False True
  ```
</CodeGroup>

The second response is the stored run, so it has no `cost_basis` and its `decision` has no `model` or `stages`. The [Idempotency](/guides/idempotency) guide covers key design and retry loops.

## Stored runs are thinner

Every run is stored under its `run_id`. What you can read back depends on the route:

| Route                          | Answer (`decision` or `verdict`) | `usage`, `cost_micros` | `cost_basis` | `decision.model`, `stages`, `thought_tokens`, `thought_closed` |
| ------------------------------ | -------------------------------- | ---------------------- | ------------ | -------------------------------------------------------------- |
| Live `POST /v1/runs`           | yes                              | yes                    | yes          | yes                                                            |
| Replay of `POST /v1/runs`      | yes                              | yes                    | no           | no                                                             |
| `GET /v1/runs/{run_id}`        | yes                              | yes                    | no           | no                                                             |
| `GET /v1/runs/{run_id}/stream` | yes                              | no                     | no           | no                                                             |
| `GET /v1/runs` (list rows)     | no                               | no                     | no           | no                                                             |

If you need the full live record, store the `POST` response yourself. For per-run cost and token detail later, use [`GET /v1/usage/runs/{run_id}`](/guides/usage-reporting).

`GET /v1/runs` lists your organization's runs newest first, with `limit` (default 20, 1 to 100) and `offset` (default 0). See [Pagination](/guides/pagination).

## Limits on every run

| Limit               | Value                                                                                                                                      | Refusal                                                                                                                                |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| Request body        | 4 MiB (4,194,304 bytes)                                                                                                                    | `413` [`body_too_large`](/problems/body_too_large)                                                                                     |
| Input for Neon 1.1  | 262,144 tokens (256k, estimated)                                                                                                           | `413` [`input_too_large`](/problems/input_too_large)                                                                                   |
| Spend per run       | 20,000 micro-USD (\$0.02), held while the run is in flight                                                                                 | `503` [`budget_exhausted`](/problems/budget_exhausted)                                                                                 |
| `deadline_ms`       | Decision: default 30,000 plus 120,000 per 262,144 input tokens, clamped to 1 to 150,000. Verdict: default 30,000, clamped to 1 to 120,000. | `504` [`deadline_exceeded`](/problems/deadline_exceeded)                                                                               |
| `max_output_tokens` | Required, greater than 0                                                                                                                   | `400` [`invalid_body`](/problems/invalid_body)                                                                                         |
| `Idempotency-Key`   | Required, 1 to 255 bytes                                                                                                                   | `400` [`idempotency_key_required`](/problems/idempotency_key_required), [`invalid_idempotency_key`](/problems/invalid_idempotency_key) |

The full table, including question and verdict schema bounds, is on [Limits](/reference/limits).

## Related

* [Decision questions](/getting-started/decision-questions) - what goes in `questions` and what comes back in `decision`.
* [Idempotency](/guides/idempotency) - choose keys and write a retry loop that never pays twice.
* [Polling](/guides/polling) - read a stored run back with `GET /v1/runs/{run_id}`.
* [Error handling](/guides/error-handling) - which errors to fix and which to retry.
* [Limits](/reference/limits) - every bound on a run in one place.
