Skip to main content

Scopes

Scopes control which endpoints an access token can reach. When you issue a token, select only the scopes the consumer actually needs. A request made with a token that lacks the scope an endpoint requires is rejected with 403 Forbidden.

There are nine categorical scopes: four personal-grant scopes any user may issue on their own tokens, and five admin scopes that only an admin user may issue. The model is deliberately coarse. Each scope names a category of data or action rather than a single resource, so there is one short list to reason about rather than dozens of fine-grained literals.

If you hold a token issued before this model landed, see the categorical scopes migration guide for how your old scopes were translated.

Personal-grant scopes

Any user may issue these on their own tokens.

ScopeGrantsDoes not grant
trading:readRead access to trades, positions, orders, executions, signals, strategies, performance, pending imports, and the dashboard summaryAny write. No order placement, no signal creation, no trade edits. No other user's data.
accounts:readRead access to accounts, brokers, balances, reconciliation reads, sync status, and licence statusConnecting or disconnecting brokers, changing broker credentials, triggering a sync, or setting the primary account. No other user's data.
activity:readRead access to your activity feed, notifications, and notification preferences, including your own audit logChanging notification preferences. No other user's activity or audit log.
signals:writeCreate, update, and cancel signals against any account you have access to, plus manual and batch signal processingDirect order placement (POST /orders), trade edits, or any read scope. Pair it with trading:read if the consumer also needs to read back what it submitted.

signals:write is the supported path for automation. UTM translates a signal into broker orders, applies your strategy and exit rules, and deduplicates intent. Direct order placement is never delegated to a token (see Never-delegate below).

Admin scopes

Only an admin user may issue these. A non-admin who names an admin scope at issuance receives a 400.

ScopeGrantsDoes not grant
admin:readSystem-wide, non-user-bound reads: queue stats, monitoring metrics, sessions, health invariants, aggregate system stateAny per-user data. No identity fields. No writes.
admin:read:userPer-user reads with userIds as opaque identifiers: another user's trades, positions, orders, signals, accounts, broker state, audit-log entries, and system-log entries, with no identity joinThe userId-to-identity join. Responses carry the userId UUID, never email, name, phone, or billing. No writes.
admin:read:identityThe userId-to-identity join: the user list and user detail, where responses include email, name, phone, billing, and free-text user-content fieldsAny write. This is the only scope that can resolve a userId to a real-world identity.
admin:writeNon-destructive single-record admin fixes: settings updates, config changes, user role and status edits, and queue control (retry, pause, trigger)Mass or irreversible operations. No permanent deletes, no bulk clears.
admin:destructiveMass or irreversible operations: permanent user delete, clear-all failed jobs, and per-queue failed-job clearsNothing beyond the destructive operations themselves. It is not a superset of the read scopes; pair it with the reads a workflow needs.

admin:read:user and admin:read:identity are split on purpose. Most cross-user diagnostics only need to correlate events by userId, which is pseudonymous. Reserve admin:read:identity for the rare case that genuinely needs to resolve a userId to a person, and keep it off tokens that do not.

Never-delegate operations

Some operations can only be performed by the user in the web app with a JWT session. No access token, at any scope, can perform them. These are the operations where delegating to a long-lived token would either let the token undermine the identity that issued it, escalate its own privilege, or touch financial, regulatory, or invariant-sensitive state.

CategoryOperationsWhy it is never delegated
IdentityPassword, MFA, email change, account deletionA token must not be able to revoke or rewrite the identity that issued it.
TokensIssuing and managing access tokensPrevents a token from minting more privilege for itself.
SubscriptionPayment, plan changes, cancellationFinancial and regulatory.
Broker connectionsOAuth, credential updates, connect, disconnectCould silently re-route where your trades execute.
Personal dataExport and erasurePrivacy obligations sit with the authenticated person.
Trade hygieneClose, edit, or delete trades, and reconciliation writesInvariant-sensitive. The signal lifecycle is the supported automation path.
Account configurationSync trigger, primary set, UTM start date, broker configOperator-level account setup, not delegated.
Strategy curationCreate, edit, or delete strategy templatesCurated by hand in the app.
Direct order placementPOST /orders, DELETE /orders/:idSignals are the supported broker-translation layer for automation.

Why there is no :live dimension

Scopes do not distinguish paper from live trading, and there is no :live suffix on any scope. Trading mode is a client-side preference, not a security boundary. A token that can submit a signal can submit it against any account the user owns, paper or live alike, exactly as the user can in the app.

Two reasons drove this:

  • The account already carries its own type. Whether an order lands against a paper or a live account is decided by the accountType on the account row the signal targets, not by a claim on the token. Gating on a token-level :live flag would duplicate that decision in a second place that could disagree with the first.
  • A :live dimension roughly doubles the scope surface for a distinction the platform does not actually enforce at the token layer. The earlier session-mode JWT claim that tried to enforce it was removed because it added a cross-check that the account type already settled.

If you want a token restricted to paper trading, restrict it at the account level by only granting the consumer access to paper accounts, not by reaching for a scope that does not exist.

Requesting a token and the scope picker

Tokens are issued from Settings -> Access tokens. See Access tokens for the full create, use, and revoke flow.

The scope picker on the create form renders exactly the scopes you are allowed to issue:

  • Every user sees the four personal-grant checkboxes.
  • Admin-role users additionally see the five admin checkboxes.

A non-admin user has no way to select an admin scope, and the API rejects one even if it is sent directly. Select the minimum the integration needs; you can always issue a second, narrower token rather than over-scoping one.

Common scope combinations

Use caseScopes
TradingView or webhook signal submissionsignals:write
Read-only portfolio monitortrading:read, accounts:read
Trading journal synctrading:read, accounts:read
Equity-curve and balance dashboardstrading:read
Strategy-aware automationtrading:read, signals:write
Full personal read accesstrading:read, accounts:read, activity:read

Scope enforcement

A request with a token that lacks the required scope receives 403 Forbidden:

{
"error": "Insufficient scope",
"code": "INSUFFICIENT_SCOPE",
"required": ["trading:read"],
"granted": ["accounts:read"]
}

Each endpoint in the API reference documents the scope it requires.

Revoking a token

Revoke a token from Settings -> Access tokens by clicking the trash icon next to its row and confirming. The token is invalidated immediately and the revocation cannot be undone; issue a fresh token to replace it. Full details are on the Access tokens page.

One credential type: access tokens

API keys and connected-app tokens have collapsed into a single credential type: access tokens. They were always functionally identical (named, scoped bearer token, revocable, hashed at rest). The settings page, the storage table, and the scope set are all unified, so there is now one place to issue tokens and one set of scopes to reason about.

Tokens issued before the unification keep working unchanged.