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:
- 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.
curlis a complete client — no SDK, no binary framing, no connection modes. - 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.
- Durability you choose. Decide per topic how much crash-safety
a write buys: best-effort
memory, crash-survivingdisk, or power-loss-prooffsync. 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
POSTevents to and read back by number. The server stamps each event with a monotonicseq; reading is “give me everything afterfrom_seq,” and advancing your storedfrom_seqis 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:4000Create 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
/v0API (topics, batched diff reads, permanent deletes, routers, multiplexed SSE, lease-based queues); the sharded WAL with adaptive group commit (TOPICS_WAL_SHARDS, defaultmin(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_durabledefaults 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
Run the server and write, read, watch, and delete your first events.
QuickstartThe five concepts — Topic, Record, seq, Router, Tombstone — in depth.
Mental ModelThe case for scaling up instead of out, and the honest availability tradeoff.
Why a Single NodeEvery /v0 endpoint, explained at three levels of depth.