Scopes
Every credential — an sk_live_… key or an MCP grant — carries a list of scopes shaped
family:level. A key gets its list when it is minted; a grant gets what you ticked on the
consent screen. Each operation in the reference names the one it requires, and a
credential without it is refused with an error that names it back.
The scopes
Account-wide
Apply to the account itself rather than to any one channel.
| Scope | Grants | Meaning |
|---|---|---|
account:read
|
Read your profile | Handle, display name and links. Every key and grant includes this. |
webhooks:read
|
List your webhooks | Registered endpoints and their health — never their secrets. |
webhooks:write
|
Manage webhooks | Register and remove endpoints that receive your account’s events. |
Channels and alerts
Composing, sending and measuring what a channel says.
| Scope | Grants | Meaning |
|---|---|---|
channels:read
|
Read your channels | Alert channels, their settings and subscriber counts. |
channels:write
|
Manage your channels | Create, edit, pause and delete alert channels. |
alerts:read
|
Read alert history | Alerts, their reads and acknowledgements. |
alerts:write
|
Compose draft alerts | Write and edit drafts. Never publishes anything. |
alerts:send
!
|
Send alerts that can ring phones | Publish now, schedule, and cancel. An alert cannot be unsent. |
analytics:read
|
Read channel analytics | Sends, reads, acknowledgements and subscriber trends. |
subscribers:read
|
Read subscriber records | Pseudonymous device records — platform, locale, timezone. Never identities. |
Implication: write covers read
x:write satisfies x:read, and
alerts:send satisfies alerts:write and
alerts:read. Implication is resolved when the check runs, never stored: a grant holds
exactly what was approved, and the check widens it. So a key minted with only
uploads:write lists uploads too — but its stored list still says exactly what you chose.
alerts:send is separate from alerts:write on purpose:
composing drafts is not the authority to ring phones. Scheduling and cancelling both ARE that authority — a
scheduled alert reaches phones with no further approval, and withdrawing a live evacuation notice changes what
reaches them — so both verbs require it too. (Early mockups had a separate
alerts:schedule; it folded into alerts:send, because
scheduling is send authority with a timestamp on it.)
Presets
The key-creation form offers these as starting points. They are explicit lists, not aliases — there is
deliberately no stored full value that would silently absorb every future scope into
old credentials.
| Preset | Pitch | Scopes |
|---|---|---|
| Read-only | Sees everything, changes nothing. | account:readwebhooks:readchannels:readalerts:readanalytics:readsubscribers:read |
| Compose only | Writes drafts and reads the numbers. CANNOT send — nothing it holds reaches a phone. | account:readchannels:readalerts:readalerts:writeanalytics:read |
| Alerts operator | Run channels end to end: compose, send, measure. | account:readchannels:readchannels:writealerts:readalerts:writealerts:sendanalytics:readsubscribers:read |
| Full access | Everything the account can do over the API. | account:readwebhooks:readwebhooks:writechannels:readchannels:writealerts:readalerts:writealerts:sendanalytics:readsubscribers:read |
Channel-bound keys
A key may be bound to one channel at creation. A bound key sees exactly its pusher —
GET /v1/pushers lists one channel, and every other channel answers
not_found — and may carry only the operating scopes:
account:read, channels:read, the three
alerts levels, analytics:read and
subscribers:read. Audio and webhook authority stay off it (they would outlive the
binding's meaning), and so does channels:write: a bound key may operate its channel's
alerts, not re-describe or delete the channel. The form clamps the selection and the server clamps it again.
Legacy credentials: full access, labeled
A key minted before scopes existed, and an OAuth grant that carries only identity scopes, keep doing everything they could — breaking every creator's cron job and every connected assistant on deploy day, for a permission they de-facto had, is not hygiene. The studio labels both “Full access — created before scopes” so owners can rotate deliberately. New keys always carry a non-empty list and new grants always carry capability scopes, so the legacy encoding stays unambiguous.
The refusal, in full
A missing scope is 403 with error.type: "insufficient_scope" —
a different word from forbidden on purpose, because the remedies differ:
forbidden means “not your role or your data”; this means “your credential was minted
without this permission — mint a key with it, or reconnect the assistant approving it”. The RFC 6750 header rides
beside it, which is the standard place an OAuth client library looks first:
$ curl -X PATCH https://drop.top/api/v1/pushers/$PUSHER_ID/alerts/$ALERT_ID \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"action":"send"}'
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="alerts:send"
Content-Type: application/json
{
"error": {
"type": "insufficient_scope",
"message": "This credential lacks alerts:send. Mint a key carrying it, or reconnect the assistant approving it.",
"param": null
}
}
Any operation can answer this way; the reference spells it out only where an operation needs more than its
declared scope — sending, where send: true raises the requirement from
alerts:write to alerts:send.