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

# Agent Runs.

> A run is a durable, server-side record of one agent task: submit it once, follow its event stream, cancel it, and read the outcome back later. The chat endpoints are a single turn; a run is the whole job.

## Why runs exist

The chat endpoints ([Messages](/api/messages), [Chat Completions](/api/chat-completions),
[Responses](/api/responses)) are stateless — one request, one turn, and the connection is
the only place the work lives. An agent loop is not one turn: it is a job that can outlive
a socket, be resumed by a different process, and needs an audit trail after it finishes.

A **run** is that job as a first-class object. It has an id, an append-only event log, a
terminal outcome, and it is scoped to the key that created it.

<Note>
  Runs, [states](#states), and [artifacts](/api/artifacts) are one surface. Reach for the
  chat endpoints when you want a completion; reach for runs when you want a job you can
  hand off, follow, and inspect afterwards.
</Note>

## Endpoints

| Endpoint                            | What it does                                                        |
| ----------------------------------- | ------------------------------------------------------------------- |
| `POST` `/v1/agent-runs`             | Create a run. With `model` + `input`, the gateway also executes it. |
| `GET` `/v1/agent-runs/{id}`         | Read the run record.                                                |
| `GET` `/v1/agent-runs/{id}/events`  | Follow the run's event log over SSE.                                |
| `POST` `/v1/agent-runs/{id}/events` | Append your own events to the log.                                  |
| `POST` `/v1/agent-runs/{id}/cancel` | Cancel a run.                                                       |
| `POST` `/v1/states`                 | Create a durable state handle.                                      |
| `POST` `/v1/states/{id}:append`     | Append a delta to a state handle.                                   |
| `GET` `/v1/states/{id}`             | Read the current state snapshot.                                    |

Every endpoint authenticates exactly like the rest of the API — `Authorization: Bearer lx1_...`
— and is scoped to the authenticated key. Ids are never a tenancy check on their own: a run
belonging to another key returns `404`, not someone else's data.

## Create a run

```http theme={null}
POST /v1/agent-runs
```

| Field          | Type            | Notes                                                                                                                                                                                                                  |
| -------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`        | string          | A catalog id. Supplying it together with `input` makes the run **executable** — see below.                                                                                                                             |
| `input`        | string \| array | A bare string is one user turn. An array is a message list: `{ role, content }`, where `role` is `user`, `assistant`, or `system` and `content` is a string or content blocks.                                         |
| `tools`        | array           | Tool definitions, in the same shape the chat endpoints take. See [Tool calling](/guides/tool-calling).                                                                                                                 |
| `optimization` | object          | `{ "mode": "exact" \| "auto", "allow": { ... } }`. `exact` pins the model you named; `auto` lets the engine choose equivalents. Defaults to the platform behavior described in [Model routing](/guides/model-routing). |
| `budgetUsd`    | number          | A non-negative ceiling for this run, in list-price USD.                                                                                                                                                                |
| `metadata`     | object          | Free-form JSON echoed back on the run record.                                                                                                                                                                          |
| `parentRunId`  | string          | Links this run to a parent, for fan-out trees.                                                                                                                                                                         |

### Executable vs. bookkeeping runs

The response status tells you which one you created:

* **`202 Accepted`** — you sent both `model` and a usable `input`, so the gateway is
  executing the run for you. The body is `{ "run": { ... }, "execution": "started" }`.
  Follow it on the event stream.
* **`201 Created`** — no executable payload. The run is a durable record you drive
  yourself by appending events. The body is `{ "run": { ... } }`.

```sh title="Executable run" theme={null}
curl https://api.layerx1.com/v1/agent-runs \
  -H "authorization: Bearer $LAYERX1_API_KEY" \
  -H "content-type: application/json" \
  -H "idempotency-key: nightly-report-2026-08-09" \
  -d '{
    "model": "lx1-gpt-oss-120b",
    "input": "Summarize the attached incident log.",
    "budgetUsd": 0.50,
    "metadata": { "job": "nightly-report" }
  }'
```

```json title="202 Accepted" theme={null}
{
  "run": {
    "id": "run_...",
    "status": "running",
    "nodeIds": ["node_..."]
  },
  "execution": "started"
}
```

### Idempotency

Send an `Idempotency-Key` header (up to 200 characters) and a repeat of the same create
returns the **original** response instead of starting a second run. Replays come back with
`200` and an `idempotency-replayed: true` header, and keys are remembered for 24 hours.

This is the header to reach for whenever a retry could duplicate real work — a network
blip on a submit, an at-least-once queue, a cron that fires twice.

<Note>
  Idempotency is best-effort by design: if the key store is briefly unavailable the create
  still succeeds rather than failing. Treat it as strong protection against retries, not as
  a distributed lock.
</Note>

## Read a run

```http theme={null}
GET /v1/agent-runs/{id}
```

Returns `{ "run": { ... } }`, or `404` when the id does not belong to your key.

## Follow the event stream

```http theme={null}
GET /v1/agent-runs/{id}/events
```

Server-Sent Events. Each frame carries the event's own type as the SSE `event:` name and
the full event object as JSON `data:`. The stream ends with a terminating frame:

```
event: node.started
data: {"type":"node.started","seq":1,...}

event: node.completed
data: {"type":"node.completed","seq":7,...}

event: done
data: {}
```

An executable run always reaches a terminal `outcome` event — including when execution
throws. A client tailing the stream is never left waiting on a run that died silently.

```sh title="Tail a run" theme={null}
curl -N https://api.layerx1.com/v1/agent-runs/run_123/events \
  -H "authorization: Bearer $LAYERX1_API_KEY"
```

## Append your own events

```http theme={null}
POST /v1/agent-runs/{id}/events
```

For runs you drive yourself: record tool calls, checkpoints, and decisions into the same
log the gateway writes to, so one timeline covers the whole job.

| Field    | Type             | Notes                                                                                  |
| -------- | ---------------- | -------------------------------------------------------------------------------------- |
| `events` | array · required | 1–1,000 events. Each must be an object with a string `type`; everything else is yours. |

```json title="Request" theme={null}
{
  "events": [
    { "type": "tool.called", "name": "search_docs", "args": { "q": "retention" } },
    { "type": "tool.returned", "name": "search_docs", "rows": 12 }
  ]
}
```

```json title="Response" theme={null}
{ "appended": 2, "lastSeq": 9 }
```

## Cancel a run

```http theme={null}
POST /v1/agent-runs/{id}/cancel
```

The body is optional: `{ "reason": "superseded by run_456" }`. Returns the updated run.

## States

A **state handle** is a durable, append-only value scoped to your key — the place to keep
an agent's working memory when the agent itself is stateless across processes. Each append
returns a monotonically increasing `seq`, so two writers can tell whose write landed last.

| Endpoint                        | Body                                 | Returns                                              |
| ------------------------------- | ------------------------------------ | ---------------------------------------------------- |
| `POST` `/v1/states`             | `{ "value": <any JSON> }`            | `201` · `{ "state": { "stateId", "seq", "value" } }` |
| `POST` `/v1/states/{id}:append` | `{ "delta": <any JSON> }` · required | `{ "state": { ... } }`                               |
| `GET` `/v1/states/{id}`         | —                                    | `{ "state": { ... } }`                               |

```sh title="Create, append, read" theme={null}
curl https://api.layerx1.com/v1/states \
  -H "authorization: Bearer $LAYERX1_API_KEY" \
  -H "content-type: application/json" \
  -d '{"value":{"visited":[]}}'

curl "https://api.layerx1.com/v1/states/state_123:append" \
  -H "authorization: Bearer $LAYERX1_API_KEY" \
  -H "content-type: application/json" \
  -d '{"delta":{"visited":["/docs/quickstart"]}}'

curl https://api.layerx1.com/v1/states/state_123 \
  -H "authorization: Bearer $LAYERX1_API_KEY"
```

<Note>
  The append path uses a colon, not a slash — `/v1/states/{id}:append`. That is deliberate:
  it keeps `{id}` a clean resource path so a state id can never collide with a sub-resource
  name.
</Note>

## Errors

This surface returns a flat, protocol-neutral error body:

```json theme={null}
{ "error": { "type": "not_found", "message": "run not found" } }
```

| Status | Type                 | When                                                                             |
| ------ | -------------------- | -------------------------------------------------------------------------------- |
| `400`  | `invalid_request`    | Malformed JSON, or a field that failed validation — the message names the field. |
| `401`  | `unauthorized`       | Missing or invalid key.                                                          |
| `404`  | `not_found`          | No such run/state **for this key**.                                              |
| `405`  | `method_not_allowed` | Right path, wrong method. Carries an `Allow` header.                             |
| `501`  | `not_implemented`    | The endpoint exists but its backing store is not enabled on this deployment.     |

## From the CLI

Every endpoint on this page has a `npx layerx1` equivalent — useful for poking at a run
without writing a client:

```sh theme={null}
npx layerx1 run create --model lx1-gpt-oss-120b --input "Summarize this." --budget 0.50
npx layerx1 run follow  run_123          # tails the SSE stream
npx layerx1 run inspect run_123
npx layerx1 run cancel  run_123 --reason "superseded"
npx layerx1 state create --value '{"visited":[]}'
npx layerx1 state append state_123 --delta '{"visited":["/quickstart"]}'
```

See the [CLI reference](/cli) for the full command list.
