Permissions
ScaiDial declares four module permissions. They're requested when an API key is minted and granted by a tenant admin.
| Permission | Gates |
|---|---|
scaidial:trunks:manage |
Trunks, DIDs, tenant policy (Settings page) |
scaidial:extensions:manage |
Extensions, extension grants, forward rules (admin-tier) |
scaidial:dialplan:manage |
Dialplans and dialplan rules |
scaidial:calls:observe |
Active calls, call history, leg controls (hangup / hold / transfer) |
A tenant-admin role typically gets all four. A read-only auditor might get only :calls:observe. A dialplan-editor role (useful for delegating routing changes without giving carrier credentials) gets only :dialplan:manage.
How permissions are evaluated#
The dependency injection layer (require_module_permission) reads the permission set on the authenticated user (JWT path) or the API key (key path). The check is strict: missing the right key returns 403 before the route body runs.
Super-admin bypasses all module permission checks. Cross-tenant access is separately blocked by _require_tenant_scope — even with the right module permission, a tenant admin can't see another tenant's trunks.
End-user portal — no module permission, grant-based#
The /me/* routes don't gate on scaidial:* permissions. They gate on ExtensionGrant rows.
| Grant role | What it allows on the granted extension |
|---|---|
owner |
Read everything + edit personal config (DND, forwarding, ring timeout, voicemail greeting). Place outbound calls from this extension. |
manage |
Read voicemail + call history. Used for admin-tier reassignment of someone else's line; not for day-to-day "where do my calls go". |
answer |
Receive ringing calls. Read voicemail (a delegated answerer needs to see what they missed). |
observe |
Read-only view of call history. Used for supervisors. |
A user with no grants on any extension lands on /my/dial and sees an empty state pointing them at their tenant admin.
The matrix#
| Operation | Permission needed | Or grant role |
|---|---|---|
GET /trunks |
scaidial:trunks:manage |
— |
POST /trunks/{id}/resync |
scaidial:trunks:manage |
— |
GET /dids |
scaidial:trunks:manage |
— |
POST /extensions |
scaidial:extensions:manage |
— |
POST /extensions/{id}/grants |
scaidial:extensions:manage |
— |
GET /dialplans |
scaidial:dialplan:manage |
— |
POST /dialplans/{id}/rules |
scaidial:dialplan:manage |
— |
GET /calls/active |
scaidial:calls:observe |
— |
POST /legs/{id}/hangup |
scaidial:calls:observe |
— |
POST /legs/{id}/transfer |
scaidial:calls:observe |
— |
GET /policy |
scaidial:trunks:manage |
— |
PATCH /policy |
scaidial:trunks:manage |
— |
GET /me/extensions |
(none) | any grant |
PATCH /me/extensions/{id} |
(none) | owner |
GET /me/voicemail |
(none) | owner or manage (per-row) |
GET /me/voicemail/{id}/audio |
(none) | owner or manage |
POST /me/click-to-call |
(none) | owner on the source extension |
POST /me/extensions/{id}/forward-rules |
(none) | owner |
Issuing an API key for ScaiDial#
API keys are minted in ScaiGrid Auth (Identity → API keys). On creation you select the module permissions to attach. For a typical tenant-admin key that should be able to fully manage ScaiDial:
1 2 3 4 | |
Add scaidial:diarize separately if the tenant has ScaiEcho voicemail-transcript opt-in and you want the key to be able to request diarization on streaming endpoints elsewhere.
Why no per-extension permission scheme#
We considered scaidial:extension:{id}:manage as a separate permission key per extension. It would let you delegate ownership of specific extensions without giving the tenant-wide manage role. We chose grant rows instead because:
- Grants are a single index on
(extension_id, user_id), which scales linearly with users-times-extensions. A permission-per-extension would explode the permission table. - Tenant admins already need an admin-tier permission for tenant-wide config; grants cover the per-extension delegation case directly.
- Grants compose with groups (a
group_idinstead of auser_id), which permissions don't.
If you need finer-grained admin delegation than the four module permissions allow, file an issue with the use case — we'll add it if there's demand.