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
| Header | Required | Description |
|---|---|---|
Content-Type | No | Stream content type. Defaults to application/octet-stream |
Stream-TTL | No | Time-to-live in seconds |
Stream-Expires-At | No | Absolute expiry (RFC 3339 timestamp) |
Note:
Stream-TTLandStream-Expires-Atare 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.1Host: api.example.comContent-Type: application/jsonHTTP/1.1 201 CreatedLocation: /streams/my-eventsContent-Type: application/jsonStream-Next-Offset: 0000000000000000_0000000000000000Example - With Initial Data
PUT /streams/conversation-123 HTTP/1.1Host: api.example.comContent-Type: application/json
{"role": "system", "content": "You are a helpful assistant."}HTTP/1.1 201 CreatedLocation: /streams/conversation-123Content-Type: application/jsonStream-Next-Offset: 0000000000000000_0000000000000052Example - With TTL
PUT /streams/temp-session HTTP/1.1Host: api.example.comContent-Type: application/jsonStream-TTL: 3600HTTP/1.1 201 CreatedLocation: /streams/temp-sessionStream-TTL: 3600Idempotent Creation
Creating an existing stream with matching configuration succeeds:
PUT /streams/my-events HTTP/1.1Content-Type: application/jsonHTTP/1.1 200 OKContent-Type: application/jsonStream-Next-Offset: 0000000000000000_0000000000000156Creating with different configuration fails:
PUT /streams/my-events HTTP/1.1Content-Type: text/plainHTTP/1.1 409 ConflictContent-Type: application/json
{"error": "Stream exists with different content type"}Response Codes
| Code | Meaning |
|---|---|
201 Created | Stream created successfully |
200 OK | Stream exists with matching config (idempotent) |
204 No Content | Alternative to 200 for idempotent success |
400 Bad Request | Invalid headers (e.g., both TTL and Expires-At) |
409 Conflict | Stream exists with different configuration |
429 Too Many Requests | Rate limit exceeded |
Response Headers
| Header | Description |
|---|---|
Location | Stream URL (on 201) |
Content-Type | Stream’s content type |
Stream-Next-Offset | Tail offset after initial data |
Append to Stream
Appends data to an existing stream.
Request
POST {stream-url}Content-Type: {content-type}
{data}Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | MUST match stream’s content type |
Transfer-Encoding | No | chunked for streaming appends |
Stream-Seq | No | Sequence number for coordination |
Request Body
Data to append. MUST NOT be empty.
Example - Simple Append
POST /streams/my-events HTTP/1.1Host: api.example.comContent-Type: application/json
{"event": "user_login", "user_id": 42}HTTP/1.1 204 No ContentStream-Next-Offset: 0000000000000000_0000000000000198Example - Batch Append (JSON Mode)
For application/json streams, arrays are flattened:
POST /streams/my-events HTTP/1.1Content-Type: application/json
[{"event": "click"}, {"event": "scroll"}, {"event": "click"}]HTTP/1.1 204 No ContentStream-Next-Offset: 0000000000000000_0000000000000267This stores three separate messages, not one array.
Example - Streaming Append
POST /streams/my-events HTTP/1.1Content-Type: application/jsonTransfer-Encoding: chunked
{"event": "start"}{"event": "progress", "percent": 50}{"event": "complete"}Example - With Sequence Number
POST /streams/my-events HTTP/1.1Content-Type: application/jsonStream-Seq: 00000001
{"event": "first"}HTTP/1.1 204 No ContentStream-Next-Offset: 0000000000000000_0000000000000295Attempting to use a lower sequence fails:
POST /streams/my-events HTTP/1.1Content-Type: application/jsonStream-Seq: 00000001
{"event": "duplicate"}HTTP/1.1 409 ConflictContent-Type: application/json
{"error": "Sequence regression: 00000001 <= 00000001"}Response Codes
| Code | Meaning |
|---|---|
204 No Content | Append successful (recommended) |
200 OK | Append successful (alternative) |
400 Bad Request | Empty body, invalid JSON, or empty array |
404 Not Found | Stream does not exist |
405 Method Not Allowed | Append not supported |
409 Conflict | Content type mismatch or sequence regression |
413 Payload Too Large | Body exceeds server limit |
429 Too Many Requests | Rate limit exceeded |
Response Headers
| Header | Description |
|---|---|
Stream-Next-Offset | New 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.1Content-Type: text/plain
Hello WorldHTTP/1.1 409 ConflictContent-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.1Content-Type: application/jsonContent-Length: 0HTTP/1.1 400 Bad RequestContent-Type: application/json
{"error": "Empty request body"}JSON Empty Array Rejection
For JSON streams, empty arrays are rejected:
POST /streams/my-events HTTP/1.1Content-Type: application/json
[]HTTP/1.1 400 Bad RequestContent-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.1Host: api.example.comHTTP/1.1 204 No ContentResponse Codes
| Code | Meaning |
|---|---|
204 No Content | Stream deleted |
404 Not Found | Stream does not exist |
405 Method Not Allowed | Delete 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
- Sequences are opaque strings compared lexicographically
- Each append’s sequence MUST be greater than the last
- Sequence comparison is per-stream (or per-writer, depending on implementation)
- Servers MUST document their sequence scope
Next Steps
- Reading Operations - How to read data
- Offsets - Understanding offset tokens
- Content Types - JSON mode and content handling