Skip to content

Reading Operations

This page documents all operations for reading data from streams. The protocol supports three read modes: catch-up, long-poll, and Server-Sent Events (SSE).

Catch-Up Read

Returns immediately with available data. Used for replaying historical data or catching up after disconnection.

Request

GET {stream-url}?offset={offset}

Parameters

ParameterRequiredDescription
offsetNoStarting position. Defaults to stream start (-1)

Example

GET /streams/events?offset=abc123 HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Content-Type: application/json
Stream-Next-Offset: def456
Stream-Up-To-Date: true
[{"event": "click", "x": 100}, {"event": "scroll", "y": 200}]

Response Codes

CodeMeaning
200 OKData returned (or empty if at end)
400 Bad RequestInvalid offset format
404 Not FoundStream does not exist
410 GoneOffset is before retention window
429 Too Many RequestsRate limit exceeded

Response Headers

HeaderDescription
Content-TypeStream’s content type
Stream-Next-OffsetOffset for next read
Stream-Up-To-DatePresent when at end of stream
Stream-CursorOptional cursor for CDN collapsing
ETagEntity tag for cache validation
Cache-ControlCaching directives

Behavior

When offset equals or exceeds the tail offset:

  • Response body is empty (or [] for JSON mode)
  • Stream-Up-To-Date: true header is present
  • Stream-Next-Offset equals the requested offset

When offset is before the tail:

  • Response contains data from offset to tail (or server-defined chunk limit)
  • Stream-Up-To-Date is present only if all data was returned

Long-Poll Read

Waits for new data if none is immediately available. Enables efficient live tailing without constant polling.

Request

GET {stream-url}?offset={offset}&live=long-poll[&cursor={cursor}]

Parameters

ParameterRequiredDescription
offsetYesStarting position
liveYesMust be long-poll
cursorNoEcho of previous Stream-Cursor

Example - Data Available

GET /streams/events?offset=def456&live=long-poll HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Content-Type: application/json
Stream-Next-Offset: ghi789
Stream-Cursor: c_12345
[{"event": "new-data"}]

Example - Timeout

GET /streams/events?offset=ghi789&live=long-poll&cursor=c_12345 HTTP/1.1
Host: api.example.com
HTTP/1.1 204 No Content
Stream-Next-Offset: ghi789

Response Codes

CodeMeaning
200 OKNew data arrived within timeout
204 No ContentTimeout expired with no new data
400 Bad RequestInvalid parameters
404 Not FoundStream does not exist
429 Too Many RequestsRate limit exceeded

Timeout Behavior

  • Server defines the timeout duration (typically 30 seconds)
  • Returns 204 No Content if no data arrives before timeout
  • Client should immediately retry with the same offset
  • Servers MAY accept a timeout parameter (in seconds) as an extension

CDN Collapsing

The cursor parameter enables CDN request collapsing:

┌─────────┐
│ Client1 │──┐
└─────────┘ │ GET ?offset=X&live=long-poll&cursor=Y
┌─────────┐ │ ┌─────┐ ┌────────┐
│ Client2 │──┼────▶│ CDN │───────▶│ Origin │
└─────────┘ │ └─────┘ └────────┘
│ │
┌─────────┐ │ │ Single upstream request
│ Client3 │──┘ │ Response fanned out
└─────────┘ ▼

Clients SHOULD echo the Stream-Cursor from the previous response.

SSE (Server-Sent Events)

Maintains an open connection for real-time streaming. Best for browser clients.

Request

GET {stream-url}?offset={offset}&live=sse

Parameters

ParameterRequiredDescription
offsetYesStarting position
liveYesMust be sse

Content Type Restriction

SSE mode REQUIRES the stream to have one of these content types:

  • text/* (any text type)
  • application/json

Binary streams (application/octet-stream) cannot use SSE mode.

Example

GET /streams/events?offset=abc123&live=sse HTTP/1.1
Host: api.example.com
Accept: text/event-stream
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
event: data
data: {"event": "click"}
event: control
data: {"streamNextOffset": "def456"}
event: data
data: {"event": "scroll"}
event: control
data: {"streamNextOffset": "ghi789"}

Event Types

EventDescription
dataStream content
controlMetadata (offset, cursor)

Control Event Format

{
"streamNextOffset": "abc123",
"streamCursor": "c_12345"
}
FieldRequiredDescription
streamNextOffsetYesOffset for resumption
streamCursorNoCDN collapsing cursor

Connection Lifecycle

Servers SHOULD close SSE connections approximately every 60 seconds:

┌────────┐ ┌────────┐
│ Client │ │ Server │
└───┬────┘ └───┬────┘
│ │
│ GET ?offset=X&live=sse │
│──────────────────────────────────────▶│
│ │
│ event: data ... │
│◀──────────────────────────────────────│
│ event: control {offset: Y} │
│◀──────────────────────────────────────│
│ │
│ ... ~60 seconds of streaming ... │
│ │
│ Connection closed │
│◀──────────────────────────────────────│
│ │
│ GET ?offset=Y&live=sse │
│──────────────────────────────────────▶│
│ │

Clients MUST reconnect using the last streamNextOffset received.

JSON Batching

For application/json streams, servers MAY batch multiple messages:

event: data
data: [
data: {"event": "a"},
data: {"event": "b"},
data: ]
event: control
data: {"streamNextOffset": "xyz789"}

Response Codes

CodeMeaning
200 OKStreaming response
400 Bad RequestContent type incompatible with SSE
404 Not FoundStream does not exist
429 Too Many RequestsRate limit exceeded

Read Flow Diagram

Typical client read flow:

┌─────────────────────────────────────────────────────────┐
│ Start Read │
└───────────────────────────┬─────────────────────────────┘
┌─────────────────────────────┐
│ GET ?offset=X (catch-up) │
└──────────────┬──────────────┘
┌─────────────────────────────┐
│ Stream-Up-To-Date? │
└──────────────┬──────────────┘
┌────────────────┴────────────────┐
│ No │ Yes
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Continue catch-up │ │ Switch to live mode │
│ with next offset │ │ (long-poll or SSE) │
└───────────────────────┘ └───────────────────────┘

Choosing a Read Mode

ModeBest ForTrade-offs
Catch-upHistorical replay, initial syncNo live updates
Long-pollCLI clients, simple implementationsRequest per batch of data
SSEBrowser apps, real-time UIsRequires text/JSON content

Next Steps