REST API

State Broker REST

Read, list, set, merge, delete and echo states, and configure TTL and size per Main Topic.

All routes are below https://api.thuum.dev/api/{domain}/state. Data routes check the key's State Broker claim data flags on the topic's Main Topic; config routes check its topic flags.

MethodPathFlagPurpose
GET/state/data/{topic}data RList a subtree's states
GET/state/data/{topic}/{key}data RRead one state
PUT/state/data/{topic}/{key}data PSet
PATCH/state/data/{topic}/{key}data PMerge
DELETE/state/data/{topic}/{key}data PDelete
POST/state/data/{topic}/{key}/echodata PEcho
GET/state/topicstopic RList TTL and size configs
GET/state/topics/{topic}topic RA Main Topic's effective config
PUT/state/topics/{topic}topic WSet a Main Topic's config
DELETE/state/topics/{topic}topic WRemove it

A state renders as:

JSON
{
	"topic": "board:42",
	"key": "item:7",
	"value": { "title": "Fix login", "column": "doing" },
	"version": 2,
	"inserted_at": "2026-10-21T13:51:37.599068Z",
	"updated_at": "2026-10-21T13:58:02.112907Z"
}

Every write notifies the WebSocket subscribers of the topic and of its super topics, exactly like the same operation over the WebSocket.

Read a state#

GET /state/data/{topic}/{key} returns one state, addressed exactly (no subtree). 404 if it doesn't exist.

Shell
curl -s https://api.thuum.dev/api/acme/state/data/board:42/item:7 -H "authorization: Bearer $TOKEN"

List states#

GET /state/data/{topic} lists the states of the topic and its sub-topics, ordered by topic, then key (byte order).

QueryMeaning
prefixOnly keys that start with it (literal), in every topic of the subtree.
limitDefault 100, at most 1000 (larger is capped).
afterCursor: the last key of the previous page (exclusive).
after_topicCursor: the last topic of the previous page. Defaults to {topic}; needs after.
Shell
curl -s "https://api.thuum.dev/api/acme/state/data/board:42?prefix=item:&limit=1000" \
  -H "authorization: Bearer $TOKEN"
TypeScript
const all = [];
let cursor: { after_topic?: string; after?: string } = {};
for (;;) {
	const page = await acme.stateBroker.data.list('board:42', { limit: 1000, ...cursor });
	all.push(...page);
	if (page.length < 1000) break;
	const last = page[page.length - 1];
	cursor = { after_topic: last.topic, after: last.key };
}

Set a state#

PUT /state/data/{topic}/{key} with { "value": … } creates or replaces the value (any JSON except null, at most 64 KiB). 201 when created, 200 when replaced; the body is the state.

curl -s -X PUT https://api.thuum.dev/api/acme/state/data/orders:1042/status \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"value":{"stage":"shipped","eta":"2026-10-23"}}'
const response = await fetch(`${API}/api/acme/state/data/orders:1042/status`, {
	method: 'PUT',
	headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
	body: JSON.stringify({ value: { stage: 'shipped', eta: '2026-10-23' } })
});
const { data: state } = await response.json();
const state = await acme.stateBroker.data.set('orders:1042', 'status', {
	stage: 'shipped',
	eta: '2026-10-23'
});

Merge into a state#

PATCH /state/data/{topic}/{key} with { "value": <patch> } applies a JSON Merge Patch. The patch must be an object; null members remove members. Creates the key if missing (201), else 200.

Shell
curl -s -X PATCH https://api.thuum.dev/api/acme/state/data/orders:1042/status \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"value":{"stage":"delivered","eta":null}}'

Delete a state#

DELETE /state/data/{topic}/{key} removes the key: 204, also when it didn't exist. Subscribers get a change with value: null if it existed.

Echo a state#

POST /state/data/{topic}/{key}/echo sends the current value to subscribers again, without writing it (same version). Returns the state; 404 if it doesn't exist.

TTL and size configs#

A config applies to a Main Topic and all its sub-topics. {topic} must be a Main Topic, else 422.

Shell
curl -s -X PUT https://api.thuum.dev/api/acme/state/topics/presence \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"ttl_seconds":120,"max_states":null}'
JSON
{
	"data": {
		"topic": "presence",
		"ttl_seconds": 120,
		"max_states": null,
		"default": false,
		"inserted_at": "…",
		"updated_at": "…"
	}
}
FieldMeaning
ttl_secondsRemove states not written (set or merge) for this long. null: never.
max_statesKeep at most this many states in the family, the most recently written. null: unbounded.
  • PUT replaces the whole config; omitted fields are null.
  • GET /state/topics/{topic} returns the effective config, "default": true when nothing is stored (by default, states never expire and are unbounded).
  • Removals run about once a minute and never notify subscribers.
TypeScript
await acme.stateBroker.topics.put('presence', { ttl_seconds: 120 });