# Streams

Source: https://www.solscanner.app/docs/indexer/streams

> Live subscriptions to heads, blocks, transactions, logs, transfers, AMM events, creations and launches, with filters you can change without reconnecting.

Streams are the push side of the same data the read routes serve. Where a read route answers what is true at one block, a stream tells you as new records land.

Topics [#topics]

| Topic          | Emits                                                                                                              |
| -------------- | ------------------------------------------------------------------------------------------------------------------ |
| `heads`        | Canonical head movement                                                                                            |
| `blocks`       | New canonical blocks                                                                                               |
| `transactions` | New transactions                                                                                                   |
| `logs`         | New logs                                                                                                           |
| `transfers`    | New token transfers                                                                                                |
| `amm-events`   | New swaps and liquidity events                                                                                     |
| `creations`    | New contract and token creations                                                                                   |
| `launches`     | Launch lifecycle transitions: new, graduating, graduated                                                           |
| `candles`      | Live candle state per named token or pool series: a snapshot on connect, then updates and a final close per bucket |

Topics are isolated, so a slow consumer on one topic does not hold up another.

Every topic has its own single-topic route under `/stream/`. All of them except `heads` can also be
multiplexed through `/stream/subscribe`. The single-topic path is `/stream/amm-events`, but the
multiplexed `select` and `resume` parameters spell the same topic `amm_events`.

One connection, several topics [#one-connection-several-topics]

`GET /stream/subscribe` multiplexes several topics over a single connection rather than making you hold one per topic.

Filters on an open subscription can be changed in place:

```
POST /stream/subscribe/{topic}/{id}/selectors
```

That mutates the filter set of a subscription that is already open, so narrowing or widening what you receive does not cost a reconnect and does not create a gap while you reconnect.

What a stream event is, and is not [#what-a-stream-event-is-and-is-not]

Streams carry the same speculative-versus-canonical distinction as everything else, and it is the thing to get right when building on them.

A speculative event is an early observation. It has not been confirmed by execution, and the transaction behind it may revert. A confirmed event has been through receipt and root verification.

If you are driving something that must not act on an unconfirmed event, key on confirmed records. If you are driving a live view where being seconds early is worth being occasionally wrong, early observations are the reason streams exist. [The overview](/docs/indexer/overview) explains how the two relate.

Recovering after a disconnect [#recovering-after-a-disconnect]

Every durable event carries an `id:` of the form `v1.<namespace>.<sequence>`. Record it only after
your application has durably processed the event, then use it to resume:

| Route                | Resume with                                                                                                                                 |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Single-topic streams | `after_sequence=<sequence>` (the number at the end of the ID), or the full ID in the `Last-Event-ID` header when `after_sequence` is absent |
| `/stream/subscribe`  | `resume=<topic>:<full ID>`, one entry per topic. `Last-Event-ID` alone is not honoured here                                                 |

A browser `EventSource` resends the last full ID as `Last-Event-ID` on its own, so it resumes without
extra code. An ID is bound to its topic and store, so send it back only to the route that emitted
it. A `Last-Event-ID` the route cannot use (a bare number, an ID from another topic, or any header on
`/stream/subscribe`) is not an error: the stream opens live and its first frame is
`event: resume_unsupported`, saying the header was not honoured and which form to use instead.

Two topics do not replay history. The head stream is not durable, and a reconnect across an API
restart reports a gap. The candle stream has no resume cursor at all: every connection opens with a
fresh snapshot of each selected series, and naming `candles` in `resume` is a `400`.

Catch-up is bounded. A consumer that reads too slowly, or whose catch-up stopped short of the
present, receives an `event: lag` frame naming the last sequence delivered, instead of silently
losing events. A resume point older than what the store still retains is handled by how you sent
it:

* An explicit `after_sequence` or `resume` below the retention floor is refused with a `400` naming
  the lowest sequence still retained.
* A `Last-Event-ID` below the floor is served live, and the first frame is an `event: lag` with
  `reason: resume_cursor_below_retention_floor`, the requested sequence, and the `retentionFloor`.
  It is served rather than refused because a browser `EventSource` never retries after a `4xx`.

In every case, read the gap from the indexed routes, then reopen the stream. Do not skip the gap.

A sequence gap is recorded as unhealthy until the corresponding canonical blocks and receipts have
been reconciled.
