Tenancy
A tenant is one branded deployment: its own name, logo, theme, enabled venues, feature flags, fee schedule, and users. One platform serves many; the API surface is identical for all of them.
How the tenant is resolved
Section titled “How the tenant is resolved”Resolution runs before authentication, in this order:
X-Tenant-Slugheader — required for server-side integrations. It is trusted only when it names a real tenant; an unknown slug is silently ignored rather than rejected, and resolution falls through. A slug naming a suspended tenant still resolves — it is rejected afterwards with410 {"error":"tenant_suspended"}, not skipped.- Origin / Host — matched against the tenant’s registered origins. This is
how a browser front end on a branded domain resolves without a header. When
no
Originis present theHostheader is used instead. - Platform default — only when the request carries neither
OriginnorHost, or when the origin is the platform tenant’s own registered origin.
Anything else is rejected with 404 {"error":"unknown_tenant_origin"}.
That last point is the wall every integrator hits first. Host is mandatory on
every HTTP request, so a server-side call to a shared host like
api.troncharts.xyz never falls through to rule 3 — it presents an origin that
belongs to no tenant and 404s before your credential is even read.
Every server-side call must send X-Tenant-Slug, or come from an origin
registered on your tenant. There is no third option.
curl -s https://api.troncharts.xyz/api/v1/venues \ -H "authorization: Bearer $TOKEN" \ -H "x-tenant-slug: $TC_TENANT_SLUG"Because an unknown slug is ignored rather than rejected, a typo in the slug
gives you the same 404 unknown_tenant_origin as sending no header at all.
A small set of paths skip the gate entirely, because the caller has no tenant to
offer yet: the bearer mint and revoke (/api/auth/api-token,
/api/auth/api-token/revoke), /api/v1/discovery, self-serve signup
(/api/public/tenants/*), the public firm-transparency reads, and the docs
themselves. Everything else — including POST /api/auth/bootstrap and all of
/api/v1/* — is gated.
What the header does and does not do
Section titled “What the header does and does not do”X-Tenant-Slug gets you past the origin gate. It does not choose which
tenant your credential acts as, and it cannot widen a credential.
A bearer token acts under its credential’s own tenant, read from the credential
row on every request; the tenant the request resolved to is deliberately never
consulted for scope. So a bearer that reaches a host belonging to
another tenant still acts as its own tenant — the request is not rejected, the
header is simply not load-bearing for scope. The one case that is rejected
(403) is a token whose pinned claim disagrees with its credential row, which
happens only if the credential was moved between tenants after the token was
minted.
What an operator controls per tenant
Section titled “What an operator controls per tenant”| Area | Controls |
|---|---|
| Brand | Display name, logo, favicon, support email |
| Theme | Colour tokens, typography, radius — light and dark |
| Markets | Which venues and symbols are enabled |
| Features | Per-tenant feature flags |
| Fees | The tenant’s fee schedule |
Read the effective configuration back at any time — useful for mirroring it into your own front end:
curl -s https://api.troncharts.xyz/api/v1/tenants/me \ -H "authorization: Bearer $TOKEN" \ -H "x-tenant-slug: $TC_TENANT_SLUG"# → { id, displayName, branding, theme, flags, venues, plan, … }Sub-resources: /api/v1/tenants/{id}/branding, /theme, /flags, /venues,
/plan. These are read-only unless your credential carries tenant:config.
The isolation rule
Section titled “The isolation rule”Tenant isolation is the load-bearing rule of the platform: every read and every write is filtered by the caller’s tenant, at the query, not in the application layer. This holds for REST and for the WebSocket channels — a socket is scoped to its tenant at handshake and is torn down if that access is revoked mid-connection.
The practical consequence for you: you never have to filter by tenant yourself, and you can treat any row you can see as yours.