Roles and Permissions
ScaiSend uses role-based access control. Users are assigned roles; roles carry permissions; permissions gate endpoints. API keys bypass the user layer — they carry scopes directly — but are gated by the same permission set.
Permissions#
A permission is a simple string like mail.send. Every endpoint declares the permission it requires. If the active credential lacks it, the request returns 403 Forbidden.
The full set:
| Permission | Category | What it allows |
|---|---|---|
mail.send |
Send mail via POST /v3/mail/send. |
|
mail.schedule |
Use send_at (future) or batch grouping on send. |
|
mail.cancel |
Cancel a queued or processing message. | |
templates.read |
templates | List and read templates and versions. |
templates.write |
templates | Create and update templates and versions. |
templates.delete |
templates | Delete templates and versions. |
suppressions.read |
suppressions | List bounces, spam reports, unsubscribes, groups. |
suppressions.write |
suppressions | Add/remove suppressions, create/edit groups. |
stats.read |
stats | Read stats, category breakdowns, totals. |
stats.export |
stats | Trigger stats exports. |
webhooks.read |
webhooks | List webhook endpoints and event settings. |
webhooks.write |
webhooks | Create, update, delete webhook endpoints. |
domains.read |
domains | List sender domains. |
domains.write |
domains | Add domains, verify DNS, rotate DKIM keys. |
admin.api_keys |
admin | Create, update, and revoke API keys. |
admin.users |
admin | Assign and remove user-role mappings. |
admin.settings |
admin | Edit tenant settings (tracking, defaults). |
Query the live list:
1 2 | |
Default roles#
Three roles are created for every new tenant. You can use them as-is, modify their permission sets, or add custom roles.
| Role | Intended for | Permissions |
|---|---|---|
admin |
Tenant operators | All of the above. |
developer |
Application integrators | mail.send, mail.schedule, templates.read, stats.read, webhooks.read. |
viewer |
Support agents, auditors | templates.read, stats.read, suppressions.read. |
The default roles are created by scaisend sync --create-roles on install. After that, they're ordinary tenant-scoped roles you can rename, re-scope, or delete.
Assigning roles to users#
A user can hold any number of roles. Role assignments are per-tenant — a user who operates on two tenants will have separate role assignments in each.
1 2 3 | |
Or from the CLI (use this when bootstrapping):
1 | |
Group-to-role mappings#
When a user is in a ScaiKey group, you can map the group to a role and every member inherits the role's permissions.
1 2 3 4 | |
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 | |
Group mappings stack with explicit role assignments — a user gets the union of permissions from both. Nested groups inherit: if Backend Team is a child of Engineering, and Engineering maps to developer, every member of Backend Team gets developer too.
This is the intended pattern for organizations that manage access through group membership in their identity provider. Map the groups once; user onboarding happens via the IdP.
API keys and scopes#
API keys carry the same permission strings as roles, stored as scopes on the key.
1 2 3 4 5 6 7 8 | |
The key gets exactly those scopes. It doesn't inherit additional permissions from the user who created it, and it can't be widened later without admin.api_keys access.
A key can never grant more than the creator has. If the creator has mail.send but not templates.write, they can't mint a key with templates.write.
Designing your permission model#
Some rules of thumb that hold up in practice:
- Give keys the narrowest scope that works. An application that only sends mail gets
mail.send, nothing else. If it's compromised, the blast radius is limited to sending mail — no template manipulation, no API key creation, no webhook rewiring. - Use separate keys for separate applications. Rotate one without disrupting the others.
- Don't use a key for human operations. If you need to poke around in a terminal, use your JWT. Keys are for non-interactive automation.
- Gate template editing to
templates.writeholders only. Templates can render arbitrary HTML into every email — in effect, they're code. Treat their authorship the way you'd treat production code. - Map groups to roles, not users to roles. Fewer things to keep in sync. Users churn; groups don't.
Checking effective permissions#
A user can see their own effective permissions:
1 2 | |
Response includes permissions: [...] — the union of all permissions granted by direct role assignments and by group mappings.
For an API key, list it:
1 2 | |
Each key's scopes field is its effective permission set.
Custom roles#
Create a new role, then attach permissions:
1 2 3 4 5 6 7 8 9 10 11 | |
Roles are tenant-scoped — a role created in tenant A doesn't exist in tenant B. Use consistent names if you're provisioning the same set of roles across tenants.
What's next#
- Authentication — using the credentials these permissions gate.
- Multi-tenancy — how tenants and partners fit together.
- Admin Reference — every admin endpoint.