API key scopes
reference. Every Driftstack API key carries a set of
scopes. Each /v1/* endpoint declares the scope it requires,
and the request is allowed only if the key’s scope set
satisfies that requirement.
Scope categories
There are three categories of scopes, in order of breadth:
- Broad scopes —
read,write,admin. Cover every resource of the corresponding verb across the customer’s account. The simplest mental model: “this key can do everything a customer can do at this verb level.” - Account-control scopes —
account_owner,driftstack_internal_admin. Gate customer-account control (account_owner) and Driftstack-staff-only operations (driftstack_internal_admin). Customer keys never carrydriftstack_internal_admin. - **Granular scopes ** —
verb:resourcesyntax (e.g.read:sessions,write:webhooks). Narrow keys for integrations that should not have full account access.
Full scope list
| Scope | Category | Grants |
|---|---|---|
read | broad | All read-side operations across every resource the customer owns. |
write | broad | All read + state-mutating operations except destructive admin actions. |
admin | broad (legacy) | Pre-alias. Treated as satisfying both account_owner + driftstack_internal_admin. |
account_owner | account-control | Mint API keys, revoke API keys, manage subscription, /v1/account/*. Customer dashboard scope. |
driftstack_internal_admin | account-control | /v1/admin/* — list all accounts, suspend account, change tier, force-actions. Driftstack staff. |
gui_control | special | Manual-control plane (tap_at, type_focused). Self-hosted GUI workflow only (locked-decision L-001). |
read:sessions | granular | Read sessions endpoints only. |
write:sessions | granular | Read + create + delete sessions. |
read:profiles | granular | Read profiles endpoints only. |
write:profiles | granular | Read + create + delete profiles. |
admin:profiles | granular | All admin operations on profiles. |
read:webhooks | granular | Read webhook endpoints only. |
write:webhooks | granular | Read + create + delete webhook endpoints. |
admin:webhooks | granular | All admin operations on webhooks. |
read:api-keys | granular | Read API keys list / metadata only. |
admin:api-keys | granular | All admin operations on API keys (mint + revoke). Implies account_owner for the keys subtree. |
read:billing | granular | Read billing state + invoice / subscription metadata only. |
admin:billing | granular | All admin operations on billing (start trial, change subscription, manage portal). |
read:audit | granular | Read account audit log only. |
broad-satisfies-granular rule
A key with a broad scope satisfies any granular scope on the same verb:
- A key with
readsatisfies everyread:*granular scope (read:sessions,read:profiles,read:webhooks,read:api-keys,read:billing,read:audit). - A key with
writesatisfies anywrite:*. - A key with
admin(oraccount_owner) satisfies anyadmin:*.
The reverse is not true: a key with read:sessions does
NOT satisfy read — narrow keys stay narrow. That’s the
point of granular scoping; a read:sessions key can only
ever read sessions, not profiles or webhooks.
key with: read → can do: read, plus every read:* (read:sessions, read:profiles, read:webhooks, read:api-keys, read:billing, read:audit)
key with: read:sessions → can do: read:sessions (only)
key with: write → can do: read, write, plus any read:*/write:*
key with: account_owner → can do: read, write, plus any read:*/write:*/admin:*
What happens on a scope mismatch
The API returns HTTP 403 with an RFC 9457 problem-details body:
{
"type": "https://errors.driftstack.dev/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "This action requires the \"write:sessions\" scope."
}
The detail string names the exact scope required so you can mint a correctly-scoped replacement key (or extend the existing one).
Picking scopes for a new key
When you mint a key from the dashboard or via
POST /v1/api-keys, you pick scopes per use case. Defaults:
- CI / test runner:
read:sessions+write:sessions. No access to profiles, webhooks, or billing. - Production application:
read+write. Excludes account-management surfaces. - Backup automation:
read+read:audit. - Webhook signing-only key: mint a key with NO scopes.
The key authenticates the webhook signature but cannot
call any
/v1/*endpoint. (This is the recommended pattern — the webhook signing secret is separate from API keys; see webhooks docs for details.) - Dashboard / customer self-service:
account_owner. The default scope minted by the customer dashboard’s “first key” flow.
Source of truth
The full scope enum lives in
packages/api-types/src/common.ts:ApiKeyScopeSchema. The
requireScope predicate is mirrored at two server-side call
sites (apps/server/src/lib/errors-helpers.ts +
apps/server/src/services/auth.ts) and verified by the
41-case unit test at
apps/server/tests/unit/scope-check.test.ts.
Any scope additions land via V-NNN slice that updates the schema, both predicate sites, and this docs page.