Getting started
Quickstart
Get a token, publish and receive an event, then set and read a state. Five minutes, with curl, Node.js or the TypeScript client.
This walkthrough uses one App Key from a terminal. In a real app the key stays on your server and browsers get tokens from it: see Connect browsers securely once you are done.
Before you start#
You need:
- your Domain (its slug, e.g.
acme), and - an App Key whose claims grant
P(publish),S(subscribe) andR(read) on the topicdemo, in both the Event Broker and the State Broker.
If you only have your owner key, follow Set up your Domain first: it takes two minutes in the console. Then export the key:
export THUUM_APP_KEY=<your 80-character App Key>Node.js examples need Node.js 22 or later (built-in fetch and WebSocket) and run as ES modules
(save them as .mjs).
1. Exchange the App Key for a token#
An App Key is never sent on data requests. You trade it for a short-lived token, then present the token as a Bearer credential.
TOKEN=$(curl -s -X POST https://api.thuum.dev/api/acme/token \
-H 'content-type: application/json' \
-d "{\"key\":\"$THUUM_APP_KEY\"}" | jq -r .data.token)
echo "$TOKEN"const API = 'https://api.thuum.dev';
const response = await fetch(`${API}/api/acme/token`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ key: process.env.THUUM_APP_KEY })
});
const { data } = await response.json();
console.log(data); // { token: 'eyJ…', token_type: 'Bearer', expires_in: 30 }import { ThuumClient } from '@thuum/client';
const thuum = new ThuumClient({ baseUrl: 'https://api.thuum.dev' });
const { token, expires_in } = await thuum.exchangeToken('acme', process.env.THUUM_APP_KEY!);
const acme = thuum.domain('acme', token);The token lives as long as the key's token TTL (30 seconds by default). Get a new one when it expires; there is no refresh token.
2. Publish an event#
Publish an event of type hello on the topic demo. The data can be any JSON value except null.
curl -s -X POST https://api.thuum.dev/api/acme/event/data/demo \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"type":"hello","data":{"text":"Hello, Thuum!"}}'const published = await fetch(`${API}/api/acme/event/data/demo`, {
method: 'POST',
headers: {
authorization: `Bearer ${data.token}`,
'content-type': 'application/json'
},
body: JSON.stringify({ type: 'hello', data: { text: 'Hello, Thuum!' } })
});
console.log(await published.json());const event = await acme.eventBroker.data.publish('demo', {
type: 'hello',
data: { text: 'Hello, Thuum!' }
});The response (201 Created) is the stored event, with a server-generated id and ts (epoch
milliseconds):
{
"data": {
"id": "0d9a7c1e-5b0f-4d8e-9a3f-2f6c0b1d7e44",
"topic": "demo",
"type": "hello",
"data": { "text": "Hello, Thuum!" },
"ts": 1792590697447
}
}Read it back from the topic's history, newest first:
curl -s "https://api.thuum.dev/api/acme/event/data/demo?limit=10" -H "authorization: Bearer $TOKEN"3. Receive events live#
Open a WebSocket, subscribe to demo, and leave it running. Every message on the socket is a JSON
array of operations; each operation you send carries a ref that comes back on its reply.
// listen.mjs: run it, then publish again from another terminal (step 2).
const API = 'https://api.thuum.dev';
const exchange = await fetch(`${API}/api/acme/token`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ key: process.env.THUUM_APP_KEY })
});
const {
data: { token }
} = await exchange.json();
const ws = new WebSocket(
`wss://api.thuum.dev/api/acme/event/connect?token=${encodeURIComponent(token)}`
);
ws.addEventListener('open', () => {
ws.send(JSON.stringify([{ op: 'sub', topic: 'demo', ref: 'sub-1' }]));
});
ws.addEventListener('message', (message) => {
for (const op of JSON.parse(message.data)) console.log(op);
});const socket = acme.eventBroker.connect();
socket.onMessage((event) => console.log(event.topic, event.type, event.data));
await socket.ready;
await socket.sub('demo');You will see the subscription confirmed, then one message per event published on demo — or on
any of its sub-topics, such as demo:lobby:
[{ "op": "sub:ok", "topic": "demo", "ref": "sub-1" }]
[{ "op": "message", "id": "…", "topic": "demo", "type": "hello", "data": { "text": "Hello, Thuum!" }, "ts": 1792590712004 }]4. Set and read a state#
States are JSON values stored under a key in a topic. Set one, patch it, and read it back:
curl -s -X PUT https://api.thuum.dev/api/acme/state/data/demo/scoreboard \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"value":{"home":0,"away":0,"period":"1st"}}'
curl -s -X PATCH https://api.thuum.dev/api/acme/state/data/demo/scoreboard \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"value":{"home":1}}'
curl -s https://api.thuum.dev/api/acme/state/data/demo/scoreboard -H "authorization: Bearer $TOKEN"await acme.stateBroker.data.set('demo', 'scoreboard', { home: 0, away: 0, period: '1st' });
await acme.stateBroker.data.merge('demo', 'scoreboard', { home: 1 });
const state = await acme.stateBroker.data.get('demo', 'scoreboard');
// { topic: 'demo', key: 'scoreboard', value: { home: 1, away: 0, period: '1st' }, version: 2, … }Every write bumps the state's version and notifies subscribers of the topic. To watch them, connect
to the State Broker the same way as in step 3, at /api/acme/state/connect, and send
{ "op": "sub", "topic": "demo" }: each change arrives as { "op": "change", "key", "value", "version", … }.
Next steps#
- Try every operation interactively in the Playground.
- Set up your Domain with separate keys for your backend and your browsers.
- Learn how topics fan out, and how claims decide who may do what.