Skip to content

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.

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.

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.

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.

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.