Streams
A stream is an append-only log identified by name. Clients create, append to, read from, and delete streams using standard HTTP methods.
Create a stream (PUT)
curl -i -X PUT -H "Content-Type: text/plain" \
http://localhost:4437/v1/stream/my-stream
Headers:
Content-Type(optional): the stream's content type, fixed at creation. Defaults toapplication/octet-streamif omittedStream-TTL(optional): time-to-live in secondsStream-Expires-At(optional): absolute expiration (ISO 8601)Stream-Closed(optional):"true"to create in closed state
Responses:
201 CreatedwithLocation,Content-Type,Stream-Next-Offset200 OKif the stream already exists with the same configuration (idempotent)409 Conflictif the stream exists with different configuration400 Bad Requestfor invalid TTL or empty Content-Type header
The Content-Type is immutable after creation. Comparison is case-insensitive and ignores charset parameters.
Append data (POST)
curl -i -X POST -H "Content-Type: text/plain" \
-d "hello world" \
http://localhost:4437/v1/stream/my-stream
Headers:
Content-Type(required): must match the stream's content typeStream-Closed(optional):"true"to close the stream (with or without data)
Responses:
204 No ContentwithStream-Next-Offset404 Not Foundif stream does not exist409 Conflictfor content-type mismatch or closed stream400 Bad Requestfor empty body withoutStream-Closed
Each append generates a unique, monotonically increasing offset. Concurrent appends to the same stream are serialized to maintain offset ordering.
Read data (GET)
curl -i http://localhost:4437/v1/stream/my-stream?offset=-1
Query parameters:
offset: where to start reading-1(default): beginning of streamnow: current tail (returns empty body)- hex offset: resume from a specific position
Response headers:
Stream-Next-Offset: save this for resumptionStream-Up-To-Date:"true"when at the tailStream-Closed:"true"when closed and at tailETag: for conditional requests withIf-None-Match
The body contains all messages concatenated from the requested offset to the current tail.
Stream metadata (HEAD)
curl -I http://localhost:4437/v1/stream/my-stream
Returns the same headers as GET (Content-Type, Stream-Next-Offset, Stream-Closed, TTL info) with no body.
Delete a stream (DELETE)
curl -i -X DELETE http://localhost:4437/v1/stream/my-stream
204 No Contenton success404 Not Foundif stream does not exist
Deleting removes all data and metadata. The stream name can be reused.
Close a stream
Closing a stream prevents further appends. Close by sending a POST with Stream-Closed: true:
# Close without data
curl -X POST -H "Content-Type: text/plain" \
-H "Stream-Closed: true" \
http://localhost:4437/v1/stream/my-stream
# Close with a final message
curl -X POST -H "Content-Type: text/plain" \
-H "Stream-Closed: true" \
-d "goodbye" http://localhost:4437/v1/stream/my-stream
After closure:
- Appends return
409 ConflictwithStream-Closed: true - Reads still work;
Stream-Closed: trueappears when the reader is at the tail - Closing is idempotent (re-closing returns 204)
TTL and expiry
Streams can have a time-to-live set at creation:
# Expire in 1 hour
curl -X PUT -H "Content-Type: text/plain" \
-H "Stream-TTL: 3600" \
http://localhost:4437/v1/stream/temp
# Expire at a specific time
curl -X PUT -H "Content-Type: text/plain" \
-H "Stream-Expires-At: 2026-12-31T23:59:59Z" \
http://localhost:4437/v1/stream/temp2
- Only one of
Stream-TTLandStream-Expires-Atmay be provided - Expired streams return 404 on all operations
- Expiration is checked lazily on access
- HEAD returns remaining
Stream-TTLandStream-Expires-At