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.

ProtocolRoutes
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#

MethodPathPurpose
GET/domain/keysList the Domain's App Keys.
POST/domain/keysCreate 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.

JSON
{
	"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 once

Creating a key links nothing: link it through the claims' …/keys/{selector} routes below.

Domain Claims#

MethodPathPurpose
GET/domain/claimsList.
POST/domain/claimsCreate: { "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 }.

TypeScript
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:

MethodPathPurpose
GET/{broker}/claimsList.
POST/{broker}/claimsCreate: { "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}/keysThe 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:

JSON
{
	"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:

TypeScript
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);