Skip to content

Writing Operations

This page documents all operations for creating streams and writing data. The protocol provides three write operations: create, append, and delete.

Create Stream

Creates a new stream at the specified URL.

Request

PUT {stream-url}
Content-Type: {content-type}

Headers

HeaderRequiredDescription
Content-TypeNoStream content type. Defaults to application/octet-stream
Stream-TTLNoTime-to-live in seconds
Stream-Expires-AtNoAbsolute expiry (RFC 3339 timestamp)

Note: Stream-TTL and Stream-Expires-At are mutually exclusive. Servers SHOULD reject requests with both.

Request Body

Optional initial data for the stream.

Example - Basic Creation

PUT /streams/my-events HTTP/1.1
Host: api.example.com
Content-Type: application/json
HTTP/1.1 201 Created
Location: /streams/my-events
Content-Type: application/json
Stream-Next-Offset: 0000000000000000_0000000000000000

Example - With Initial Data

PUT /streams/conversation-123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"role": "system", "content": "You are a helpful assistant."}
HTTP/1.1 201 Created
Location: /streams/conversation-123
Content-Type: application/json
Stream-Next-Offset: 0000000000000000_0000000000000052

Example - With TTL

PUT /streams/temp-session HTTP/1.1
Host: api.example.com
Content-Type: application/json
Stream-TTL: 3600
HTTP/1.1 201 Created
Location: /streams/temp-session
Stream-TTL: 3600

Idempotent Creation

Creating an existing stream with matching configuration succeeds:

PUT /streams/my-events HTTP/1.1
Content-Type: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Stream-Next-Offset: 0000000000000000_0000000000000156

Creating with different configuration fails:

PUT /streams/my-events HTTP/1.1
Content-Type: text/plain
HTTP/1.1 409 Conflict
Content-Type: application/json
{"error": "Stream exists with different content type"}

Response Codes

CodeMeaning
201 CreatedStream created successfully
200 OKStream exists with matching config (idempotent)
204 No ContentAlternative to 200 for idempotent success
400 Bad RequestInvalid headers (e.g., both TTL and Expires-At)
409 ConflictStream exists with different configuration
429 Too Many RequestsRate limit exceeded

Response Headers

HeaderDescription
LocationStream URL (on 201)
Content-TypeStream’s content type
Stream-Next-OffsetTail offset after initial data

Append to Stream

Appends data to an existing stream.

Request

POST {stream-url}
Content-Type: {content-type}
{data}

Headers

HeaderRequiredDescription
Content-TypeYesMUST match stream’s content type
Transfer-EncodingNochunked for streaming appends
Stream-SeqNoSequence number for coordination

Request Body

Data to append. MUST NOT be empty.

Example - Simple Append

POST /streams/my-events HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"event": "user_login", "user_id": 42}
HTTP/1.1 204 No Content
Stream-Next-Offset: 0000000000000000_0000000000000198

Example - Batch Append (JSON Mode)

For application/json streams, arrays are flattened:

POST /streams/my-events HTTP/1.1
Content-Type: application/json
[{"event": "click"}, {"event": "scroll"}, {"event": "click"}]
HTTP/1.1 204 No Content
Stream-Next-Offset: 0000000000000000_0000000000000267

This stores three separate messages, not one array.

Example - Streaming Append

POST /streams/my-events HTTP/1.1
Content-Type: application/json
Transfer-Encoding: chunked
{"event": "start"}
{"event": "progress", "percent": 50}
{"event": "complete"}

Example - With Sequence Number

POST /streams/my-events HTTP/1.1
Content-Type: application/json
Stream-Seq: 00000001
{"event": "first"}
HTTP/1.1 204 No Content
Stream-Next-Offset: 0000000000000000_0000000000000295

Attempting to use a lower sequence fails:

POST /streams/my-events HTTP/1.1
Content-Type: application/json
Stream-Seq: 00000001
{"event": "duplicate"}
HTTP/1.1 409 Conflict
Content-Type: application/json
{"error": "Sequence regression: 00000001 <= 00000001"}

Response Codes

CodeMeaning
204 No ContentAppend successful (recommended)
200 OKAppend successful (alternative)
400 Bad RequestEmpty body, invalid JSON, or empty array
404 Not FoundStream does not exist
405 Method Not AllowedAppend not supported
409 ConflictContent type mismatch or sequence regression
413 Payload Too LargeBody exceeds server limit
429 Too Many RequestsRate limit exceeded

Response Headers

HeaderDescription
Stream-Next-OffsetNew tail offset after append

Content Type Matching

The Content-Type header MUST match the stream’s configured type:

# Stream created with Content-Type: application/json
POST /streams/my-events HTTP/1.1
Content-Type: text/plain
Hello World
HTTP/1.1 409 Conflict
Content-Type: application/json
{"error": "Content type mismatch: expected application/json, got text/plain"}

Empty Body Rejection

Empty appends are rejected:

POST /streams/my-events HTTP/1.1
Content-Type: application/json
Content-Length: 0
HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error": "Empty request body"}

JSON Empty Array Rejection

For JSON streams, empty arrays are rejected:

POST /streams/my-events HTTP/1.1
Content-Type: application/json
[]
HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error": "Empty JSON array"}

Delete Stream

Deletes a stream and all its data.

Request

DELETE {stream-url}

Example

DELETE /streams/my-events HTTP/1.1
Host: api.example.com
HTTP/1.1 204 No Content

Response Codes

CodeMeaning
204 No ContentStream deleted
404 Not FoundStream does not exist
405 Method Not AllowedDelete not supported

Post-Deletion Behavior

After deletion:

  • Reads return 404 Not Found
  • The URL SHOULD NOT be reused
  • In-flight SSE connections receive no more data

Writer Coordination

The Stream-Seq header enables multi-writer coordination:

┌──────────┐ ┌────────┐ ┌──────────┐
│ Writer A │ │ Server │ │ Writer B │
└────┬─────┘ └───┬────┘ └────┬─────┘
│ │ │
│ POST seq=001 │ │
│───────────────────▶│ │
│ │ │
│ 204 OK │ │
│◀───────────────────│ │
│ │ │
│ │ POST seq=001 │
│ │◀───────────────────│
│ │ │
│ │ 409 Conflict │
│ │───────────────────▶│
│ │ │
│ │ POST seq=002 │
│ │◀───────────────────│
│ │ │
│ │ 204 OK │
│ │───────────────────▶│

Sequence Rules

  1. Sequences are opaque strings compared lexicographically
  2. Each append’s sequence MUST be greater than the last
  3. Sequence comparison is per-stream (or per-writer, depending on implementation)
  4. Servers MUST document their sequence scope

Next Steps