allowed_scopes allowlist, and the user granting consent can
further narrow the set on the consent screen.
Applies to OAuth only. API keys don’t use scopes — they’re
bound to the tenant with all-or-nothing access inside their
allowed_scopes.
OIDC core
| Scope | Purpose |
|---|---|
openid | Required for OIDC. Returns an id_token. |
profile | Adds name and tenant_name claims to /userinfo. |
email | Adds email and email_verified claims. |
offline_access | Required to receive a refresh_token. Without it, tokens cannot be refreshed. |
Resource scopes
| Scope | Unlocks |
|---|---|
campaigns:read | GET /api/v1/campaigns, GET /api/v1/campaigns/{id} |
campaigns:write | POST, PATCH, DELETE /api/v1/campaigns/* |
leads:read | GET /api/v1/campaigns/{id}/leads, list + get |
leads:write | POST /api/v1/campaigns/{id}/leads, :bulk |
calls:read | GET /api/v1/calls, GET /api/v1/calls/{id} |
calls:write | Future — agent triggered hangup etc. Reserved. |
agents:read | GET /api/v1/agents |
Composition
Scopes are space-separated in the token request:scope claim. Server-side
gate check is exact-match per route — requesting only
campaigns:read and trying to POST /api/v1/campaigns returns a
403 { "detail": "missing scope campaigns:write" }.
Principle of least privilege
Partners are expected to request only the scopes they need. AppExchange and similar review processes will flag a listing that requestscampaigns:write but never actually writes campaigns. Narrow the
set to what your integration actually uses.