REST API
Management API
Create and delete App Keys, edit Domain Claims and broker claims, link keys to claims, and pick keys, all through the API.
Everything the console does is available to scripts. These routes are gated by the caller's
Domain Claim: R on the protocol to read, W to change.
| Protocol | Routes |
|---|---|
domain | /domain/keys, /domain/claims |
event | /event/claims, /event/keys |
state | /state/claims, /state/keys |
All paths are below https://api.thuum.dev/api/{domain}.
App Keys#
| Method | Path | Purpose |
|---|---|---|
GET | /domain/keys | List the Domain's App Keys. |
POST | /domain/keys | Create one: { "name", "token_ttl_seconds"? } → 201. |
GET | /domain/keys/{selector} | Read one. |
DELETE | /domain/keys/{selector} | Delete it (204); 409 while it is linked to any claim. |
There is no update: keys are immutable. token_ttl_seconds defaults to 30, at most 86400. An App Key
renders as below; the create response adds key, the plaintext, returned only this once.
{
"selector": "3f9c1a7be04d52aa",
"name": "backend",
"token_ttl_seconds": 300,
"domain_claim_id": null,
"event_claim_id": "8b1d…",
"state_claim_id": null,
"inserted_at": "2026-10-21T10:02:11.000000Z"
}curl -s -X POST https://api.thuum.dev/api/acme/domain/keys \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"name":"backend","token_ttl_seconds":300}'const created = await acme.domain.keys.create({ name: 'backend', token_ttl_seconds: 300 });
saveSecret('THUUM_APP_KEY', created.key); // shown onceCreating a key links nothing: link it through the claims' …/keys/{selector} routes below.
Domain Claims#
| Method | Path | Purpose |
|---|---|---|
GET | /domain/claims | List. |
POST | /domain/claims | Create: { "name", "description"?, "protocols"? } → 201. |
GET | /domain/claims/{id} | Read one. |
PUT | /domain/claims/{id} | Update name and/or description (same as PATCH). |
PATCH | /domain/claims/{id} | Update name and/or description. |
DELETE | /domain/claims/{id} | Delete (204); 409 while keys are linked. |
PUT | /domain/claims/{id}/protocols/{protocol} | Set a protocol's flags: { "flags": 5 }. Returns the claim. |
DELETE | /domain/claims/{id}/protocols/{protocol} | Remove a protocol (204). |
PUT | /domain/claims/{id}/keys/{selector} | Link a key. Returns the claim. |
DELETE | /domain/claims/{id}/keys/{selector} | Unlink a key (204). |
protocol is domain, event or state; flags are W and R only (0, 1, 4, 5). The
protocols map on creation sets the initial rows, e.g. { "domain": 1, "event": 5 }.
import { READ, WRITE, combine } from '@thuum/client';
const ops = await acme.domain.claims.create({
name: 'Operations',
protocols: { event: combine(READ, WRITE), state: READ }
});
await acme.domain.claims.keys.link(ops.id, '3f9c1a7be04d52aa');Event Broker and State Broker claims#
The two kinds have identical routes under /event and /state:
| Method | Path | Purpose |
|---|---|---|
GET | /{broker}/claims | List. |
POST | /{broker}/claims | Create: { "name", "description"?, "topic_flags"?, "fallback_data_flags"?, "topics"? } → 201. |
GET | /{broker}/claims/{id} | Read one. |
PUT | /{broker}/claims/{id} | Update name, description, topic_flags, fallback_data_flags (same as PATCH). |
PATCH | /{broker}/claims/{id} | Update the fields given. |
DELETE | /{broker}/claims/{id} | Delete (204); 409 while keys are linked. |
PUT | /{broker}/claims/{id}/topics/{topic} | Set a Main Topic's data flags: { "data_flags": 11 }. Returns the claim. |
DELETE | /{broker}/claims/{id}/topics/{topic} | Remove it (204): the topic falls back to fallback_data_flags. |
PUT | /{broker}/claims/{id}/keys/{selector} | Link a key. Returns the claim. |
DELETE | /{broker}/claims/{id}/keys/{selector} | Unlink a key (204). |
GET | /{broker}/keys | The key picker: every App Key with its link to this kind of claim. |
topic_flags takes W and R (0, 1, 4, 5); data flags take all four (0–15). {topic}
must be a Main Topic. A claim renders as:
{
"id": "8b1d…",
"name": "Browser chat",
"description": null,
"topic_flags": 0,
"fallback_data_flags": 0,
"topics": { "chat": 11 },
"keys": ["3f9c1a7be04d52aa"],
"inserted_at": "…",
"updated_at": "…"
}The key picker (GET /event/keys, GET /state/keys) needs only R on that protocol, so someone who
manages broker claims can link keys without domain access. Each entry is
{ "selector", "name", "token_ttl_seconds", "event_claim_id" } (or state_claim_id).
CLAIM=$(curl -s -X POST https://api.thuum.dev/api/acme/event/claims \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"name":"Browser chat","topics":{"chat":11}}' | jq -r .data.id)
curl -s -X PUT https://api.thuum.dev/api/acme/event/claims/$CLAIM/keys/3f9c1a7be04d52aa \
-H "authorization: Bearer $TOKEN"
curl -s -X PUT https://api.thuum.dev/api/acme/event/claims/$CLAIM/topics/notifications \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"data_flags":3}'const claim = await acme.eventBroker.claims.create({ name: 'Browser chat', topics: { chat: 11 } });
await acme.eventBroker.claims.keys.link(claim.id, '3f9c1a7be04d52aa');
await acme.eventBroker.claims.topics.put(claim.id, 'notifications', 3);Renewing a key with the API#
The console's Renew is four calls:
const old = await acme.domain.keys.get(oldSelector);
const next = await acme.domain.keys.create({
name: old.name,
token_ttl_seconds: old.token_ttl_seconds
});
if (old.domain_claim_id) await acme.domain.claims.keys.link(old.domain_claim_id, next.selector);
if (old.event_claim_id) await acme.eventBroker.claims.keys.link(old.event_claim_id, next.selector);
if (old.state_claim_id) await acme.stateBroker.claims.keys.link(old.state_claim_id, next.selector);
// Deploy next.key everywhere old.key was used, then retire the old key:
if (old.domain_claim_id) await acme.domain.claims.keys.unlink(old.domain_claim_id, old.selector);
if (old.event_claim_id) await acme.eventBroker.claims.keys.unlink(old.event_claim_id, old.selector);
if (old.state_claim_id) await acme.stateBroker.claims.keys.unlink(old.state_claim_id, old.selector);
await acme.domain.keys.delete(old.selector);