<!-- source: https://secondlayer.tools/docs/api-reference#tail-stream-events -->

## Tail the event firehose over SSE

`GET /v1/streams/events/stream`

Server-sent events over the same read as `/v1/streams/events`, with the same parameters. The server polls every 1.5 seconds by default (`STREAMS_SSE_POLL_MS`) and writes one frame per event: `id` is the event's cursor and `data` is `{ "event": <StreamsEvent> }`, or `{ "event", "sig", "key_id" }` with an ed25519 signature over the event's JSON when the instance has a signing key. After 20 seconds with no events it writes `event: ping` with empty data; ignore it. With no `cursor`, `from_cursor` or `from_height`, the tail starts at the current tip minus the reorg margin. `Last-Event-ID` is not read; to resume after a disconnect, reconnect with `from_cursor` set to the last `id` you processed.

### Filters

| Name | Type | Description |
| --- | --- | --- |
| `chain` | enum | `stacks` (default) is the existing Stacks feed, byte-identical to before. `bitcoin` serves Runes events (rune_etch, rune_mint, rune_transfer, rune_burn — see the RuneStreamsEvent schema) on their own cursor space: a bitcoin cursor only resumes with chain=bitcoin. Stacks-only filters (contract_id, sender, recipient, asset_identifier, filters, clock) 400 on chain=bitcoin; rune and address are bitcoin-only. Values: `stacks`, `bitcoin`. |
| `types` | array of enum | Comma-separated event types to include. Default: all of them. |
| `event_type` | enum | One event type, as an alias for `types`. Mutually exclusive with `types`; a set must use `types`. Values: `stx_transfer`, `stx_mint`, `stx_burn`, `stx_lock`, `ft_transfer`, `ft_mint`, `ft_burn`, `nft_transfer`, `nft_mint`, `nft_burn`, `print`. |
| `not_types` | array of enum | Comma-separated event types to exclude, applied after `types`. |
| `contract_id` | string | Contract principal, or a comma-separated set. Matches print events by the emitting contract and ft/nft events by the asset's contract. Other event types never match. |
| `sender` | string | Principal, or a comma-separated set, matched against the payload's `sender`. Event types without a sender never match. |
| `recipient` | string | Principal, or a comma-separated set, matched against the payload's `recipient`. |
| `asset_identifier` | string | Exact `<contract>::<asset>` match on the payload. |
| `filters` | string | JSON object of up to 8 labelled filter groups: `{ "<label>": { types?, contractId?, sender?, recipient?, assetIdentifier? } }`. Each value is a string or array of strings. Groups OR together, fields inside a group AND, and the flat filters above still apply to the whole scan. Each event lists the labels it matched in `matched`. Labels are letters, digits, `-` and `_`, 32 characters at most. |
| `rune` | string | chain=bitcoin only. A RuneRef: an id (`840000:3`) or a name, spacers/case ignored (`DOG•GO•TO•THE•MOON`, `dog.go.to.the.moon`, `doggotothemoon`). |
| `address` | string | chain=bitcoin only. A mainnet address. |

### Range and paging

| Name | Type | Description |
| --- | --- | --- |
| `limit` | integer · 1 to 1000 | Most events written per poll. Default 100. Values above 1000 are clamped to 1000. |
| `cursor` | string | `<block_height>:<event_index>` from a previous page's `next_cursor`. Resumes strictly after it. Mutually exclusive with `from_cursor` and `from_height`. |
| `from_cursor` | string | Same as `cursor`. `0:0` starts at genesis. |
| `from_height` | integer | First block height to read. Mutually exclusive with a cursor. With neither, the read starts 1000 blocks behind the tip. |
| `to_height` | integer | Last block height to read, inclusive. Always clamped to the tip minus a 2-block reorg margin, which is also the default. |

### Responses

- `200` An open `text/event-stream`. Frames: `id: <cursor>` plus `data: {"event":{...}}` per event, and `event: ping` after 20 seconds idle
- `400` An unknown query parameter. Other bad values close the stream without a frame
- `401` This bind requires the instance token, and it was missing or wrong
- `429` Rate limited on the hosted API (50/s per IP without a key, 10/s on a free key). Self-hosted instances do not throttle Streams. Retry after `Retry-After` seconds

### Example

```bash
curl -G https://api.secondlayer.tools/v1/streams/events/stream \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY" \
  -d contract_id=SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token \
  -d asset_identifier=SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token::alex \
  -d filters={"alex":{"types":["ft_transfer"],"contractId":"SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token"},"stx":{"types":"stx_transfer"}}
```
