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

[← Back to the overview](https://ngx-ramblers.org.uk/how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock)

## Tenants

A tenant is either:

- A **group** with a 4-character `groupCode` (e.g. `KT50` for East Kent Walking Group), or
- An **area** with a 2-character `areaCode` (e.g. `KT` for Kent Area)

The kind matters for the API path: groups are queried as `/api/groups/{code}/members`, areas as `/api/areas/{code}/members`.

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/915719e3-2d02-4314-b24a-e32ca99ffef7.png)

*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** — `group` or `area`
- **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](https://ngx-ramblers.org.uk/?contact-us&role=support&subject=Salesforce%20Mock%20-%20Tenant%20transfer%20request&redirect=how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock/tenants-and-tokens).

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.

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/c10cf889-2281-4ee1-9c62-7d288b6103e5.png)

*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](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions), 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 `KT50` cannot read `NS03`; the API returns 401.
- **Per-operator isolation** — every query is filtered by `tenantCode` (data) or `ownerOperator` (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.

## Next

[**Loading member data →**](https://ngx-ramblers.org.uk/how-to/technical-articles/2026-04-21-using-ramblers-salesforce-mock/loading-member-data)