Rules: keys and errors
Last updated 29 October 2026
Check these rules before you hand a key or a token to a program or a web page: each keeps access narrow and errors handled.
The requests keep the stream’s URL in STREAM, a key in TONBO_API_KEY, offsets in NEXT and OFFSET, and the stream’s incarnation in INCARNATION, as Append, Read and tail and Snapshots and retention do.
Exchange an Agent’s key before you send it
A stream takes a key that acts as you directly, but refuses a key that acts as an Agent with 401 and the code exchange_required. Exchange an Agent’s key for a token, which lasts at most 10 minutes, and exchange again before it expires (see A short-lived token).
TOKEN=$(curl -sS -X POST "https://tonbo.dev/api/iam/token" \
-H "Authorization: Bearer $AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"audience":"https://streams.tonbo.dev"}' | jq -r .access_token)
curl -sS -i "$STREAM?offset=-1" \
-H "Authorization: Bearer $TOKEN"curl -sS -i "$STREAM?offset=-1" \
-H "Authorization: Bearer $AGENT_KEY"Give each program a key narrowed to its streams
A key that acts as you and is not narrowed reaches every stream and database you can. Give a reader a read-only key narrowed to its stream, and a family of runs a key narrowed to their prefix (see Narrow a key).
curl -sS -X POST "https://api.tonbo.dev/v1/api-keys" \
-H "Authorization: Bearer $TONBO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"events-reader","read_only":true,"streams":["events"],"expires_in":"15_days"}'Give a web page a token for its streams, never a key
A page should never hold an API key, or a token exchanged from one: either reaches everything the key does. Your server mints the page a read-only token for only the streams it shows, which lasts minutes (see A token for a web page), or reads the stream itself and passes on what the page may see. A page can read and tail a public stream with no token (see Public and private).
Match a refusal’s code, never its message
A refusal’s JSON names a code, such as STREAM_LIFECYCLE, and a message written for people, which may change (see Troubleshooting).
if (body.error.code === "STREAM_LIFECYCLE") createThroughTheApi();if (body.error.message.includes("lifecycle")) createThroughTheApi();