---
title: Streaming events
description: Tail a session's log over SSE, and pick back up from where you stopped when the connection drops.
sidebar:
  order: 1
---

The point of a durable event log is that watching it is optional. A run keeps going whether or not anyone is connected, and when you reconnect the log tells you everything you missed.

## Reading the log

Two endpoints read the same events. The paged one is for catching up on a session that already happened; the stream is for watching one that is happening.

```http
GET /v1/sessions/{session_id}/events
GET /v1/sessions/{session_id}/events/stream
```

```bash
curl -N "https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID/events/stream" \
  -H "Authorization: Bearer $SUBAKO_SESSION_TOKEN"
```

Both endpoints take the **session token** — the `id` and `session_token` a
[created session](/quickstart) returned, held here in `SESSION_ID` and
`SUBAKO_SESSION_TOKEN`. An API key works too, but a token that reads one
session is the one to hand a browser.

## Where the stream starts

Connecting with no cursor tails from the log's **current head**: you see what happens next, and nothing that already happened. That is what you want for a session you are about to drive, and not what you want for one already in flight — there you would miss the run's opening events and, if the run has finished, receive nothing at all.

`after_seq` names the starting point, sending events whose sequence number is strictly greater. `after_seq=0` replays the whole log and then continues live, which is how you catch up and tail over one connection.

```bash
curl -N "https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID/events/stream?after_seq=0" \
  -H "Authorization: Bearer $SUBAKO_SESSION_TOKEN"
```

Each SSE frame carries three fields:

| Field | What it holds |
| --- | --- |
| `id` | The event's sequence number — what a reconnect resumes from |
| `event` | The event kind, e.g. `message_assistant` |
| `data` | The same body the paged endpoint returns for that event |

## Resuming after a drop

Reconnect with `Last-Event-ID` set to the last sequence number you actually processed. The stream replays from just after it, then continues live.

```bash
curl -N "https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID/events/stream" \
  -H "Authorization: Bearer $SUBAKO_SESSION_TOKEN" \
  -H "Last-Event-ID: 42"
```

In the browser, `EventSource` sends that header for you on reconnect, using the last `id` it saw. Two rules keep it honest:

1. **Advance your cursor only after you have handled the event**

    The header is a promise about what you have processed, not about what
    arrived. Move the cursor at the end of your handler.

2. **Treat events as replayable**

    A reconnect can hand you an event you already saw if your cursor lagged.
    Key your UI state off the sequence number rather than appending blindly.

## Knowing when a run is over

A run ends in exactly one of three events:

```txt
run_completed   the turn finished with nothing left to do
run_failed      it stopped on an error; `error` carries the text
run_cancelled   a cancel request was honored
```

The stream does not close when a run ends — the session is still there, and appending more input starts another run. If you are waiting for one specific run, watch for its `run_id` on the terminal event rather than for the connection closing.

```http
POST /v1/sessions/{session_id}/cancel
```

Cancelling asks the current run to stop. It is a request, not a kill: what has already been committed to the log stays there, and the run ends with `run_cancelled` when it lands.

:::note
Since the log is the whole truth, you never need to hold a stream open to stay correct. A worker that polls the paged endpoint every few seconds sees exactly what a streaming client sees, just later.
:::
