API key scopes
Every Driftstack API key carries a set of
scopes. Endpoints that declare a required scope allow the
request only if the key’s scope set satisfies it. A few
endpoints require no specific scope beyond a valid key — their
docs pages say so explicitly (GET /v1/whoami is one).
You choose a key’s scopes when you create it in the Driftstack dashboard, where API keys are created, rotated and revoked.
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 scope —
account_owner. Gates customer-account control. - 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, except the saved proxies (/v1/account/me/proxies), which need account_owner even to list. |
write |
broad | All state-mutating operations except destructive admin actions. Does NOT include read — pair with read. |
admin |
broad (legacy) | Deprecated customer alias. Satisfies account_owner and every admin:* scope. |
account_owner |
account-control | Account control: webhook endpoint management, every saved-proxy route (/v1/account/me/proxies: list, save, import, update, delete, test, and the change-IP routes), plus the account settings, API keys, team and billing managed in the dashboard. Customer dashboard scope. |
read:sessions |
granular | Read sessions and agent sessions — list, get, the agent-session transcript stream, page state, network log, captures, cookies, downloads and the page’s frames. |
write:sessions |
granular | Create + drive + delete sessions on /v1/sessions. Not enough for agent sessions, whose writes need the broad write. Does not include read — pair with read:sessions to list/get. |
read:profiles |
granular | Read profiles endpoints and their snapshots — /v1/profiles, /v1/profiles/:id/snapshots, /v1/profile-snapshots and /v1/profile-snapshots/:id. Snapshot reads return metadata (label, parent profile, capture time), never the stored browser state. |
write:profiles |
granular | Create + edit + delete profiles (and their snapshots). Does not include read — pair with read:profiles. |
admin:profiles |
granular | Reserved. Enforced on no route today — profile reads require read:profiles, and every mutation (create / edit / delete, including snapshots) requires write:profiles. A key holding only this scope is refused by every profile endpoint. |
read:webhooks |
granular | Read webhook endpoints and their delivery history — GET /v1/webhooks, GET /v1/webhooks/:id, and GET /v1/webhooks/:id/deliveries. The delivery rows carry last_response_status, last_response_excerpt and last_error from your own endpoint, so this scope sees more than the endpoint list. |
write:webhooks |
granular | Declared but enforced on no route today — reads require read:webhooks, and endpoint management (create / update / delete / rotate-secret / send-test) requires account_owner — see note. |
admin:webhooks |
granular | Reserved. Webhook endpoint management is account-control-level and requires account_owner, not this granular scope. |
read:api-keys |
granular | Read API key metadata (the dashboard’s key list) only. |
admin:api-keys |
granular | Reserved. API keys are created, rotated and revoked in the dashboard. |
read:billing |
granular | Read billing state and orders, which are managed in the dashboard. A broad read or account_owner key also satisfies it; a write-only key is refused. |
admin:billing |
granular | Billing changes. Checkout, the billing portal and orders are done in the dashboard, and the API refuses them when the request carries an API key. |
read:audit |
granular | Read account audit log only. |
Note — saved proxies are account-control too. Every route under
/v1/account/me/proxiesneedsaccount_owner, listing included: aread+writekey gets403. That isGETandPOST /v1/account/me/proxies,PUTandDELETE /v1/account/me/proxies/{id},POST /v1/account/me/proxies/{id}/test,POST /v1/account/me/proxies/import, and the change-IP routes. Every agent session needs aproxy_id, so look the id up once with anaccount_ownerkey and give the job the id. See Find a proxy’s id.
Note — webhook management is an account-control operation. Creating, updating, deleting, or rotating webhook endpoints requires the
account_ownerscope, not a granularwrite:webhooks/admin:webhooksscope. Account-configuration surfaces are deliberately gated at the account-control level rather than at granular write level. (A broadadminkey still satisfiesaccount_ownerthrough the legacyadminalias.)
Note — agent-session endpoints require the broad
writescope. Session routes (/v1/sessions/*) accept the granularwrite:sessions, but the agent-session writes that start or drive a session (/v1/agent-sessions/*— create, message, steps, stop, close, reopen, resume, cookie import, history and file upload) gate on the broadwritescope. There is no agent-sessions-specific granular scope. If you mint a narrow CI key, include the broadwritescope to call these endpoints. Agent-session reads acceptread:sessions. One documented agent-session write takes a granular scope: the profile notes (PUT /v1/agent-sessions/{id}/profile-notes) needwrite:profiles, because they edit the profile and run nothing. An OAuth token can hold only granular scopes, so OAuth apps can’t run AI sessions today; use an API key for that.
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: write, plus every write:* — but NO read:* (writes never imply reads)
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).
Checking what your key actually has
GET /v1/whoami
Answers the other half of a scope mismatch — not what the route wanted, but what the key you are holding actually carries:
{
"account_id": "acc_...",
"api_key_id": "key_...",
"tier": "pro_v3",
"scopes": ["read", "write:sessions"]
}
Useful when a 403 is surprising: it confirms which key the request authenticated with, which matters when an environment has several configured.
Requires only a valid key, whatever its scopes, so any key can call it to see which scopes it has.
Picking scopes for a new key
When you create a key in the dashboard, you pick scopes per use case. Defaults:
- CI / test runner:
read+writewhen it runs agent sessions (agent-session writes need the broadwrite).write:sessionswrites only/v1/sessions;read:sessionsstill reads both kinds of session. The granular pair cannot read or manage webhooks, saved proxies or billing.read+writecan read webhook endpoints, their deliveries and billing state. It cannot manage webhooks or list saved proxies (both needaccount_owner), and billing changes are made in the dashboard. - Production application:
read+write. Excludes account-management surfaces. - AI automation job (agent sessions):
read+write— agent-session writes need the broadwrite. Keepaccount_owneroff the job key. That key cannot list your saved proxies, so look up theproxy_idonce with anaccount_ownerkey and give the job the id. Store your Anthropic key or opt in to bundled billing once from the dashboard instead. See Run AI tasks from your code. - Backup automation:
read+read:audit. - Webhook receiver: needs no API key. Driftstack signs each
delivery with the endpoint’s
whsec_secret, so your receiver needs only that secret; see webhooks docs. A key you create in the dashboard must have at least one scope: an empty scope list is refused. - Dashboard / customer self-service:
account_owner. The default scope minted by the customer dashboard’s “first key” flow.