Skip to Content
Getting StartedIntroduction

Introduction

topics is a persistent event log you talk to over plain HTTP. You write events to a named topic, the server stamps each one with a monotonic sequence number, and you read them back by asking for “everything after sequence N.” You can follow a topic live, fan it out to other topics, and delete what you no longer need — all from a single process on one machine, the way SQLite is one file instead of a database fleet. The engine still moves millions of events a second.

That’s the whole thing. The rest of this site is detail.

The short version

Three deliberate bets define topics:

  1. A small API. Create a topic, write, read by sequence, watch, delete. A handful of HTTP calls, JSON in and out, IDs assigned for you. curl is a complete client — no SDK, no binary framing, no connection modes.
  2. One node, not a fleet. It runs on a single machine — SQLite’s operating model, with a network port — so you skip the distributed system entirely: no cluster, no consensus, no replication hops. Growing means a bigger machine, not more of them to operate; the price is brief downtime on restarts, which topics makes cheap.
  3. Durability you choose. Decide per topic how much crash-safety a write buys: best-effort memory, crash-surviving disk, or power-loss-proof fsync. Strong where it matters, fast where it doesn’t.

And one rule underneath all of it: data loss is always explicit, never silent — if records you wanted were dropped by a retention cap or TTL, the read hands you the exact range you missed. See Tombstones.

The full /v0 API is implemented and durable: topics, diff reads, deletes, routers, multiplexed SSE, and lease-based queues — backed by a write-ahead log with group commit, segments, snapshots, and crash-recovery replay on a single restartable process. An fsync topic’s writes are fsync-gated: an acknowledged write is committed, and a restart replays the WAL to recover it.

The API in one minute

The model fits in a sentence:

A topic is a log you POST events to and read back by number. The server stamps each event with a monotonic seq; reading is “give me everything after from_seq,” and advancing your stored from_seq is the ack.

Everything else is that idea expanded into a few verbs. Below, an online store records its orders — run the calls top to bottom and you’ve used the whole product. They assume TOPICS points at your server and auth is off on the default loopback bind:

export TOPICS=http://localhost:4000

Create a topic — or skip it; a topic is created lazily on the first write.

curl -X PUT $TOPICS/v0/topics/orders -d '{ "durable": true }'

Write records — the server numbers each one and returns the assigned seqs.

curl -X POST $TOPICS/v0/topics/orders \ -d '{ "records": [ { "data": { "sku": "AEROPRESS-GO", "qty": 1, "total": 3499 }, "tag": "order-7731" } ] }' # → { "seqs": [1], "head_seq": 1, ... }

Read from a cursor — get records after from_seq, plus the next cursor. The seq is the cursor; there’s no opaque token.

curl -X POST $TOPICS/v0/topics/orders/diff -d '{ "from_seq": 0 }' # → { "records": [ { "$seq": 1, "$tag": "order-7731", "data": { "sku": "AEROPRESS-GO", ... } } ], # "next_from_seq": 1, "caught_up": true }

Watch live — create a session, then open its SSE stream to follow one or many topics, woken on append rather than polled.

curl -X POST $TOPICS/v0/watch -d '{ "topics": { "orders": { "tail": true } } }' # → { "wid": "wid_…", "stream_url": "/v0/watch/wid_…" } curl -N $TOPICS/v0/watch/wid_…

Delete — permanently remove records by seq range and/or tag, effective immediately, point-in-time (it never touches records written later).

curl -X POST $TOPICS/v0/topics/orders/delete -d '{ "match": ["tag", "Eq", "order-7731"] }'

Fan out — a router copies every record from one topic into another, server-side, preserving the origin node.

curl -X PUT $TOPICS/v0/routers/orders-to-fulfillment -d '{ "source": "orders", "dest": "fulfillment" }'

That’s the simple level. The same surface scales up into lease-based queues (claim/ack/nack/extend), multiplexed SSE, and per-topic durability and retention — read it at full depth in the API Reference, or follow the Quickstart end to end.

What’s implemented today

topics is honest about maturity. The /v0 contract is fully implemented; a few operational edges are partial or planned.

  • Implemented — the full /v0 API (topics, batched diff reads, permanent deletes, routers, multiplexed SSE, lease-based queues); the sharded WAL with adaptive group commit (TOPICS_WAL_SHARDS, default min(num_cpus, 8), shard-count-agnostic recovery); async + derived router forwarding (off the write/ack path, forwarded copies not WAL-logged so one source append is one WAL write, durable per-router cursor with replay-from-cursor recovery, single-source-per-dest); the four durability classes (ephemeral/memory/disk/fsync); segments, snapshots, and crash recovery; advisory single-writer fencing on the data directory; the per-topic tag index; node loop-prevention; bearer auth with keys hashed at rest, optional per-key scopes and a topic-name prefix allowlist; DoS-hardening resource limits; optional hot→cold segment tiering; and the loopback-default bind.
  • Partial — the cap-vs-TTL tombstone reason is best-effort across a restart (the gap range is always authoritative). Queue lease durability is best-effort (leases_durable defaults off, and a transient WAL error on a lease append degrades to the queue’s baseline at-least-once rather than losing or duplicating work).
  • Out of scope (by design, not on the roadmap) — native TLS (terminate at a TLS-terminating reverse proxy), hard multi-tenant namespace isolation beyond the per-key scope + topic-name-prefix allowlist that ships today, multi-server / replication / HA beyond the single-process data-dir lock, LSM / keyed log compaction, and durable consumer groups as a server primitive (built instead as an app pattern: a topic per consumer + delete).

topics speaks plain HTTP — it does not terminate TLS. For any non-loopback exposure, run it behind a TLS-terminating reverse proxy or bind loopback and tunnel. See Security.

Where to go next

Last updated on