21-Apr-2026 — Tenants and API tokens
A tenant is the scope your data lives in. An API token authorises calls against exactly one tenant. You will need at least one of each before the public API will return anything.
Tenants
A tenant is either:
- A group with a 4-character
groupCode(e.g.KT50for East Kent Walking Group), or - An area with a 2-character
areaCode(e.g.KTfor Kent Area)
The kind matters for the API path: groups are queried as /api/groups/{code}/members, areas as /api/areas/{code}/members.

The dashboard sidebar lists every tenant you own. Click + New to add one, or click an existing code (KT, KT06) to open its detail pane.
Create a tenant
In the dashboard sidebar, click + New next to "Tenants" and fill in:
- Code — 2 to 6 alphanumeric characters (
KT50,KT,NS03) - Kind —
grouporarea - Display name — human-readable, optional (e.g.
East Kent Walking Group)
Hit Create. The tenant appears in the sidebar; click it to open the detail pane, which has three tabs: Members, Generate, and Tokens.
Tenant codes are globally unique
Tenant codes are unique across the whole mock server, not just per operator. If KT50 has already been claimed by someone else, you'll get a 409 conflict on create. Pick a different code, or request a transfer.
This is intentional: the public API path /api/groups/{code}/members would otherwise be ambiguous, and downstream consumers wouldn't know whose dataset they were calling against.
API tokens
Open the tenant's Tokens tab. Each token authorises calls against this tenant only — there is no concept of a multi-tenant or operator-wide token.

The Tokens tab inside a tenant's detail pane. Type a label, hit Generate token, and copy the revealed plaintext immediately — it's only shown once.
Generate a token
Type a human-readable label (the input placeholder shows MailMan - NS03 dev as one example; NGX production sync and James local laptop are equally fine — the label is just for your own bookkeeping) and hit Generate token.
A panel reveals the full token string with a Copy button. Copy it now. The plaintext is shown exactly once; on the next page load all you'll see is the 16-character prefix in the table below. The server stores only a SHA-256 hash, so nobody — not even the root operator — can retrieve the original after generation.
Token format
rsm_<tenantCode>_<48-hex-random>
The tenant code is baked into the plaintext. That makes the prefix self-identifying: skim a log, see rsm_KT50_…, and you immediately know which tenant the request was scoped to. The 48-hex suffix is generated from crypto.randomBytes(24), so the keyspace is 192 bits.
Where to store it
A password manager, a GitHub Actions secret, a Fly.io secret, or whatever your team uses — anywhere except a checked-in file.
Revoke a token
In the same Tokens table, every non-revoked row has a Revoke button. Revocation is immediate and one-way: the token row stays in the table for audit, but any call presenting that token will fail with 401.
If you lose a token, revoke it and generate a new one. There is no recovery path.
Security model summary
- Tokens are shown in plaintext only once, when generated. Server stores a SHA-256 hash.
- Tokens are scoped to exactly one tenant. A token for
KT50cannot readNS03; the API returns 401. - Per-operator isolation — every query is filtered by
tenantCode(data) orownerOperator(admin). Operators cannot see each other's tenants, tokens, members, or consent events. - Pre-production only. Do not put real production member data in here. Synthetic data for demos; real Insight Hub exports only for focused integration testing into a clean tenant.