Producers

Producer headers enable exactly-once append semantics over HTTP. A producer identifies itself with a stable ID, uses a monotonic epoch for session management, and a monotonic sequence number for per-request deduplication. The server validates state and deduplicates retries automatically.

Producer headers

All three must be provided together (or none):

HeaderFormatDescription
Producer-IDnon-empty stringStable identifier for the producer
Producer-Epochinteger (0 to 2^53-1)Incremented on producer restart
Producer-Seqinteger (0 to 2^53-1)Incremented per request within an epoch

Basic flow

# First message: epoch 0, seq 0
curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 0" \
  -H "Producer-Seq: 0" \
  -d "message 1" \
  http://localhost:4437/v1/stream/my-stream
# → 200 OK (new data accepted)

# Second message: epoch 0, seq 1
curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 0" \
  -H "Producer-Seq: 1" \
  -d "message 2" \
  http://localhost:4437/v1/stream/my-stream
# → 200 OK

Response codes

CodeMeaningWhen
200 OKNew data acceptedseq = lastSeq + 1 (or first append)
204 No ContentDuplicate (already persisted)seq <= lastSeq
400 Bad RequestInvalid headersPartial set, empty ID, non-integer, or epoch bump with seq != 0
403 ForbiddenEpoch fenced (zombie producer)epoch < server's current epoch
409 ConflictSequence gapseq > lastSeq + 1

Duplicate detection

Retrying the same (ID + epoch + seq) is safe and returns 204:

# Retry of seq 0 (already succeeded above)
curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 0" \
  -H "Producer-Seq: 0" \
  -d "message 1" \
  http://localhost:4437/v1/stream/my-stream
# → 204 No Content (duplicate, no data stored)

Epoch fencing (zombie detection)

When a producer restarts, it bumps its epoch and starts seq at 0:

# Producer restarts with epoch 1
curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 1" \
  -H "Producer-Seq: 0" \
  -d "restarted" \
  http://localhost:4437/v1/stream/my-stream
# → 200 OK

After this, the old epoch is fenced. A request from epoch 0 returns 403 Forbidden:

curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 0" \
  -H "Producer-Seq: 2" \
  -d "zombie" \
  http://localhost:4437/v1/stream/my-stream
# → 403 Forbidden (epoch fenced)
# Producer-Epoch: 1 (server's current epoch)

Sequence gaps

If a sequence number is skipped, the server returns 409 Conflict with diagnostic headers:

curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 0" \
  -H "Producer-Seq: 5" \
  -d "skipped" \
  http://localhost:4437/v1/stream/my-stream
# → 409 Conflict
# Producer-Expected-Seq: 1
# Producer-Received-Seq: 5

Multiple producers

Multiple producers can write to the same stream. Each has independent epoch and sequence tracking:

# Producer A writes
curl -X POST -H "Producer-ID: user-alice" \
  -H "Producer-Epoch: 0" -H "Producer-Seq: 0" ...

# Producer B writes
curl -X POST -H "Producer-ID: user-bob" \
  -H "Producer-Epoch: 0" -H "Producer-Seq: 0" ...

Messages are ordered by arrival time, not by producer.

Closing with producer headers

A producer can atomically append and close:

curl -X POST -H "Content-Type: text/plain" \
  -H "Producer-ID: my-producer" \
  -H "Producer-Epoch: 0" \
  -H "Producer-Seq: 2" \
  -H "Stream-Closed: true" \
  -d "final message" \
  http://localhost:4437/v1/stream/my-stream
# → 200 OK, Stream-Closed: true

Retrying the same closing request returns 204 No Content with Stream-Closed: true.