API reference

All endpoints are under the /v1/stream/ base path. The health check is at /healthz.

Health check

GET /healthz

Returns 200 OK with body ok. No authentication required.


Create stream

PUT /v1/stream/{name}
HeaderRequiredDescription
Content-TypenoStream content type (immutable after creation; defaults to application/octet-stream)
Stream-TTLnoTime-to-live in seconds
Stream-Expires-AtnoAbsolute expiration (ISO 8601)
Stream-Closedno"true" to create closed

Body is optional. If provided, it is appended as initial stream data.

StatusMeaning
201 CreatedStream created
200 OKIdempotent create (same config)
409 ConflictStream exists with different config
400 Bad RequestInvalid TTL or empty Content-Type header

Response headers: Location, Content-Type, Stream-Next-Offset


Append data

POST /v1/stream/{name}
HeaderRequiredDescription
Content-TypeyesMust match stream's content type
Stream-Closedno"true" to close (with or without data)
Producer-IDno*Producer identifier
Producer-Epochno*Producer epoch
Producer-Seqno*Producer sequence number

*Producer headers: all three or none.

StatusMeaning
200 OKNew data accepted (producer mode)
204 No ContentData appended (no producer) or duplicate (producer)
400 Bad RequestEmpty body without Stream-Closed, invalid producer headers
403 ForbiddenEpoch fenced (zombie producer)
404 Not FoundStream does not exist
409 ConflictContent-Type mismatch, closed stream, or sequence gap
413 Payload Too LargeMemory limit exceeded

Response headers: Stream-Next-Offset, Stream-Closed (if closed), Producer-Epoch, Producer-Seq


Read data (catch-up)

GET /v1/stream/{name}?offset={offset}
ParameterDefaultDescription
offset-1Start offset. Sentinels: -1 (start), now (tail)
HeaderRequiredDescription
If-None-MatchnoETag from previous read
StatusMeaning
200 OKData returned
304 Not ModifiedETag match (no new data)
400 Bad RequestInvalid offset format
404 Not FoundStream does not exist or expired

Response headers: Content-Type, Stream-Next-Offset, Stream-Up-To-Date, ETag, Cache-Control, Stream-Closed (if closed and at tail)


Read data (long-poll)

GET /v1/stream/{name}?offset={offset}&live=long-poll[&cursor={cursor}]

Same as catch-up, plus:

ParameterDescription
liveMust be "long-poll"
cursorEcho of previous Stream-Cursor (optional)

Additional response header: Stream-Cursor

Returns 204 No Content on timeout or closed stream at tail.


Read data (SSE)

GET /v1/stream/{name}?offset={offset}&live=sse

Returns Content-Type: text/event-stream. See Read modes for event format.

Additional response header: stream-sse-data-encoding: base64 (for binary content types)


Stream metadata

HEAD /v1/stream/{name}

Returns the same headers as GET with no body.

StatusMeaning
200 OKStream exists
404 Not FoundStream does not exist or expired

Response headers: Content-Type, Stream-Next-Offset, Stream-Closed, Stream-TTL, Stream-Expires-At, Cache-Control


Delete stream

DELETE /v1/stream/{name}
StatusMeaning
204 No ContentDeleted
404 Not FoundDoes not exist

Common response headers

These headers appear on all responses via middleware:

HeaderValueNotes
Cache-Controlno-storeno-cache for SSE
X-Content-Type-Optionsnosniff
Cross-Origin-Resource-Policycross-origin