> Fetch the index of every Tonbo docs page as Markdown at https://tonbo.dev/llms.txt

# Read, tail and HEAD

Last updated 2 November 2026

Every parameter, header and status of a request that reads a stream: a read, a long-poll, an SSE tail, `HEAD` and the reads a cache can keep.

Reads are `GET` and `HEAD` on the stream’s URL, and change nothing, so any of them may be sent again; the guide is [Read and tail](https://tonbo.dev/docs/streams/read.md). The headers every request shares and the error codes are on the [overview](https://tonbo.dev/docs/reference/streams.md).

## Read

Reads the stream from an offset to its end, or as far as one answer carries, and says where to read next.

`GET {stream}?offset=…` · Protocol: [protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up), [protocol 6](https://ursula.tonbo.io/docs/specs/durable-stream/#6-offsets), [protocol 7.1](https://ursula.tonbo.io/docs/specs/durable-stream/#71-json-mode), [protocol 8.1](https://ursula.tonbo.io/docs/specs/durable-stream/#81-catch-up-and-long-poll-reads), [extensions 9.2](https://ursula.tonbo.io/docs/specs/extensions/#92-leader-consistency), [extensions 9.3](https://ursula.tonbo.io/docs/specs/extensions/#93-response-size), [extensions 9.4](https://ursula.tonbo.io/docs/specs/extensions/#94-json-stream-reads)

Credential: A key or token that reaches the stream, read-only or not; none for a [public stream](https://tonbo.dev/docs/streams/access.md#public).

### Request

| Parameter | Value | Required | What it does |
| --- | --- | --- | --- |
| `offset` query | offset | Optional; `-1` when omitted | Where to start: `-1`, the start of the stream; `now`, its current end, which answers at once with nothing and the end as `Stream-Next-Offset`; or a `Stream-Next-Offset` an earlier answer gave. An offset before the stream's retention answers `410` ([Offsets](https://tonbo.dev/docs/reference/streams.md#offsets)). ([protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up)) |
| `consistency` query | `local` or `leader` | Optional; `local` when omitted | `local` reads from the replica that answers, which may not yet hold the latest acknowledged appends. `leader` sees every append acknowledged before the request was sent, for a reader that must observe its own write. Any other value, or `leader` with `live`, answers `400`. ([extensions 9.2](https://ursula.tonbo.io/docs/specs/extensions/#92-leader-consistency)) |
| `max_bytes` query | positive integer | Optional; 8 MiB when omitted | The most bytes one answer returns; a larger value counts as 8 MiB, and a value that is not an integer is ignored. On a JSON stream it counts bytes of the newline-delimited answer, so an answer may end inside a message: keep the incomplete last line until the next answer completes it. On a tail it bounds each answer or each data event. ([extensions 9.3](https://ursula.tonbo.io/docs/specs/extensions/#93-response-size)) |
| `If-None-Match` header | entity tag | Optional | The `ETag` of an earlier answer at the same offset: when nothing changed, the answer is `304` with no body. ([protocol 8.1](https://ursula.tonbo.io/docs/specs/durable-stream/#81-catch-up-and-long-poll-reads)) |

### Answer

| Field | Value | When | What it means |
| --- | --- | --- | --- |
| `Content-Type` header | media type | On 200 | The stream's content type, except on a JSON stream, whose reads are `application/x-ndjson`: one message per line, each ending in a newline, as it was stored. ([extensions 9.4](https://ursula.tonbo.io/docs/specs/extensions/#94-json-stream-reads)) |
| `Stream-Next-Offset` header | offset | On 200 and 304 | Where to read next. It equals the request's offset when the answer is empty. ([protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up)) |
| `Stream-Up-To-Date` header | `true` | When the answer reached the end | The answer holds everything the stream had when it was made. Absent, the answer stopped short, at 8 MiB or at `max_bytes`: read again from `Stream-Next-Offset`. It is not the end of the stream: only `Stream-Closed` says nothing more will come. ([protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up)) |
| `Stream-Closed` header | `true` | When the stream is closed and the answer reaches its end | Nothing more will ever be appended: the reader is done. A read that stops short of the end of a closed stream does not carry it. ([protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up)) |
| `Stream-Cursor` header | string | Sometimes | Send it back as `cursor` on the next [long-poll](#long-poll-cursor). ([protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up)) |
| `ETag` header | entity tag | On 200, but not for `offset=now` | Names this answer's bytes, and changes when the stream closes: send it as `If-None-Match` to read the same offset again without its body. ([protocol 5.6](https://ursula.tonbo.io/docs/specs/durable-stream/#56-read-stream---catch-up)) |

### Statuses

| Status | What it means |
| --- | --- |
| `200` | The bytes from the offset on, or an empty body at the end of the stream. |
| `304` | Nothing changed since the `ETag` the request sent. |
| `400` | A malformed offset, or a `consistency` that is not `local` or `leader` or is combined with `live`. |

| Refusal | When | Send again |
| --- | --- | --- |
| [`401`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unauthorized) | The key or token is missing, expired or revoked, or the request sent a key that acts as an Agent, which answers the code `exchange_required`. | With a valid key or a new token |
| [`403`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#forbidden) | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No |
| [`404`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#not-found) | The stream does not exist yet, is still being created, or was deleted or expired; a request with a missing or expired token may answer the same. | Once it is active |
| [`410`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#trimmed) | The offset is before the stream’s retention, which a snapshot replaced. | From the answer’s offset |
| [`412`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#incarnation) | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No |
| [`429`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#rate-limited) | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After |
| [`503`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unavailable) | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After |

Sending it again: Always safe: a read changes nothing.

### Example

```shell
curl -sS -i "$STREAM?offset=-1" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Long-poll

At the end of the stream, waits until something is appended or the timeout passes, then answers once.

`GET {stream}?offset=…&live=long-poll` · Protocol: [protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll), [extensions 9.1](https://ursula.tonbo.io/docs/specs/extensions/#91-long-poll-timeout), [protocol 8.1](https://ursula.tonbo.io/docs/specs/durable-stream/#81-catch-up-and-long-poll-reads)

Credential: As for a read. Each long-poll held open counts toward the account's 16 open tails in the region ([Limits](https://tonbo.dev/docs/streams/limits.md#tails)).

### Request

| Parameter | Value | Required | What it does |
| --- | --- | --- | --- |
| `offset` query | offset | Required | Where to wait: the last `Stream-Next-Offset`, or `now` to wait for only what is appended next. Before the end, the answer is at once, as a [read](#read). ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |
| `live` query | `long-poll` | Required | Makes the read wait at the end of the stream. ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |
| `timeout_ms` query | integer, 1 to 60000 | Optional; `1000` when omitted | How long to wait, in milliseconds, before answering `204`. A larger value counts as 60000, and one that is not a number as the default. Every other read ignores it. ([extensions 9.1](https://ursula.tonbo.io/docs/specs/extensions/#91-long-poll-timeout)) |
| `cursor` query | string | Optional | The last `Stream-Cursor` an answer gave. Send it on every long-poll after the first, so a cache in front never serves the same empty answer twice. ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |

### Answer

| Field | Value | When | What it means |
| --- | --- | --- | --- |
| `Stream-Next-Offset` header | offset | On 200 and 204 | Where to wait next. On a `204`, the end of the stream. ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |
| `Stream-Up-To-Date` header | `true` | On 204, and on a 200 that reached the end | The reader holds everything appended so far. ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |
| `Stream-Cursor` header | string | On every answer while the stream is open | Send it back as `cursor` on the next long-poll. Absent once the stream is closed. ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |
| `Stream-Closed` header | `true` | When the stream is closed | Nothing more will come. At the end of a closed stream the long-poll answers `204` with it at once, without waiting. ([protocol 5.7](https://ursula.tonbo.io/docs/specs/durable-stream/#57-read-stream---live-long-poll)) |

### Statuses

| Status | What it means |
| --- | --- |
| `200` | What was appended while it waited, as a [read](#read) answers. |
| `204` | The timeout passed with nothing appended, or the stream is closed at the end: read again from `Stream-Next-Offset` unless `Stream-Closed` is set. |
| `400` | A missing or malformed offset or parameter. |

| Refusal | When | Send again |
| --- | --- | --- |
| [`401`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unauthorized) | The key or token is missing, expired or revoked, or the request sent a key that acts as an Agent, which answers the code `exchange_required`. | With a valid key or a new token |
| [`403`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#forbidden) | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No |
| [`404`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#not-found) | The stream does not exist yet, is still being created, or was deleted or expired; a request with a missing or expired token may answer the same. | Once it is active |
| [`410`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#trimmed) | The offset is before the stream’s retention, which a snapshot replaced. | From the answer’s offset |
| [`412`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#incarnation) | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No |
| [`429`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#rate-limited) | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After |
| [`503`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unavailable) | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After |

Sending it again: Always safe. After a dropped connection, a `408` or a `5xx`, wait a moment and long-poll again from the last `Stream-Next-Offset`; after a `429` or `503`, at least its `Retry-After`.

### Example

```shell
curl -sS -i "$STREAM?offset=$NEXT&live=long-poll&timeout_ms=30000" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Tail with Server-Sent Events

Keeps one connection open and sends what is appended as Server-Sent Events, each batch followed by a control event with the next offset.

`GET {stream}?offset=…&live=sse` · Protocol: [protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse), [protocol 6](https://ursula.tonbo.io/docs/specs/durable-stream/#6-offsets), [extensions 9.3](https://ursula.tonbo.io/docs/specs/extensions/#93-response-size), [extensions 9.5](https://ursula.tonbo.io/docs/specs/extensions/#95-binary-sse)

Credential: As for a read, sent with the request, so a client that can set `Authorization` (not a browser's `EventSource`) or a public stream. Each open tail counts toward the account's 16 open tails in the region.

### Request

| Parameter | Value | Required | What it does |
| --- | --- | --- | --- |
| `offset` query | offset | Required | Where to start: the last `streamNextOffset`, `-1`, or `now` for only what is appended next. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `live` query | `sse` | Required | Answers as an event stream instead of one body. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `max_bytes` query | positive integer | Optional; 8 MiB when omitted | The most bytes one data event carries. A text stream's events carry at least 4 bytes, so each holds a whole character. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `Accept` header | `text/event-stream` | Optional | The answer is an event stream whether or not it is sent; the docs send it for clients and proxies that expect it. |

### Answer

| Field | Value | When | What it means |
| --- | --- | --- | --- |
| `Content-Type` header | `text/event-stream` | On 200 | The answer is an event stream. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `Stream-Data-Content-Type` header | media type | On 200 | What a data event's payload is once decoded: `application/x-ndjson` for a JSON stream, the stream's content type otherwise. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `Stream-Sse-Data-Encoding` header | `base64` | On a stream neither `text/*` nor JSON | Each data event is the standard base64 of a range of the stream's bytes: join its lines, remove the line breaks, then decode. ([extensions 9.5](https://ursula.tonbo.io/docs/specs/extensions/#95-binary-sse)) |
| `data` event | text | For each batch | What was appended. Its `data:` lines are transport framing, not message boundaries: on a JSON stream, join them and split messages at each newline, keeping an incomplete last line for the next event. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `control` event | JSON object | After every data event | Where the reader stands, in the fields below. Reconnect from its `streamNextOffset`. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `streamNextOffset` control event | offset | Always | The offset after the bytes sent so far: where a new connection starts. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `streamCursor` control event | string | While the stream is open | The cursor, as `Stream-Cursor` is for a long-poll. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `upToDate` control event | `true` | When the reader has everything | Everything appended so far has been sent. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `streamClosed` control event | `true` | When the stream is closed | The last event: the stream is closed and everything was sent. The server ends the connection; do not reconnect. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `error` event | JSON error body | When the tail fails | The tail ended with the error body of [Error codes](https://tonbo.dev/docs/reference/streams.md#errors), such as the stream being deleted. Reconnect, and the answer to the reconnect decides. ([protocol 5.8](https://ursula.tonbo.io/docs/specs/durable-stream/#58-read-stream---live-sse)) |
| `credential-expired` event | `{"reason":"credential_expired"}` | At the token's expiry | The token the tail was opened with has expired, and the connection ends. It is not the stream's end: reconnect from the last `streamNextOffset` with a fresh token. |

### Statuses

| Status | What it means |
| --- | --- |
| `200` | The event stream. A proxy may close a quiet connection, since the server sends no heartbeat: reconnect from the last `streamNextOffset` unless the last control event had `streamClosed`. |
| `400` | A missing or malformed offset or parameter. |

| Refusal | When | Send again |
| --- | --- | --- |
| [`401`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unauthorized) | The key or token is missing, expired or revoked, or the request sent a key that acts as an Agent, which answers the code `exchange_required`. | With a valid key or a new token |
| [`403`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#forbidden) | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No |
| [`404`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#not-found) | The stream does not exist yet, is still being created, or was deleted or expired; a request with a missing or expired token may answer the same. | Once it is active |
| [`410`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#trimmed) | The offset is before the stream’s retention, which a snapshot replaced. | From the answer’s offset |
| [`412`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#incarnation) | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No |
| [`429`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#rate-limited) | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After |
| [`503`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unavailable) | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After |

Sending it again: Always safe. Reconnect from the last `streamNextOffset` after a dropped connection, an `error` event or a `credential-expired` event; after a `429` or `503`, wait at least its `Retry-After`.

### Example

```shell
curl -sS -N "$STREAM?offset=$NEXT&live=sse" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Accept: text/event-stream"
```

## HEAD

Reads the stream's state without its contents: its end, whether it is closed, its retention, its latest snapshot and its limits.

`HEAD {stream}` · Protocol: [protocol 5.5](https://ursula.tonbo.io/docs/specs/durable-stream/#55-stream-metadata), [extensions 2.4](https://ursula.tonbo.io/docs/specs/extensions/#24-snapshot-metadata-on-head-and-stream-incarnation), [extensions 4.4](https://ursula.tonbo.io/docs/specs/extensions/#44-read-origin-hint), [extensions 10](https://ursula.tonbo.io/docs/specs/extensions/#10-server-metadata)

Credential: As for a read.

### Answer

| Field | Value | When | What it means |
| --- | --- | --- | --- |
| `Content-Type` header | media type | On 200 | The stream's content type, which every append sends: `application/json` for a JSON stream, though its reads are `application/x-ndjson`. ([protocol 5.5](https://ursula.tonbo.io/docs/specs/durable-stream/#55-stream-metadata)) |
| `Stream-Next-Offset` header | offset | On 200 | The end of the stream, where the next append starts. A `HEAD` sees every append acknowledged before it was sent. ([protocol 5.5](https://ursula.tonbo.io/docs/specs/durable-stream/#55-stream-metadata)) |
| `Stream-Closed` header | `true` | When the stream is closed | Absent while the stream is open. ([protocol 5.5](https://ursula.tonbo.io/docs/specs/durable-stream/#55-stream-metadata)) |
| `Stream-Retained-Offset` header | offset | On 200 | The earliest offset still readable: reads before it answer `410`. The start of the stream until [retention](https://tonbo.dev/docs/reference/streams/snapshots.md#retention) advances. ([extensions 2.4](https://ursula.tonbo.io/docs/specs/extensions/#24-snapshot-metadata-on-head-and-stream-incarnation)) |
| `Stream-Snapshot-Offset` header | offset | When a snapshot exists | The offset of the latest snapshot, which [Read a snapshot](https://tonbo.dev/docs/reference/streams/snapshots.md#read-snapshot) takes. `HEAD` and bootstrap are the only ways to learn it. ([extensions 2.4](https://ursula.tonbo.io/docs/specs/extensions/#24-snapshot-metadata-on-head-and-stream-incarnation)) |
| `Stream-Snapshot-Digest` header | 64 lowercase hexadecimal digits | When a snapshot exists | The latest snapshot's digest, the one its publish answered. ([extensions 2.4](https://ursula.tonbo.io/docs/specs/extensions/#24-snapshot-metadata-on-head-and-stream-incarnation)) |
| `Stream-Previous-Incarnation` header | token | After a rewrite of the stream's history | The stream's `Stream-Incarnation` before its history was last rewritten, as by a restore. A reader that held it keeps what it read below `Stream-Inherited-Offset`; any other reader treats what it read as void. ([extensions 2.4](https://ursula.tonbo.io/docs/specs/extensions/#24-snapshot-metadata-on-head-and-stream-incarnation)) |
| `Stream-Inherited-Offset` header | offset | With Stream-Previous-Incarnation | Below this offset the stream's bytes are the same as under the previous incarnation; at or above it they may differ. ([extensions 2.4](https://ursula.tonbo.io/docs/specs/extensions/#24-snapshot-metadata-on-head-and-stream-incarnation)) |
| `Ursula-Max-Request-Bytes` header | integer | On 200 | The largest request body the stream accepts, 8 MiB today ([Limits](https://tonbo.dev/docs/streams/limits.md#body)). Send no larger body. ([extensions 10](https://ursula.tonbo.io/docs/specs/extensions/#10-server-metadata)) |
| `Ursula-Max-Snapshot-Bytes` header | integer | On 200 | The largest snapshot the stream accepts [in parts](https://tonbo.dev/docs/reference/streams/snapshots.md#stage-part). ([extensions 10](https://ursula.tonbo.io/docs/specs/extensions/#10-server-metadata)) |
| `Stream-Read-Origin` header | origin | Not sent today | Another origin that serves the [cacheable reads](#immutable-chunk) of this stream. Tonbo's regions send none; a client that finds one may ignore it, and sends only those reads there. ([extensions 4.4](https://ursula.tonbo.io/docs/specs/extensions/#44-read-origin-hint)) |

### Statuses

| Status | What it means |
| --- | --- |
| `200` | The stream exists; its state is in the headers. |

| Refusal | When | Send again |
| --- | --- | --- |
| [`401`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unauthorized) | The key or token is missing, expired or revoked, or the request sent a key that acts as an Agent, which answers the code `exchange_required`. | With a valid key or a new token |
| [`403`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#forbidden) | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No |
| [`404`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#not-found) | The stream does not exist yet, is still being created, or was deleted or expired; a request with a missing or expired token may answer the same. | Once it is active |
| [`412`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#incarnation) | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No |
| [`429`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#rate-limited) | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After |
| [`503`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unavailable) | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After |

Sending it again: Always safe.

### Example

```shell
curl -sS -I "$STREAM" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Read a cacheable chunk

Reads the stream from an offset to the next 4 MiB boundary, or to its end, at a URL whose answer, once marked, never changes.

`GET {stream}/incarnation/{token}/chunk/{offset}` · Protocol: [extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources), [extensions 4.2](https://ursula.tonbo.io/docs/specs/extensions/#42-chunk-grid), [extensions 4.3](https://ursula.tonbo.io/docs/specs/extensions/#43-immutable-responses)

Credential: As for a read. Only an answer to a request without a credential, of a public stream, may be kept by a shared cache.

### Request

| Parameter | Value | Required | What it does |
| --- | --- | --- | --- |
| `token` path | token | Required | The stream's `Stream-Incarnation`, from `HEAD` or any answer. Once the stream is deleted and created again, the URL answers `412`. A `Stream-Incarnation` request header changes nothing here. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `offset` path | offset | Required | Where the chunk starts, URL-encoded. The chunks of every reader meet at the same 4 MiB boundaries, so after one answer every reader requests the same URLs. A query on the URL answers `400`. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `If-None-Match` header | entity tag | Optional | An earlier answer's `ETag`: `304` when it still holds. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |

### Answer

| Field | Value | When | What it means |
| --- | --- | --- | --- |
| `Content-Type` header | media type | On 200 and 304 | As for a [read](#read): `application/x-ndjson` on a JSON stream. A chunk may end inside a message. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `Stream-Offset` header | offset | On 200 and 304 | The first offset the body holds: the one in the URL. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `Stream-Next-Offset` header | offset | On 200 and 304 | The offset after the body's last byte: the next chunk's URL. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `Stream-Closed` header | `true` | When the chunk ends at the end of a closed stream | Nothing more will come. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `Stream-Immutable` header | `true` | When the answer can never change | Every later answer for this URL is the same, so it can be kept for good. A chunk that ends at the open end of the stream is not marked: read on from there with an ordinary [read](#read). ([extensions 4.3](https://ursula.tonbo.io/docs/specs/extensions/#43-immutable-responses)) |
| `ETag` header | entity tag | On 200 and 304 | Names the chunk, for `If-None-Match`. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |

### Statuses

| Status | What it means |
| --- | --- |
| `200` | The chunk. |
| `304` | The `ETag` the request sent still names the chunk. |
| `400` | A query on the URL, or a token or offset in a form the stream never gives. |
| `416` | The stream now at this URL, after a recreate, ends before the offset. |

| Refusal | When | Send again |
| --- | --- | --- |
| [`401`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unauthorized) | The key or token is missing, expired or revoked, or the request sent a key that acts as an Agent, which answers the code `exchange_required`. | With a valid key or a new token |
| [`403`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#forbidden) | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No |
| [`404`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#not-found) | The stream does not exist yet, is still being created, or was deleted or expired; a request with a missing or expired token may answer the same. | Once it is active |
| [`410`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#trimmed) | The offset is before the stream’s retention, which a snapshot replaced. | From the answer’s offset |
| [`412`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#incarnation) | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No |
| [`429`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#rate-limited) | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After |
| [`503`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unavailable) | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After |

Sending it again: Always safe.

### Example

```shell
curl -sS -i "$STREAM/incarnation/$INCARNATION/chunk/$OFFSET" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

## Read the snapshot at a cacheable URL

Reads the latest snapshot at a URL that names the stream's incarnation, so its answer can be kept for good.

`GET {stream}/incarnation/{token}/snapshot/{snapshot_offset}` · Protocol: [extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources), [extensions 4.3](https://ursula.tonbo.io/docs/specs/extensions/#43-immutable-responses)

Credential: As for a [cacheable chunk](#immutable-chunk).

### Request

| Parameter | Value | Required | What it does |
| --- | --- | --- | --- |
| `token` path | token | Required | The stream's `Stream-Incarnation`, as for a [chunk](#immutable-chunk-token). ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `snapshot_offset` path | offset | Required | The latest snapshot's offset, from `Stream-Snapshot-Offset`; any other answers `404`. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |

### Answer

| Field | Value | When | What it means |
| --- | --- | --- | --- |
| `Stream-Snapshot-Offset` header | offset | On 200, 206 and 304 | The snapshot's offset. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `Stream-Snapshot-Digest` header | 64 lowercase hexadecimal digits | On 200, 206 and 304 | The snapshot's digest, as its publish answered. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |
| `Stream-Immutable` header | `true` | On 200 and 206 | A snapshot at its URL never changes. ([extensions 4.1](https://ursula.tonbo.io/docs/specs/extensions/#41-immutable-resources)) |

### Statuses

| Status | What it means |
| --- | --- |
| `200` | The snapshot, with the headers of [Read a snapshot](https://tonbo.dev/docs/reference/streams/snapshots.md#read-snapshot); `Range` and `If-None-Match` work the same. |
| `206` | The byte range the request's `Range` named. |

| Refusal | When | Send again |
| --- | --- | --- |
| [`401`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unauthorized) | The key or token is missing, expired or revoked, or the request sent a key that acts as an Agent, which answers the code `exchange_required`. | With a valid key or a new token |
| [`403`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#forbidden) | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No |
| [`404`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#snapshot-not-latest) | A snapshot read names an offset that is not the latest snapshot’s. | At the latest offset |
| [`412`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#incarnation) | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No |
| [`429`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#rate-limited) | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After |
| [`503`](https://tonbo.dev/docs/streams/troubleshooting/stream.md#unavailable) | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After |

Sending it again: Always safe.

### Example

```shell
curl -sS -i "$STREAM/incarnation/$INCARNATION/snapshot/$OFFSET" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```
