Scopes & tiers
Two orthogonal gates sit on every call. Scopes decide which surfaces you can reach. Tiers decide how far you can write on the trading surface. A credential carries both.
Scopes
Section titled “Scopes”| Scope | Unlocks |
|---|---|
firm:operate |
Operate the firm: create, configure, deploy, go live, pause, and approve payouts. /api/v1/firms/*. |
prop:manage |
Manage accounts inside a firm: trader identities, challenge templates, risk profiles, trading groups, provisioning, resets, promotions, and the account interventions. /api/v1/users/*, /api/v1/prop-templates/*, /api/v1/risk-profiles/*, /api/v1/trading-groups/*, /api/v1/accounts/create-prop, POST /api/v1/prop-accounts/{id}/reset, POST /api/v1/prop-accounts/{id}/promote, PATCH /api/v1/accounts/{id}/suspension, POST /api/v1/accounts/{id}/block, POST /api/v1/accounts/{id}/unblock. |
tenant:config |
Write your own tenant’s branding, theme, venues and feature flags — PATCH /api/v1/tenants/{id}/*, for partners bringing their own front end. The matching GETs are not scope-gated. Also mints and revokes your tenant’s own credentials: /api/v1/api-clients. |
The scopes are independent; a full prop-firm operator credential carries
firm:operate and prop:manage together.
POST /api/v1/prop/setup is the one endpoint that reads as either, depending
on the body you send: creating a firm (firm) needs firm:operate, while
reusing one (firmId) needs only prop:manage. There is a catch worth knowing
before you plan around it — listing firms to find a firmId is itself
firm:operate, so “just reuse a firm” is not a way around not holding that
scope unless the id reached you from somewhere else.
The admin console grants all three at mint, and its edit form toggles the same
three afterwards over PATCH /admin-api/api-clients/{id}. Leave them unticked
and the credential is minted with none — which is the state to suspect
first when a brand-new credential answers prop_manage_required or
firm_operate_required on its very first call.
A tenant created through self-serve signup gets firm:operate + prop:manage
on its credential from the start, so it never passes through that state.
Credential tiers
Section titled “Credential tiers”Tiers gate the OMS write surface, enforced per endpoint. The rule is
liquidation may reduce risk, never add it:
| Tier | Reads | /oms/cancel · /oms/cancel-all · /oms/flatten |
Every other /oms/* write |
|---|---|---|---|
readonly |
✓ | ✗ | ✗ |
liquidation |
✓ | ✓ | ✗ |
fullTrading |
✓ | ✓ | ✓ |
“Every other /oms/* write” means /oms/intents, /oms/modify,
/oms/reverse, /oms/chase, and the three /oms/brackets/* routes. Placing,
modifying, reversing a position and chasing a working order to a new price all
establish or re-establish exposure, so each of them requires fullTrading.
Cancelling and flattening only ever shrink the book, so liquidation reaches
them.
A rejected /api/v1/oms/* call returns 403 — {"error":"tier_readonly"} when
the credential is readonly, {"error":"tier_insufficient"} otherwise.
detail names the tier you hold and the tier the route wants. Other tier-gated
routes may answer tier_insufficient for every rejected tier —
POST /api/v1/sandbox/paper-account does — so branch on both codes.
liquidation exists for risk systems that must be able to flatten a book they
are not allowed to add to. Issue the narrowest tier that does the job — a
read-only dashboard has no business holding fullTrading.
Tenant binding
Section titled “Tenant binding”On top of both gates, every credential is bound to one tenant. The tenant comes from the credential’s own row, not from the request, so you can only ever read and write your own tenant’s data. You do not send a tenant header on a bearer call — the credential already names the tenant, and nothing in the request can contradict it. See Tenancy.
Checking what you hold
Section titled “Checking what you hold”GET /api/v1/discovery catalogs endpoints with the tier each one needs, and
carries a scope field on the entries that need one:
{ "path": "/api/v1/firms", "methods": ["GET"], "summary": "List prop firms", "tier": "readonly", "scope": "firm:operate" }Two limits before you lean on it. The catalog is partial — a few dozen
entries against a much larger mounted surface, by
deliberate decision rather than by omission — and the
scope field is only on the firm:operate entries, so a prop:manage or
tenant:config requirement does not appear anywhere in it.
Neither does the catalog tell you what your credential holds; it describes
endpoints, not you. There is no endpoint that answers “what are my scopes”, and
the scope field in the token-mint response is not it — that one is always
mcp:<tier> and carries no scope at all. Until there is one, the way to
confirm a scope is to call an endpoint it gates and read the 403:
firm_operate_required, prop_manage_required or tenant_config_required
each name the scope that was missing.