# Events and live streams

Every [intent](/docs/intents) and [session](/docs/sessions) keeps a
**timeline**: its status changes and its steps, oldest first. Read it as a
page, or follow it live as Server-Sent Events.

## The event object

```json
{
  "id": "evt_3b9f1a",
  "object": "event",
  "type": "status",
  "created_at": "2026-10-13T08:30:04Z",
  "data": { "status": "running" }
}
```

`type` says what `data` holds. New types may appear: skip the ones you don't
know.

| `type` | Of | `data` |
| --- | --- | --- |
| `status` | intents, sessions | `{ status, reason? }`: the new status, and why when there is a why (a failed intent's error code, say). |
| `step` | intents | `{ action }`: what the agent did: `open`, `look`, `click`, `type`, `read`. Never what it typed. |
| `checkout` | intents | `{ merchant, total, currency }`: the payment step was reached. |
| `page` | sessions | `{ url, title }`: the session's page changed. |
| `command` | sessions | `{ id, type, status, typed_chars? }`: a command ran. |

An intent that moves to a fresh browser shows `{ "status": "queued",
"reason": "browser_lost" }`, and one tried again to get past a shop's check,
`{ "status": "queued", "reason": "retrying" }`. One that waits for its
customer to sign in shows `{ "status": "requires_login", "reason":
"login_required" }`, then `{ "status": "queued", "reason": "signed_in" }`.
One whose agent asks your customer something to sign them in shows
`{ "status": "requires_input", "reason": "code" }` (or `phone`, `email`,
`approval`), then `{ "status": "running", "reason": "answered" }` (or
`declined`, `approved`, `expired`). Never the answer itself.

## A page of events

<Endpoint method="GET" path="/v1/intents/{id}/events?limit=50" />

<Endpoint method="GET" path="/v1/sessions/{id}/events?limit=50" />

```json
{
  "object": "list",
  "data": [{ "id": "evt_3b9f1a", "object": "event", "type": "status", "data": { "status": "queued" } }],
  "next_cursor": "evt_3b9f1a"
}
```

Pass the `next_cursor` back as `cursor` for the next page.

## Live streams

Ask the same endpoint for `text/event-stream`:

```bash
curl -N https://api.openintents.io/v1/intents/int_7f3a2c/events \
  -H "Authorization: Bearer $OPENINTENTS_API_KEY" \
  -H "Accept: text/event-stream"
```

```text
id: evt_3b9f1a
data: {"id":"evt_3b9f1a","object":"event","type":"status","created_at":"2026-10-13T08:30:00Z","data":{"status":"queued"}}

id: evt_3b9f2c
data: {"id":"evt_3b9f2c","object":"event","type":"status","created_at":"2026-10-13T08:30:01Z","data":{"status":"running"}}

: keep-alive
```

- Past events come first, then new ones as they happen. Lines starting with
  `:` only keep the connection open.
- Once the intent or session is over and its last event is sent, the stream
  closes.
- To resume, reconnect with the last event's `id` in `Last-Event-ID`: you get
  what came after it, without repeats. A stream may also close on its own
  after a while; resuming picks it up.
- Asking for the stream of something over, with nothing left to send,
  answers `204 No Content`: stop there.
- Too many open streams answer `429` with `code: too_many_streams`; try again
  shortly.

A browser's `EventSource` can't send an `Authorization` header, so follow
streams from your server, or read the response body as a stream with `fetch`.

## Events or webhooks

[Webhooks](/docs/webhooks) tell your server about the changes that matter
(completed, failed, payment required), whether or not anyone is watching.
Event streams show everything as it happens, to whoever follows: a progress
view for your users, say.
