# Streaming & events

Use the conversation's event stream to show progress and its durable journal to restore state.
Both expose the same recorded history; the stream also carries temporary text updates for your UI.

## Choose how to read

| Interface | Use it for | Ordering |
| --- | --- | --- |
| [`events.list`](/api-reference/events/list) | History, backfilling, or polling | Oldest durable event first |
| [`conversations.stream`](/api-reference/events/conversations-stream) | Replaying history, then following live work | Durable sequence plus live frames |

`events.list` returns `data` and `nextCursor`. Pass the cursor back unchanged until it is
`null`, meaning the end as of that read. More history may arrive later.

## Connect to the stream

Open `GET /v1/users/{userId}/conversations/{conversationId}/stream` with both authentication headers and
`Accept: text/event-stream`. A `cursor` of `0` replays from the beginning. A positive cursor
replays durable events strictly after that journal sequence.

```bash
curl --fail-with-body -sSN \
  "$FIN_BASE_URL/v1/users/$FIN_USER_ID/conversations/$FIN_CONVERSATION_ID/stream?cursor=0" \
  -H "X-Api-Key: $FIN_API_KEY" \
  -H "Authorization: Bearer $FIN_USER_TOKEN" \
  -H 'Accept: text/event-stream'
```

For a browser application, connect through your backend so your partner key stays server-side.
Native `EventSource` does not accept arbitrary authorization headers; use your authenticated
same-origin proxy or a streaming HTTP client suited to your session design.

## Durable and live frames

This illustrates the framing, not a complete run transcript:

```text
: connected

id: 42
event: run.started
data: {"delivery":"durable","seq":42,"payload":{"input":"Hello","runId":"…"}}

event: agent.text_delta
data: {"delivery":"live","payload":{"text":"Hello"}}
```

| Delivery | Persistence | Client behavior |
| --- | --- | --- |
| `durable` | Has a journal `seq`, appears in history, replays on reconnect | Apply to saved conversation state. |
| `live` | Has no journal sequence and does not replay | Use for temporary presentation only. |

`agent.text_delta` provides text while the assistant is generating. Replace that temporary
text with the canonical reply when durable `agent.entry` arrives. Lines beginning with `:`
are comments or heartbeats, not events. Parse SSE framing before interpreting `data` as JSON.

## Reconnect without losing your place

Record the last durable sequence you successfully applied. Reconnect with that value in
`Last-Event-ID` or `?cursor=`; `Last-Event-ID` takes precedence when both are supplied.
Deduplicate durable events by conversation and sequence when reloading or replaying them.
Live text deltas never advance this cursor.

Reconnect with backoff and honor `Retry-After` on `429`. A stream uses the user's credential;
if it expires, renew it through your authentication integration before reconnecting. Streams
have their own capacity limits in addition to normal request limits.

## Read resources after a reconnect

The journal shows progress; resource reads provide the current facts without reconstructing
every event. [`runs.get`](/api-reference/agent-runs/runs-get) returns the original request,
current status, full public assistant response, structured output and pending approvals. The
same public run id survives every continuation.

`approval.opened` announces a decision. Fetch
[`approvals.get`](/api-reference/approvals/get) for its persisted tool input, summary,
fingerprint and authorization contract, even after a decision. You do not need to find the
original event to render those exact terms.

## Handle approvals and continuations

`approval.decided` records a decision. `approval.consumed` records permission being spent before
execution; it is not a successful tool result. Follow the journal for tool results and replies,
or read the same run resource for its current result.

A `run.ended` event with `status: "paused"` ends an execution segment while its public run waits
for a decision. Approval resume keeps the same public run id and emits no new `run.started`.
After `approval.decided`, continue processing tool results, assistant entries and the eventual
`run.ended`; do not wait for a fresh start event. The public run ends when its status becomes
`completed`, `failed` or `aborted`.

## Read financial evidence separately

[`financialOperations.get`](/api-reference/financial-operations/financial-operations-get)
returns a financial request, its frozen plan, actual execution legs and complete public
receipts. `intents.progress` and `intents.settlement` name the `operationId`; use settlement
updates to refresh that resource. A model reply or completed run does not prove settlement.

Direct withdrawals have no conversation journal. Read their financial operation for consent
and execution evidence. See [Financial operations](/financial-operations).

The [event catalog](/events) describes each event. The [quickstart](/quickstart) shows a
complete connection.
