Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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 mail Send mail via POST /v3/mail/send.
mail.schedule mail Use send_at (future) or batch grouping on send.
mail.cancel mail 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:

bash
1
2
curl https://scaisend.scailabs.ai/v3/scopes \
  -H "Authorization: Bearer $SCAISEND_API_KEY"

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.

bash
1
2
3
# As a JWT with admin.users
curl -X POST https://scaisend.scailabs.ai/api/admin/users/usr_ada/roles/role_developer \
  -H "Authorization: Bearer $SCAISEND_JWT"

Or from the CLI (use this when bootstrapping):

bash
1
scaisend assign-role ada@example.com --role developer --tenant acme-corp

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.

bash
1
2
3
4
curl -X POST https://scaisend.scailabs.ai/api/admin/group-mappings \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"group_id": "grp_engineering", "role_id": "role_developer"}'
python
1
2
3
4
5
6
7
8
import os
import httpx

httpx.post(
    "https://scaisend.scailabs.ai/api/admin/group-mappings",
    headers={"Authorization": f"Bearer {os.environ['SCAISEND_JWT']}"},
    json={"group_id": "grp_engineering", "role_id": "role_developer"},
)
typescript
1
2
3
4
5
6
7
8
await fetch("https://scaisend.scailabs.ai/api/admin/group-mappings", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAISEND_JWT}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ group_id: "grp_engineering", role_id: "role_developer" }),
});

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.

bash
1
2
3
4
5
6
7
8
curl -X POST https://scaisend.scailabs.ai/v3/api_keys \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-sender",
    "environment": "live",
    "scopes": ["mail.send"]
  }'

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.write holders 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:

bash
1
2
curl https://scaisend.scailabs.ai/v3/auth/me \
  -H "Authorization: Bearer $SCAISEND_JWT"

Response includes permissions: [...] — the union of all permissions granted by direct role assignments and by group mappings.

For an API key, list it:

bash
1
2
curl https://scaisend.scailabs.ai/v3/api_keys \
  -H "Authorization: Bearer $SCAISEND_JWT"

Each key's scopes field is its effective permission set.

Custom roles#

Create a new role, then attach permissions:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1. Create the role
curl -X POST https://scaisend.scailabs.ai/api/admin/roles \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "billing-agent", "description": "Read stats and resend invoices"}'

# 2. Attach a full permission set
curl -X PUT https://scaisend.scailabs.ai/api/admin/roles/role_billing_agent/permissions \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"permission_ids": ["perm_stats_read", "perm_mail_send", "perm_templates_read"]}'

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#

Updated 2026-05-17 01:33:26 View source (.md) rev 1