Skip to main content

Migrating to categorical scopes

UTM moved from a long list of fine-grained scope literals (trades:read, signals:read:all, queues:write:all, and so on) to nine categorical scopes. This page is for anyone holding a token issued under the old model.

If you are issuing a new token, you do not need this page; go straight to the scope reference.

Your existing tokens keep working

You do not have to do anything. Every token issued under the old model was rewritten in place to the categorical scopes that cover the same access. The rewrite was a one-time migration over the stored token records, so a token that previously held, say, trades:read and positions:read now holds trading:read and authenticates exactly as before. No token was regenerated, so the token string in your integration is unchanged and continues to work.

The legacy X-API-Key: utm_* header also still authenticates tokens issued before the credential unification, against the same scope checks.

What changed and why

The old model had a separate read scope per resource (trades:read, positions:read, signals:read, and so on) and a parallel *:read:all family for cross-user admin reads. That was a large surface to reason about, and it conflated two genuinely different concerns: reading per-user data by an opaque userId, and resolving that userId to a real-world identity.

The categorical model collapses the per-resource reads into four personal-grant scopes and splits admin reads into pseudonymous (admin:read:user) and identity-bearing (admin:read:identity). The result is a shorter list that maps to how access actually clusters. See the scope reference for the full catalogue, including what each scope does not grant and the never-delegate list.

Mapping table

Every old scope maps to exactly one or, in two cases, two categorical scopes. No access was dropped without a replacement.

Old scopeNow covered byNotes
signals:writesignals:writeUnchanged.
accounts:readaccounts:readSame name, broader: now also covers brokers, balances, reconciliation reads, and sync status.
signals:readtrading:readFolded into the general trading read.
trades:readtrading:read
positions:readtrading:read
strategies:readtrading:read
logs:readactivity:readYour own activity feed.
trades:read:alladmin:read:userPer-user reads keyed by opaque userId.
positions:read:alladmin:read:user
orders:read:alladmin:read:user
signals:read:alladmin:read:user
strategies:read:alladmin:read:user
accounts:read:alladmin:read:user
brokers:read:alladmin:read:user
sync:read:alladmin:read:user
reconciliation:read:alladmin:read:user
apps:read:alladmin:read:user
notifications:read:alladmin:read:user
logs:read:alladmin:read:userAudit and system logs, now pseudonymous by userId.
activity:read:alladmin:read:user
users:read:alladmin:read:identityResponses include email and name, so this is the identity scope.
queues:read:alladmin:readSystem-wide queue stats.
health:read:alladmin:read plus admin:read:userThe catalogue endpoint stays system-wide; per-user invariant runs are pseudonymous. A holder gained both so no access was lost.
queues:write:alladmin:write plus admin:destructiveSingle-job control moved to admin:write; mass clear moved to admin:destructive. A holder gained both.

Suggested action: re-issue for clearer audit trails

Re-issuing is optional, but worth doing. A token migrated automatically holds whatever the translation produced, which can be broader than the consumer actually uses. Issuing a fresh token through the categorical scope picker lets you grant the minimum the integration needs and gives you a cleaner audit trail keyed to the categorical names from day one.

  1. Review what the integration actually calls and pick the smallest set of categorical scopes that covers it. The common combinations table is a good starting point.
  2. Issue a new token with those scopes from Settings -> Access tokens.
  3. Update the integration to use the new token.
  4. Revoke the old token once the new one is in use.