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

# Snapshots and retention

Last updated 29 October 2026

Give new readers a starting point, and stop keeping history you no longer need: publish a snapshot of the stream, advance its retention past the history before it, and give a new reader the current state in one request.

A stream keeps everything appended to it until it is deleted, and a reader that starts from the beginning reads all of it. The examples use `curl` with the stream’s URL in `STREAM` and an API key in `TONBO_API_KEY`, as the [quickstart](https://tonbo.dev/docs/getting-started/quickstarts/stream.md#key) sets them.

## Publish a snapshot

A writer that can summarize the stream up to an offset, such as a count, a state or a compacted log, can publish that summary as a snapshot, so a new reader starts from it instead (see [snapshots](https://ursula.tonbo.io/docs/concepts/snapshots/)). Take the offset from a `Stream-Next-Offset`, which on a JSON stream is always between two messages, and keep it in `OFFSET`:

```shell
curl -sS -i -X PUT "$STREAM/snapshot/$OFFSET" \
  -H "Authorization: Bearer $TONBO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages":3}'
```

It answers `204` with `Stream-Snapshot-Offset` and `Stream-Snapshot-Digest`. The snapshot has its own content type, `application/octet-stream` when none is sent, and replaces the stream’s earlier snapshot without removing anything from the stream. The same snapshot sent again answers `204` with the same digest; another body at the same offset, or a snapshot at an offset before the latest one, answers `409` naming the latest. One request carries at most 8 MiB, so a larger snapshot goes up in parts, staged with `POST` to `…/snapshot/$OFFSET/parts` and published together (see [upload in parts](https://ursula.tonbo.io/docs/api/publish-snapshot/#upload-in-parts)).

## Read the latest snapshot

Read the latest snapshot at its offset, which a [HEAD](https://tonbo.dev/docs/streams/read.md#head) names in `Stream-Snapshot-Offset`:

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

It answers `200` with the snapshot and its content type, and `Stream-Next-Offset`, where the stream’s messages after it start. An offset that is not the latest snapshot’s answers `404`. A `Range` header reads part of it (`206`), as the [read reference](https://ursula.tonbo.io/docs/api/read-snapshot/) describes.

## Retention

Publishing a snapshot keeps the history before it. To stop keeping it, and paying for it, advance the stream’s retention to the snapshot’s offset or before it:

```shell
curl -sS -i -X PUT "$STREAM/retention/$OFFSET" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

It answers `204` with `Stream-Retained-Offset`, the new start of the stream. Retention only moves forward: an offset at or before the current one changes nothing and answers the current one, so sending it again is safe, and an offset past the latest snapshot is refused. From then on, a read before the retained offset, from `-1` included, answers `410` with the earliest readable offset as `Stream-Next-Offset`. A reader that gets it reads the snapshot and continues from there, or bootstraps. The two steps are separate so that a writer can check its snapshot before the history goes (see [advancing retention](https://ursula.tonbo.io/docs/api/publish-snapshot/#advance-retention)). What a stream keeps is what it costs to store (see [the worked example](https://tonbo.dev/docs/streams/pricing.md#fan-out)).

## Bootstrap

Bootstrap is one request that gives a new reader the stream’s current state: its latest snapshot and every message after it.

```shell
curl -sS -i "$STREAM/bootstrap" \
  -H "Authorization: Bearer $TONBO_API_KEY"
```

It answers `200` with a `multipart/mixed` body, the standard HTTP form of one body made of several parts, each with its own headers, which the [bootstrap reference](https://ursula.tonbo.io/docs/api/bootstrap/) describes part by part: first the latest snapshot, or an empty part when there is none, then the messages after it, one part per message on a JSON stream. `Stream-Snapshot-Offset` names the snapshot’s offset (`-1` without one) and `Stream-Next-Offset` where the answer ends. With `Stream-Up-To-Date: true` it reached the end; without it, it stopped short, after 8 MiB or where older messages are only in object storage, so read on from `Stream-Next-Offset` before treating the state as current. Bootstrap never tails: to go live, open a [tail](https://tonbo.dev/docs/streams/read.md#tail) at its `Stream-Next-Offset`.

## When a snapshot request is refused

The table says, for each refusal these requests can get, whether to send them again. [Troubleshooting](https://tonbo.dev/docs/streams/troubleshooting.md) lists every refusal.

| Answer | When | Send again | What to do |
| --- | --- | --- | --- |
| `401` | 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 | Send a valid key, or exchange the key for a new token, as `Authorization: Bearer`. An Agent’s key is always exchanged first (see [A short-lived token](https://tonbo.dev/docs/streams/access.md#token)). |
| `402` `PAYMENT_REQUIRED` | The account’s balance is zero. Appends pause; reads and tails continue. | After a top-up | Add credit on [Usage & billing](https://tonbo.dev/billing), and send the append again ([When the balance runs out](https://tonbo.dev/docs/databases/pricing.md#zero-balance)). |
| `403` | The key is narrowed and does not reach this stream, or is read-only and the request appends. | No | Use a key that reaches the stream, or one that is not read-only (see [Narrow a key to streams](https://tonbo.dev/docs/streams/access.md#narrow)). |
| `404` | 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 | Check it with `tonbo stream show <name>`, create it first, and wait until it is `active`. |
| `404` | A snapshot read names an offset that is not the latest snapshot’s. | At the latest offset | Send `HEAD` and read the snapshot at its `Stream-Snapshot-Offset`. |
| `409` | Another snapshot body at the same offset, or a snapshot at an offset before the latest one. | No | Read the latest snapshot the answer names, and publish after it. |
| `412` | The request carried Stream-Incarnation, and the stream was deleted and created again since. | No | Treat it as a new stream: read it from the start, with the answer’s Stream-Incarnation. |
| `413` | The body is over the request body limit. | No | Split it into several (see [Limits](https://tonbo.dev/docs/streams/limits.md)); a snapshot goes up in parts. |
| `429` | The account sent more requests per second, or held more tails open at once, than its region allows. | After Retry-After | Wait as long as `Retry-After` says, then send it again. |
| `503` | The region cannot take the request, or check an API key sent directly, for a moment. | Unchanged, after Retry-After | Wait as long as `Retry-After` says, then send the same request again; with producer headers, a repeated append applies once. |
