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

# Events

> List and stream a session's events, resumable by sequence.

Every [session](https://vetta.sh/docs/api/sessions.md) emits an ordered stream of **events**, each with a monotonically increasing `seq`. You can list past events or subscribe to a live Server-Sent Events (SSE) stream. Both are **resumable**: pass the last `seq` you handled to continue with no gaps or duplicates.

Event types use a `{domain}.{action}` naming scheme. For the full catalog across all phases, see [Events & streaming](https://vetta.sh/docs/concepts/events-and-streaming.md).

## The event object

**id** `string`

Unique id (e.g. `evt_01H...`).

**seq** `integer`

Monotonic sequence number within the session — starts at 1, gap-free. Use it to resume. Mirrored as `last_seq` on the session and as the SSE `id:`.

**type** `string`

The `{domain}.{action}` type — see [below](#event-types).

**session\_id** `string`

The session that emitted the event.

**data** `object`

Type-specific payload.

**created\_at** `string`

Emission timestamp.

## Event types

| Type | Data | Meaning |
| - | - | - |
| `message.delta` | `text` | A chunk of assistant text. |
| `message.completed` | `text`, `tokens` | An assistant message finished. |
| `tool.started` | `name`, `args` | A tool call began. |
| `tool.completed` | `name`, `result`, `duration_ms` | A tool call finished. |
| `tool.confirm` | `tool_call_id`, `name`, `args` | A tool requires approval (`ask` [permission](https://vetta.sh/docs/api/agents.md)). Answer via [tool confirmations](https://vetta.sh/docs/api/sessions.md#respond-to-a-tool-confirmation). |
| `tool.confirm.resolved` | `tool_call_id`, `decision`, `reason`, `scope`, `actor`, `decided_at`, `proposed_args`, `final_args` | A person answered a `tool.confirm`. `decision` (`allow`/`deny`), `reason` and `scope` are what was sent to [tool confirmations](https://vetta.sh/docs/api/sessions.md#respond-to-a-tool-confirmation); `actor` is the user or key that sent it; `proposed_args` are the arguments the card showed; `final_args` are the ones that run — the same, unless the person edited a template install's `answers` as they approved it. For a team member's call it is on the member's session. Listed only with `include_accepted=true`. |
| `tool.confirm.answered` | `tool_call_id`, `answers`, `answered_at` | A person answered a question the agent parked (`awaiting_answer`). `answers` are exactly what was sent to [answer a question](https://vetta.sh/docs/api/sessions.md#answer-a-question), keyed by field. Listed only with `include_accepted=true`; the live stream sends it either way. Never sent to [webhooks](https://vetta.sh/docs/api/webhooks.md). |
| `session.mode` | `mode` | The session changed [mode](https://vetta.sh/docs/concepts/session-operations.md#plan-mode): `plan` or `build`, from a send or from the approval of its plan. Listed only with `include_accepted=true`. |
| `plan.updated` | `object`, `markdown`, `version`, `by` | The session's plan was written — by the agent (`by: "agent"`) or through [`PUT /v1/sessions/{id}/plan`](https://vetta.sh/docs/api/sessions#edit-the-plan) (`by: "owner"`). The data is the plan. Listed only with `include_accepted=true`. |
| `outcome.evaluation.started` · `outcome.evaluation.progress` · `outcome.evaluation.completed` | `outcome_id`, `iteration`, `thread_id`; `completed` adds `result`, `explanation`, `criteria`, `token_usage`, `consumed_micro_usd` | A session held to an outcome is being graded: a grader thread started, began working, and wrote its result (`satisfied`, `needs_revision`, `failed`, `max_iterations_reached`, `interrupted`). `thread_id` is `null` on an `interrupted` no grader wrote. Listed only with `include_accepted=true`. Never sent to [webhooks](https://vetta.sh/docs/api/webhooks.md). |
| `session.running` | — | The agent started a turn. |
| `session.usage` | `token_usage`, `consumed_micro_usd`, `active_seconds`, `tool_calls` | Cumulative usage snapshot, emitted before every idle/terminal transition. |
| `session.idle` | `stop_reason`, `pending_actions?`, `structured_output`, `error?`, `code?`, `background?` | A turn yielded control. Read `stop_reason` (see [session lifecycle](https://vetta.sh/docs/api/sessions.md#lifecycle)). `structured_output` is `null` unless an [output schema](https://vetta.sh/docs/capabilities/structured-outputs.md) was satisfied; `error` appears only when a required schema was not. `code` appears only when the session stopped on failure: the [error code](https://vetta.sh/docs/api/errors.md) of its last attempt. `background` is `[{ job_id, kind }]`: every job the session started and has not finished — a video, a 3D model, a clip, an edit or a skill build, `kind` as on `media.job.*` — `[]` when none. A turn's idle always carries it; an idle with no turn (a wake while waiting on you) leaves it out, and the last one that has it still stands. It is on the [webhook](https://vetta.sh/docs/api/webhooks.md) and `on_idle` envelopes too. |
| `budget.exceeded` | `estimate_micro_usd`, `remaining_micro_usd` | A call was refused because it would breach the cap. |
| `media.job.queued` | `job_id`, `kind`, `model` | A media job was submitted — by an agent's `generate_video` or `generate_3d_model` ([video](https://vetta.sh/docs/api/video.md)), its `clip_video`, `edit_video` or `build_media_skill`, or by `POST /v1/media/clips` ([clips](https://vetta.sh/docs/api/clips.md)). `kind` is `video` (an edit too), `mesh`, `clip`, or `skill_build`; `model` is `null` for a clip, an edit and a build. `job_id` is its `med_` (`clj_` for an agent's clip, `ctp_` for a build). On a session's stream it comes at the job's first check, just after the turn that started it. A REST submit has no session, so it reaches [webhooks](https://vetta.sh/docs/api/webhooks.md) only. |
| `media.job.completed` | `job_id`, `kind`, `file_ids`, `cost_micro_usd?` | The job's files are in [Files](https://vetta.sh/docs/api/files.md); the session has been woken with the first id and told the settled cost. A skill build makes a skill, not files: its `file_ids` is `[]` and it has no `cost_micro_usd` (it is billed outside the session). |
| `media.job.failed` | `job_id`, `kind`, `code` | The job produced nothing and billed nothing; `code` is an [error code](https://vetta.sh/docs/api/errors.md). A skill build the session stopped watching (it may still finish) says neither. |
| `file.imported` | `file_id`, `url` | A [URL import](https://vetta.sh/docs/api/files.md#import-a-file-from-a-url) landed in [Files](https://vetta.sh/docs/api/files.md). A REST import has no session, so this reaches [webhooks](https://vetta.sh/docs/api/webhooks.md) only. |
| `app.db.migrated` | `app_id`, `migration_id`, `name` | A [migration](https://vetta.sh/docs/api/database.md#apply-a-migration) was applied to an app's database for the first time; a replay emits nothing. No session is involved, so this reaches [webhooks](https://vetta.sh/docs/api/webhooks.md) only. |
| `app.storage.bucket.created` | `app_id`, `bucket` | A [bucket](https://vetta.sh/docs/api/storage.md#buckets) was created on an app's object store. Webhooks only, like `app.db.migrated`; object reads and writes are [audit](https://vetta.sh/docs/api/audit-logs.md) rows, not events. |
| `app.storage.bucket.deleted` | `app_id`, `bucket` | A bucket was deleted (`?force=true` empties it first). Webhooks only. |
| `browser.session.opened` | `browser_session_id`, `computer_id`, `region` | The agent's `browser` tool opened its [browser](https://vetta.sh/docs/api/browser.md) on the session's computer — lazily, on the first action. A REST open has no session and is audit-logged instead. |
| `browser.session.closed` | `browser_session_id`, `minutes` | The browser closed and its minutes were settled on the `browser` line; the tool's browser closes when the session ends or pauses for input. |
| `browser.context.saved` | `context_id`, `identity_id` | The `save_context` action marked a [saved login](https://vetta.sh/docs/api/browser.md#save-a-login) saved from the browser opened with it; the close persists the storage. |
| `browser.context.granted` | `grant_id`, `context_id`, `grantee_type`, `grantee_id` | A saved login was shared with an agent or role over REST. No session, so [webhooks](https://vetta.sh/docs/api/webhooks.md) only. |
| `browser.context.revoked` | `context_id` | A saved login was deleted over REST, and every grant on it with it. Webhooks only. |
| `wallet.created` | `wallet_id`, `identity_id`, `network`, `address` | A [wallet](https://vetta.sh/docs/api/wallet.md) was provisioned for an identity on that network. Operator action over REST, so [webhooks](https://vetta.sh/docs/api/webhooks.md) only. |
| `wallet.policy.updated` | `wallet_id`, `identity_id`, `policy` | The per-tx or daily cap changed; the new policy is at the provider already. Webhooks only. |
| `wallet.transaction.confirmed` | `wallet_id`, `identity_id`, `transaction_id`, `kind`, `amount_micro_usd`, `counterparty`, `tx_hash`, `status` | A `fund`, `transfer` or `pay` settled on chain. A `pending` or `failed` transaction emits nothing. Never carries a key, a signature or a payment header. |
| `wallet.swept` | `wallet_id`, `identity_id`, `transaction_id`, `amount_micro_usd`, `counterparty`, `tx_hash`, `status` | The whole balance was drained and the wallet retired. Webhooks only. |
| `card.issued` | `card_id`, `identity_id`, `status`, `load_micro_usd`, `brand`, `last4` | A prepaid [card](https://vetta.sh/docs/api/cards.md) became `active` — on the buy, on a `retry`, or on the reconcile a read does. Never carries a credential. |
| `card.failed` | `card_id`, `identity_id`, `status`, `load_micro_usd`, `reason` | The issuer could not complete the order. The debit stays on the row so `retry` re-runs issuance; `refund` returns the credits. |
| `card.cancelled` | `card_id`, `identity_id`, `status`, `load_micro_usd` | A card was marked cancelled. Local status only — a prepaid card cannot be voided at the issuer. |
| `card.refunded` | `card_id`, `identity_id`, `status`, `load_micro_usd`, `price_micro_usd` | The credits for a `pending_payment` or `failed` card went back to the balance. |
| `card.credentials_revealed` | `card_id`, `identity_id`, `status`, `load_micro_usd` | Someone read the card's number/PIN. The event names the card, never the credential; the [audit](https://vetta.sh/docs/api/audit-logs.md) row names the caller. |
| `thread.created` · `thread.idle` · `thread.terminated` | `thread_id`, … | A [team](https://vetta.sh/docs/team/delegation.md) coordinator's view of a delegated member session: spawned, result folded back, or failed to start. Payloads are on [Context and budgets](https://vetta.sh/docs/team/context-and-budgets.md). `thread.running` is in the enum but nothing emits it today. |

The `session.idle` `stop_reason` is one of `end_turn`, `awaiting_input`, `awaiting_approval`, `awaiting_answer`, `awaiting_delegation`, `budget_paused`, `interrupted`, `max_iterations`, `context_exhausted`, or `error` — the same enum as the [session object](https://vetta.sh/docs/api/sessions.md#lifecycle), which is where each one is explained.

Three of them park the turn on something outside the loop, and only two of those park it on a *person*: `awaiting_approval` and `awaiting_answer` both carry `pending_actions` and both wait for a reply, while `awaiting_delegation` waits for the coordinator's own children and no reply will move it.

## List events

`GET /v1/sessions/{id}/events` → `200 OK`. Past events in order (oldest-first). Resume by passing `after_seq`; combine with `limit` for paging. Events are replayable for at least 72 hours.

**after\_seq** `integer`

Return events with `seq`**strictly greater** than this. Omit to start from the beginning.

**limit** `integer`

Max events to return. Default `20`, max `100`.

**include\_accepted** `boolean`

Include the types this release added — `message.accepted` (a message the session has accepted and not yet answered), `handoff.sent` (a person's fan-out, recorded on the sender's log), `tool.confirm.resolved` (a person's decision on a held tool call), `tool.confirm.answered` (a person's answers to a parked question), `session.mode`, `plan.updated`, and an outcome's grade — `outcome.evaluation.started`, `outcome.evaluation.progress` and `outcome.evaluation.completed`. Off by default, so clients published before those types existed keep reading the page; the live stream sends them either way. It changes only which rows are in `data`: `has_more` and `next_cursor` are the page's own, so nothing is skipped by leaving it off.

```bash theme={"system"}
curl -fsSL "https://api.vetta.sh/v1/sessions/ses_01H9AB.../events?after_seq=140&limit=50" \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{
  "data": [
    { "id": "evt_01H9II...", "seq": 141, "type": "session.running", "session_id": "ses_01H9AB...", "data": {}, "created_at": "2026-08-20T17:05:01Z" },
    { "id": "evt_01H9IJ...", "seq": 142, "type": "message.delta", "session_id": "ses_01H9AB...", "data": { "text": "Looking up order #4821…" }, "created_at": "2026-08-20T17:05:01Z" },
    { "id": "evt_01H9IK...", "seq": 143, "type": "session.idle", "session_id": "ses_01H9AB...", "data": { "stop_reason": "end_turn" }, "created_at": "2026-08-20T17:05:04Z" }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Stream events (SSE)

`GET /v1/sessions/{id}/stream?after_seq={seq}` opens a Server-Sent Events stream. Each SSE message carries one event as JSON in its `data:` field, with the event `seq` as the SSE `id:`. On reconnect, pass the last `seq` you saw as `after_seq` to replay everything since.

```bash theme={"system"}
curl -N "https://api.vetta.sh/v1/sessions/ses_01H9AB.../stream?after_seq=140" \
  -H "authorization: Bearer sk_live_..." \
  -H "accept: text/event-stream"
```

```text Stream theme={"system"}
id: 141
data: {"seq":141,"type":"session.running","data":{}}

id: 142
data: {"seq":142,"type":"message.delta","data":{"text":"Looking up order #4821…"}}

id: 143
data: {"seq":143,"type":"session.idle","data":{"stop_reason":"end_turn"}}
```

> [!NOTE]
>
> Track the highest `seq` you have fully processed. After any disconnect, reconnect with `?after_seq=<that seq>` — the stream is gap-free and duplicate-free from that point.

> [!NOTE]
>
> `tool.confirm` pauses the turn (`stop_reason: "awaiting_approval"`) until you resolve it via [`POST /v1/sessions/{id}/tool_confirmations`](https://vetta.sh/docs/api/sessions#respond-to-a-tool-confirmation). `budget.exceeded` accompanies a `session.idle` with `stop_reason` `budget_paused`.
>
> **A question emits no event of its own.** An agent that calls `ask_operator` parks the same way, and the only signal on the stream is the `session.idle` — `stop_reason: "awaiting_answer"`, with the question in `pending_actions`. Answer it via [`POST /v1/sessions/{id}/answers`](https://vetta.sh/docs/api/sessions#answer-a-question). A stream reader that branches only on `tool.confirm` will sit on a session that is waiting for it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.