Skip to content

Stream Model

A stream is an append-only sequence of bytes with durability guarantees. This page describes the stream model in detail.

Properties

Every stream has the following properties:

PropertyTypeDescription
URLstringUnique identifier for the stream
Content-TypestringMIME type of stream data
Current OffsetstringPosition after last byte (tail)
Created AttimestampWhen the stream was created
TTLsecondsOptional time-to-live
Expires AttimestampOptional absolute expiry

Durability Guarantees

Once a write is acknowledged (server returns success), the data is durable:

┌────────┐ ┌────────┐ ┌─────────┐
│ Client │ │ Server │ │ Storage │
└───┬────┘ └───┬────┘ └────┬────┘
│ │ │
│ POST (data) │ │
│──────────────────▶│ │
│ │ write + fsync │
│ │───────────────────▶│
│ │ │
│ │ ack │
│ │◀───────────────────│
│ │ │
│ 204 No Content │ │
│◀──────────────────│ │
│ │ │
▼ ▼ ▼
Data is now durable - survives server restart

Durability Semantics

  • Write Acknowledgment: Server MUST only return success after data is durable
  • Read Consistency: Any data returned by a read MUST be durable
  • Ordering: Writes are strictly ordered by offset
  • Atomicity: Each append is atomic - either fully written or not at all

Immutability

Streams are immutable by position:

Offset: 0 10 20 30 40 50
│ │ │ │ │ │
Stream: ───┴────┴────┴────┴────┴────┴───▶
▲ ▲ ▲ ▲
│ │ │ │
Message boundaries (never change)
  • Data at a given offset MUST NOT change after being written
  • New data can only be appended to the end
  • The only mutation allowed is deletion of the entire stream

Stream Lifecycle

Creation

Streams are created with a PUT request:

PUT /streams/my-stream HTTP/1.1
Host: api.example.com
Content-Type: application/json
Stream-TTL: 86400
{"initial": "data"}
HTTP/1.1 201 Created
Location: /streams/my-stream
Content-Type: application/json
Stream-Next-Offset: 0000000000000017

Idempotent Creation

Creating an existing stream with matching configuration returns success:

PUT /streams/my-stream HTTP/1.1
Content-Type: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Stream-Next-Offset: 0000000000000042

Creating with different configuration returns conflict:

PUT /streams/my-stream HTTP/1.1
Content-Type: text/plain
HTTP/1.1 409 Conflict

Active State

Active streams accept reads and writes:

POST /streams/my-stream HTTP/1.1
Content-Type: application/json
{"event": "update"}
HTTP/1.1 204 No Content
Stream-Next-Offset: 0000000000000059

Deletion

Streams are deleted with a DELETE request:

DELETE /streams/my-stream HTTP/1.1
HTTP/1.1 204 No Content

After deletion:

  • Reads return 404 Not Found
  • The URL SHOULD NOT be reused for new streams

Expiration

Streams with TTL or expiry are automatically deleted:

PUT /streams/temporary HTTP/1.1
Stream-TTL: 3600

After 3600 seconds:

  • Stream is automatically deleted
  • Reads return 404 Not Found

Retention

Servers MAY implement retention policies that drop data older than a certain age while keeping the stream active:

Full Stream:
├────────────────────────────────────────────┤
│ Old data │ Retained data │
├───────────────┼────────────────────────────┤
Dropped Still readable
Reading dropped offsets returns:
HTTP/1.1 410 Gone

Retention differs from expiration:

  • Retention: Drops old data, stream continues
  • Expiration: Deletes entire stream

Metadata

Stream metadata is available via HEAD request:

HEAD /streams/my-stream HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
Stream-Next-Offset: 0000000000000059
Stream-TTL: 3542
Cache-Control: no-store

Available Metadata

HeaderDescription
Content-TypeStream’s content type
Stream-Next-OffsetCurrent tail offset
Stream-TTLRemaining seconds until expiry
Stream-Expires-AtAbsolute expiry time (RFC 3339)

Independent Read/Write Implementation

Servers MAY implement read and write paths independently:

ImplementationReadsWritesExample Use Case
FullYesYesGeneral-purpose streaming
Read-onlyYesNoDatabase change feeds
Write-onlyNoYesLog aggregation

For read-only streams, POST returns:

HTTP/1.1 405 Method Not Allowed

For write-only streams, GET returns:

HTTP/1.1 405 Method Not Allowed

Next Steps