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

# Sessions

> Directly-controlled runs of an agent.

A **session** is one directly-controlled run of an [agent](/docs/api/agents). You create it, send input, stream [events](/docs/api/events), interrupt it, and cancel it. Sessions are durable — they can run for a long time and sit idle at storage cost only.

## The session object

<ResponseField name="id" type="string">Unique id (e.g. `ses_01H...`).</ResponseField>
<ResponseField name="agent_id" type="string">The agent this session runs.</ResponseField>
<ResponseField name="agent_version" type="integer">The pinned agent version.</ResponseField>
<ResponseField name="deployment_id" type="string | null">The [deployment](/docs/api/deployments) that created this session, if any.</ResponseField>
<ResponseField name="identity_id" type="string | null">The [persona](/docs/api/identities) this session acts as, or `null` when the create call named none.</ResponseField>
<ResponseField name="computer_id" type="string | null">The [computer](/docs/api/computers) this session provisioned for its filesystem and shell tools, or `null` when its toolset needs none. It is a real computer — it lists under `GET /v1/computers` as `session {id}` and takes `exec`, `snapshot` and `fs/*` — but it belongs to the session: `DELETE` on it is refused while the session can still run, and it is destroyed when the session ends.</ResponseField>
<ResponseField name="status" type="string">Lifecycle state — see [below](#lifecycle).</ResponseField>
<ResponseField name="stop_reason" type="string | null">Why the last turn yielded control — see [below](#lifecycle). Read it whenever the session is not `running`; do not treat `idle` as success.</ResponseField>
<ResponseField name="window" type="string">Effective [completion window](/docs/concepts/completion-window) for this session.</ResponseField>
<ResponseField name="budget_micro_usd" type="integer">Per-session cap, in micro-USD. Always set: it falls back to the agent's `budget.max_task_micro_usd`.</ResponseField>
<ResponseField name="consumed_micro_usd" type="integer">Cumulative spend on this session so far, in micro-USD.</ResponseField>

<ResponseField name="token_usage" type="object">
  Cumulative token counts across five billed tiers.

  <Expandable title="token_usage">
    <ResponseField name="input" type="integer">Prompt tokens.</ResponseField>
    <ResponseField name="cache_write" type="integer">Tokens written to the prompt cache.</ResponseField>
    <ResponseField name="cache_read" type="integer">Tokens served from the prompt cache.</ResponseField>
    <ResponseField name="output" type="integer">Generated output tokens.</ResponseField>
    <ResponseField name="reasoning" type="integer">Reasoning tokens.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="pending_actions" type="object[]">
  What the session is parked on. One array, two kinds of row, distinguished by `kind` — which defaults to `"tool"`, so a row without it is a held tool call.

  <Expandable title="pending_actions[]">
    <ResponseField name="kind" type="string">`tool` (default) or `question`.</ResponseField>
    <ResponseField name="tool_call_id" type="string">The id you answer or confirm this row by.</ResponseField>
    <ResponseField name="name" type="string">The tool that was called — `ask_operator` on a question.</ResponseField>
    <ResponseField name="args" type="object">The call's arguments, verbatim.</ResponseField>
    <ResponseField name="question" type="object | null">Present only when `kind` is `"question"`: `{ prompt, fields }`, with one to three fields. The same content as `args`, parsed, so a renderer need not know that `ask_operator`'s arguments happen to be the question.</ResponseField>
  </Expandable>

  A `kind: "tool"` row is resolved with [tool confirmations](#respond-to-a-tool-confirmation) and accompanies `stop_reason: "awaiting_approval"`. A `kind: "question"` row is resolved with [answers](#answer-a-question) and accompanies `awaiting_answer`. A session parked on both reports `awaiting_approval` and carries both.
</ResponseField>

<ResponseField name="output_schema" type="object | null">JSON Schema the final result must satisfy, inherited from the agent unless overridden at create.</ResponseField>
<ResponseField name="structured_output" type="object | null">The typed result, populated when the session goes idle satisfying `output_schema`. See [Structured outputs](/docs/capabilities/structured-outputs).</ResponseField>
<ResponseField name="last_seq" type="integer">Highest event `seq` emitted so far. See [Events](/docs/api/events).</ResponseField>
<ResponseField name="metadata" type="object">Arbitrary key/value pairs.</ResponseField>
<ResponseField name="started_at" type="string | null">When the session first started running.</ResponseField>
<ResponseField name="ended_at" type="string | null">When the session reached a terminal state (`completed`, `failed`, `cancelled`).</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

## Lifecycle

A session moves through these `status` values:

| Status      | Meaning                                                                                         |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `queued`    | Accepted, waiting for capacity to start.                                                        |
| `running`   | The agent is actively executing a turn.                                                         |
| `idle`      | Stopped but resumable — waiting for input, approval, or the next fire. Carries a `stop_reason`. |
| `completed` | The agent declared the task done. Terminal.                                                     |
| `failed`    | An unrecoverable error ended the session. Terminal.                                             |
| `cancelled` | The session was cancelled by a caller. Terminal.                                                |

Whenever a turn yields control, `stop_reason` is set to one of:

| `stop_reason`         | Meaning                                                                                                                                                                                                                                                                                                                      |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `end_turn`            | The turn finished normally.                                                                                                                                                                                                                                                                                                  |
| `awaiting_input`      | The agent is waiting for more input.                                                                                                                                                                                                                                                                                         |
| `awaiting_approval`   | A tool needs approval — see `pending_actions`.                                                                                                                                                                                                                                                                               |
| `budget_paused`       | Work paused at the budget cap.                                                                                                                                                                                                                                                                                               |
| `interrupted`         | A caller interrupted the turn.                                                                                                                                                                                                                                                                                               |
| `max_iterations`      | The turn hit its step ceiling.                                                                                                                                                                                                                                                                                               |
| `error`               | The turn ended in error.                                                                                                                                                                                                                                                                                                     |
| `context_exhausted`   | The turn produced neither a message nor a tool call.                                                                                                                                                                                                                                                                         |
| `awaiting_delegation` | A [coordinator](/docs/team/coordinator) yielded because every thread it delegated to is still running. Nobody is being asked for anything — the children finishing is what unblocks it.                                                                                                                                           |
| `awaiting_answer`     | The agent asked a question and cannot continue without the answer — see [Answer a question](#answer-a-question). The question is a `pending_actions` row with `kind: "question"`. Reported only when *every* parked row is a question; a session parked on both reports `awaiting_approval` and still carries the questions. |

<Note>
  `idle` is resumable and `completed` is terminal — both are non-`running`. Always branch on `stop_reason` rather than treating `idle` as success. Anything worth keeping is written to [Files](/docs/api/files); scratch work stays in the sandbox.
</Note>

## Create a session

`POST /v1/sessions` → `201 Created`. **Creates and starts** the session. Inherits the agent's configuration; you may override the window, set a per-session budget, and provide an initial `message`.

<ParamField body="agent_id" type="string" required>The agent to run.</ParamField>
<ParamField body="agent_version" type="integer">Pin a specific version. Defaults to the agent's `current_version`.</ParamField>
<ParamField body="message" type="string" required>Initial input. Starts the first turn immediately.</ParamField>
<ParamField body="window" type="string">Override the completion window (`immediate | priority | loose`).</ParamField>
<ParamField body="budget_micro_usd" type="integer">Per-session cap in micro-USD. Work pauses at the cap.</ParamField>
<ParamField body="deployment_id" type="string">Attribute this session to a [deployment](/docs/api/deployments), so its spend shows under that schedule in the [ledger](/docs/api/credits). Defaults to `null`.</ParamField>
<ParamField body="identity" type="string">The [persona](/docs/api/identities) this session acts as — an `idn_` id or the persona's name. The agent must hold a grant to it. Defaults to `null`.</ParamField>
<ParamField body="output_schema" type="object">JSON Schema the final result must satisfy. Overrides the agent default. See [Structured outputs](/docs/capabilities/structured-outputs).</ParamField>
<ParamField body="structured_output_required" type="boolean">When `true`, a session that cannot produce a conforming object goes idle with `stop_reason: "error"`. Overrides the agent default; requires an `output_schema` on the session or the agent.</ParamField>
<ParamField body="metadata" type="object">Arbitrary metadata.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/sessions \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -H "idempotency-key: $(uuidgen)" \
    -d '{ "agent_id": "agt_01H8XK...", "message": "Refund order #4821", "window": "immediate", "budget_micro_usd": 2000000 }'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "id": "ses_01H9AB...",
    "object": "session",
    "agent_id": "agt_01H8XK...",
    "agent_version": 2,
    "deployment_id": null,
    "identity_id": null,
    "computer_id": "cmp_01H8YZ...",
    "status": "queued",
    "stop_reason": null,
    "window": "immediate",
    "budget_micro_usd": 2000000,
    "consumed_micro_usd": 0,
    "token_usage": { "input": 0, "cache_write": 0, "cache_read": 0, "output": 0, "reasoning": 0 },
    "pending_actions": [],
    "output_schema": null,
    "structured_output": null,
    "last_seq": 0,
    "metadata": {},
    "started_at": null,
    "ended_at": null,
    "created_at": "2026-08-20T17:05:00Z"
  }
  ```
</ResponseExample>

## Retrieve a session

`GET /v1/sessions/{id}` → `200 OK` with the current status, `stop_reason`, and usage.

```json Response theme={"system"}
{ "id": "ses_01H9AB...", "status": "idle", "stop_reason": "end_turn", "consumed_micro_usd": 41870, "last_seq": 142 }
```

## List sessions

`GET /v1/sessions` → `200 OK`, cursor-paginated. Filter with `?agent_id=`, `?deployment_id=`, and `?status=`. See [Pagination](/docs/api/pagination).

## Send input

`POST /v1/sessions/{id}/messages` → `202 Accepted`. Sends input to a session and produces [events](/docs/api/events). The reply is the **acceptance**, not a session snapshot: the turn has not run yet.

<ParamField body="message" type="string" required>The message text to send.</ParamField>
<ParamField body="interrupt" type="boolean">When `true`, steer a `running` session in one call: the current turn stops at its next commit boundary and the new message is applied. This is the common case. Defaults to `false`.</ParamField>
<ParamField body="idempotency_key" type="string">Accepted and ignored. De-duplicate a retried send with the `Idempotency-Key` **header** — see [Idempotency](/docs/api/overview#idempotency); this body field does nothing.</ParamField>

Without `interrupt`, sending to a `running` session returns `409` with code `session_running` — interrupt first, or set `interrupt: true`.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/sessions/ses_01H9AB.../messages \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "message": "Actually, refund order #4822 instead", "interrupt": true }'
```

```json Response theme={"system"}
{ "session_id": "ses_01H9AB...", "status": "running", "accepted_seq": 143 }
```

`accepted_seq` is the session's event high-water mark at the moment the send was accepted: **every event this turn produces has `seq > accepted_seq`**. That is what to wait on — poll `GET /v1/sessions/{id}/events?after_seq=143`, or wait for the session's `last_seq` to pass it. Do not wait on `status` alone: the send returns before the first event is committed, so a status read taken immediately after can still describe the previous turn.

## Interrupt a session

`POST /v1/sessions/{id}/interrupt` → `200 OK`. Stops the current turn at its next commit boundary, preserving history and the sandbox, and returns the session `idle` with `stop_reason: "interrupted"`. An optional `message` is applied as the next input. Unlike [cancel](#cancel-a-session), the session stays resumable.

<ParamField body="message" type="string">Optional input to apply after interrupting.</ParamField>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/sessions/ses_01H9AB.../interrupt \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{ "id": "ses_01H9AB...", "status": "idle", "stop_reason": "interrupted" }
```

## Respond to a tool confirmation

When a tool with `permission: "ask"` runs, the session goes idle with `stop_reason: "awaiting_approval"` and the blocking calls appear in `pending_actions`. Resolve each with:

`POST /v1/sessions/{id}/tool_confirmations` → `202 Accepted`, carrying the full [session object](#the-session-object) with the resolved call removed from `pending_actions`. Like a send, the reply is the acceptance: the tool has not run yet.

<ParamField body="tool_call_id" type="string" required>The id from `pending_actions[].tool_call_id` (also carried on the `tool.confirm` [event](/docs/api/events)).</ParamField>
<ParamField body="decision" type="string" required>`allow` to run the tool, or `deny` to reject it.</ParamField>
<ParamField body="reason" type="string">On `deny`, fed back to the model as the tool result.</ParamField>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/sessions/ses_01H9AB.../tool_confirmations \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "tool_call_id": "call_01H...", "decision": "allow" }'
```

```json Response theme={"system"}
{ "id": "ses_01H9AB...", "object": "session", "status": "running", "pending_actions": [] }
```

## Answer a question

An agent that needs something from a person parks the turn: the session goes idle with `stop_reason: "awaiting_answer"` and the question appears in `pending_actions` as a row with `kind: "question"`, carrying a `prompt` and one to three `fields`. Answer it with:

`POST /v1/sessions/{id}/answers` → `202 Accepted`, carrying the full [session object](#the-session-object) with the answered question removed from `pending_actions`. Scope `sessions:write`.

<ParamField body="tool_call_id" type="string" required>The id from `pending_actions[].tool_call_id` on the row you are answering.</ParamField>
<ParamField body="answers" type="object" required>One entry per field, keyed by `fields[].key`. A string, or an array of strings for a `choice` field with `multiple: true`. Every field must be answered.</ParamField>

This is a separate route from `tool_confirmations`, not a variant of it. A held tool call is answered with a verb and no payload; a question is answered with a payload and no verb. Posting an answer for a `kind: "tool"` row is rejected with `validation_failed` (400) naming `tool_call_id`, and points you at the other route.

A `choice` field accepts anything the person types unless it sets `other: false`, in which case only its listed `options` are accepted. The values are read back by the model as words, so send the option text and not an index.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/sessions/ses_01H9AB.../answers \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "tool_call_id": "call_01H...", "answers": { "mailbox": "hello@acme.com", "tone": "warm" } }'
```

```json Response theme={"system"}
{ "id": "ses_01H9AB...", "object": "session", "status": "running", "pending_actions": [] }
```

## Raise the budget

`PATCH /v1/sessions/{id}/budget` → `200 OK`. Scope `sessions:write`.

A session that hit its cap stops with `stop_reason: "budget_paused"`. Raise the cap and it becomes resumable.

<ParamField body="cap_micro_usd" type="integer" required>The new per-session cap, in micro-USD.</ParamField>

<Warning>
  **Raise only.** A cap at or below what the session has already consumed is rejected with `validation_failed` (400) — lowering it would be retroactive, charging a limit against spend that already happened.
</Warning>

```bash theme={"system"}
curl -fsSL -X PATCH https://api.vetta.sh/v1/sessions/ses_01H9AB.../budget \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "cap_micro_usd": 5000000 }'
```

Returns the full session object with the new `budget_micro_usd`.

**Errors** — `validation_failed` (400) for a cap that is not a raise; `not_found` (404); `insufficient_credits` (402) when the organization cannot cover the new cap.

## Cancel a session

`POST /v1/sessions/{id}/cancel` → `200 OK`. Terminally stops the session and moves it to `cancelled`. Any in-flight turn is stopped. Use [interrupt](#interrupt-a-session) instead if you want to keep the session resumable.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/sessions/ses_01H9AB.../cancel \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{ "id": "ses_01H9AB...", "status": "cancelled" }
```

<Card title="Stream events" icon="bolt" href="/docs/api/events">
  List and stream a session's events, resumable by `seq`.
</Card>
