Snapshots and retention

Last updated 29 October 2026

View .md

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 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). Take the offset from a Stream-Next-Offset, which on a JSON stream is always between two messages, and keep it in OFFSET:

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).

Read the latest snapshot

Read the latest snapshot at its offset, which a HEAD names in Stream-Snapshot-Offset:

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 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:

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). What a stream keeps is what it costs to store (see the worked example).

Bootstrap

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

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 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 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 lists every refusal.

AnswerWhenSend againWhat to do
401The 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 tokenSend 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).
402 PAYMENT_REQUIREDThe account’s balance is zero. Appends pause; reads and tails continue.After a top-upAdd credit on Usage & billing, and send the append again (When the balance runs out).
403The key is narrowed and does not reach this stream, or is read-only and the request appends.NoUse a key that reaches the stream, or one that is not read-only (see Narrow a key to streams).
404The 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 activeCheck it with tonbo stream show <name>, create it first, and wait until it is active.
404A snapshot read names an offset that is not the latest snapshot’s.At the latest offsetSend HEAD and read the snapshot at its Stream-Snapshot-Offset.
409Another snapshot body at the same offset, or a snapshot at an offset before the latest one.NoRead the latest snapshot the answer names, and publish after it.
412The request carried Stream-Incarnation, and the stream was deleted and created again since.NoTreat it as a new stream: read it from the start, with the answer’s Stream-Incarnation.
413The body is over the request body limit.NoSplit it into several (see Limits); a snapshot goes up in parts.
429The account sent more requests per second, or held more tails open at once, than its region allows.After Retry-AfterWait as long as Retry-After says, then send it again.
503The region cannot take the request, or check an API key sent directly, for a moment.Unchanged, after Retry-AfterWait as long as Retry-After says, then send the same request again; with producer headers, a repeated append applies once.