Streams

A stream is an ordered, durable log of batches, identified by a base62 id and bounded by a retention window. This page is the concept in full: identity, the cursor, retention, modes, and the guarantees you can and cannot lean on.

Identity

Every stream is named by a 22-character base62 id, for example cBczepiZSW8GJaCae0xIj7. There is no prefix and no other id format. You may let the server generate the id at creation, or you may generate a base62 id client-side and create the stream with it; supplying your own id makes stream creation itself idempotent.

The human-readable name is metadata for your own convenience. It is not unique across a project by default and is never used to address a stream.

The cursor

A stream's cursor is the sequence number of the newest batch it has accepted. It starts at 0 on an empty stream and only ever moves forward. Subscribers each track their own position, also called a cursor, which is simply the sequence of the last batch they processed. "Resume from cursor N" means "give me everything after sequence N".

GET /v1/streams/{id}/cursor
{"cursor":41823,"updated_at":"2026-08-04T11:02:13.418Z"}

Retention

A stream keeps batches until they fall outside either of two bounds, whichever is hit first:

  • retention_seconds - age. A batch older than this is eligible for eviction. Range 60 to 2,592,000 seconds (one minute to thirty days); values outside the range are rejected with retention_out_of_range.
  • max_bytes - total stored size. When the sum of batch sizes would exceed this, the oldest batches are evicted until it fits. Default 256 MiB.

Eviction is from the tail (oldest first) and is the only way batches leave a stream other than deleting the stream. Eviction advances the effective floor but never the cursor: sequence numbers of surviving batches do not change. A subscriber that resumes from a cursor older than the floor receives the oldest surviving batch next, and the gap shows up as a jump in sequence numbers, which is your signal that it fell behind the window.

Sizing tip

Pick retention from your worst realistic subscriber outage, not your average. If a consumer can be down for two hours during a deploy, an hour of retention will lose data; give it a comfortable margin. There is a worked calculation on the blog under Sizing ring buffers.

json and raw modes

A stream is created in one of two modes, fixed for its lifetime:

ModePublish bodyDelivered asUse for
json (default){"events":[...]}JSON batch with an events arrayStructured domain events
rawapplication/octet-stream bytesBase64 in the SSE data field, or verbatim bytes on replayprotobuf, msgpack, Avro, your own framing

In raw mode Runnev never parses your payload. See Publishing for both paths.

Lifecycle: create, inspect, delete

Create

POST /v1/streams
curl https://runnev.dev/v1/streams \
  -H "Authorization: Bearer $RUNNEV_API_KEY" \
  -d '{"name":"orders-eu","retention_seconds":86400,"mode":"json"}'

Only name is required. A duplicate name is allowed; if you want name uniqueness, enforce it on your side or generate the id yourself. Reusing an id you already created returns 409 with stream_name_taken.

Inspect

A plain GET on a stream (no Accept: text/event-stream) returns its metadata:

GET /v1/streams/{id}
{
  "id": "cBczepiZSW8GJaCae0xIj7",
  "name": "orders-eu",
  "created_at": "2026-06-11T09:22:31Z",
  "retention_seconds": 86400,
  "max_bytes": 268435456,
  "cursor": 41823,
  "bytes_stored": 118293011,
  "mode": "json"
}

List all streams in the project with GET /v1/streams, which paginates with ?limit= and ?cursor= (a pagination token, unrelated to a stream's sequence cursor).

Delete

DELETE /v1/streams/{id}
curl -X DELETE https://runnev.dev/v1/streams/$STREAM \
  -H "Authorization: Bearer $RUNNEV_API_KEY"
# 204 No Content

Deletion is immediate and permanent. Open subscribers receive a terminal event: end and the connection closes.

Guarantees

What you can rely on:

  • Order. Within a stream, batches are delivered in sequence order.
  • Durability within retention. An accepted batch is readable by any subscriber until it is evicted by the retention rules.
  • At-least-once delivery. A subscriber that resumes correctly from its cursor sees every surviving batch at least once. A reconnect around a network blip may redeliver the last batch; dedup on seq.
  • Idempotent writes. Publishing the same (stream, seq) twice stores it once.

What is explicitly not guaranteed:

  • No exactly-once. See at-least-once above.
  • No per-consumer acknowledgement or redelivery. There is no server-side notion of a consumer having processed a batch.
  • No cross-stream ordering. Two streams have no defined relative order. If you need a global order, use one stream.
  • No dead-letter handling. A batch you cannot process is your concern; Runnev does not quarantine it.