Scopes
A scope is a capability (resource:action), not a surface. The same scope
authorizes the same capability whether you arrive over REST or MCP.
Request only what you need — space-delimited in an OAuth authorize request, or selected per key in the admin center.
| Scope | Grants | Spends money |
|---|---|---|
sms:read | Texting configuration — which numbers can text, blocked senders, saved replies, schedules. Not message content. | No |
sms:write | Resolve and assign conversations; manage blocked senders, saved replies and drafts. Cannot send to anyone. | No |
sms:send | Send SMS from the account's numbers, and schedule one to go out later | Yes |
messages:read | Read message content — the inbox, a thread's text and media, delivery receipts, unread counts | No |
messages:write | Mark messages read or unread; permanently delete messages or whole conversations | No |
users:read | The account's users — names, emails, extensions. Personal data. | No |
recordings:read | Call recordings and transcripts — the contents of conversations | No |
webhooks:read | List and view webhook subscriptions | No |
webhooks:write | Create, update, delete, enable and disable subscriptions | No |
reports:read | Call history — who called whom, when, how long, how it ended | No |
fax:read | Sent and received faxes — the list, each fax's delivery status, and the fax documents themselves | No |
fax:send | Send faxes from the account's fax numbers | Yes |
calls:make | Place click-to-call calls — ring a phone, then connect it to a number | Yes |
flows:call | Place an outbound call into one of the account's Flows — the flow call trigger, outside the REST API. API keys only. | Yes |
The live list is scopes_supported in the
discovery document. It lists only scopes OAuth can grant,
so flows:call (key-only, below) is not in it.
Why texting is five scopes
Because they are five different decisions:
- Building a number picker (
sms:read) should not require the ability to spend money. - A help-desk integration that only closes threads (
sms:write) should not be able to text customers. - A send-only integration (
sms:send) should not be able to enumerate the account's number inventory. - Reading message content is its own consent.
messages:readis separate fromsms:readfor the same reasonrecordings:readis separate from everything else: a thread is the contents of a conversation, not metadata about it. Folding it intosms:readwould also have silently upgraded every token already issued for a number picker into one that can read the account's entire message history. - Destroying messages is separate from bookkeeping.
sms:writeis reversible — a thread can be reopened, an assignment changed. A delete undermessages:writeis permanent.
Ask for each only if you do that thing.
Why faxing is two scopes
For the same reasons, split the same way. fax:read is the contents of
communications — a fax document is as sensitive as a message thread or a
recording — and is its own consent. fax:send spends money and reaches someone
outside the account, so an archiving integration that only reads fax history
must not be able to send one. See Faxes.
Why placing a call is its own scope
calls:make spends money and reaches someone outside the account, like
sms:send and fax:send. An integration that reads call history
(reports:read) must not also be able to dial. A key with calls:make must
also list the phones it may ring, or it cannot place calls at all. See
Click to call.
:::note Deleting has a second gate that scopes do not override
messages:write lets you call the delete endpoints. If the account has
restrict deleting SMS switched on, they still answer 403 — that setting
exists so an account owner can stop their own staff and integrations from
destroying message history, and no scope overrides it.
:::
A scope is a ceiling, not a key
Holding a scope does not mean you get everything under it.
For an OAuth token, the portal permissions of the person who authorized your app still apply on top. A scope can only narrow what they could already do themselves — never widen it.
| Endpoint | Scope | …and the authorizing user's portal permission |
|---|---|---|
GET /users | users:read | Users — you see the users they can see, no more |
GET /sms/lines | sms:read | Phone numbers — only the numbers assigned to them |
POST /sms/messages | sms:send | Phone numbers — from must be one of theirs |
POST /sms/conversations/…/resolve, …/assign | sms:write | Phone numbers — the line must be one of theirs |
GET /sms/conversations, GET /sms/conversations/{line}/{number} | messages:read | Phone numbers — only threads on lines that are theirs |
DELETE /sms/conversations/… | messages:write | Phone numbers, plus the account's restrict-deleting-SMS setting |
POST /calls/search, GET /calls/{id} | reports:read | Call reports — only the calls their role exposes |
GET /calls/{id}/recordings, .../transcripts | recordings:read | Call reports → allow recordings — without it, 403 even with the scope |
GET /faxes, GET /faxes/{id} | fax:read | Fax History not Block, and Phone numbers — only faxes on their numbers |
GET /faxes/{id}/file | fax:read | Fax History → Allow with View Fax — without it, 403 even with the scope |
POST /faxes | fax:send | TeleConsole → Fax, and Phone numbers — from must be one of theirs |
POST /calls | calls:make | Their own phones only; Phone numbers — a callerId number must be one of theirs |
So the same request can legitimately return different data for two tokens on the same account. If your app sees less than you expect, check the authorizing user's role in the portal before assuming a bug.
The two failures look alike and are fixed in different places:
error.type | What is wrong | Fixed by |
|---|---|---|
insufficient_scope | The credential lacks the scope | Your code — re-authorize |
permission_denied | The scope is fine; the role lacks the permission | Their admin, in the portal |
API keys are checked on scope only today — a key gets the whole account within its scopes. See What a key can reach.
:::note The fax and call endpoints already apply a role to keys
On /faxes, a key is held to the role of the user it runs as (set on the key
in the admin center), using the permissions in the table above. A key whose user
has Fax History set to Block gets 403 permission_denied, whatever its
scopes. See Faxes.
On POST /calls, a callerId number must be among that user's role numbers.
The phone to ring is not taken from the role: a key may ring only the
phones selected on it, and a key with no phones cannot place calls. See
Click to call.
:::
Scopes a key can hold but OAuth cannot
flows:call — ringing a number and running one of the account's Flows when it
answers — is key-only and can never be granted through the user-consent flow:
it places billable calls that reach people outside the account, so no delegated
third-party app may ask for it. Your own systems use it through a key you create.
It is the only scope for POST /api/v1/flows/{flowId}/calls
(see Calling into a Flow), and that endpoint accepts
nothing else — no REST endpoint accepts flows:call, and no other scope can
place a flow call. Two things narrow it:
- Flow grants. A key with
flowresource grants may call only those flows. Without them it may call every flow on the account. - Allowed IPs, as for any key.
The key's run-as user's role is not consulted on this endpoint — not even the Flows permission. Narrow the key itself.