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

Multi-tenancy

ScaiSend is multi-tenant by default. Every object — API key, template, message, suppression, webhook endpoint, sender domain — belongs to a tenant, and every request resolves to exactly one tenant before any business logic runs. This page describes the hierarchy and how your credentials map to it.

The three levels#

text
1
2
3
Partner
  └── Tenant
        └── User

Partner. The top level. A partner is typically a reseller, an agency, or a platform building on top of ScaiSend. Partners group tenants together and can see aggregate stats across their tenants.

Tenant. An isolated environment within a partner. A single end customer of the platform. Tenants own the entities developers actually create: API keys, templates, messages, suppressions, webhook endpoints, sender domains. Cross-tenant visibility is not supported.

User. A human identity, scoped to a tenant via role assignments. A user can belong to multiple tenants by holding roles in each; the active tenant is resolved per request from the JWT claim.

Partner, tenant, and user identities all originate in ScaiKey. ScaiSend maintains local mirrors (the partners, tenants, users tables) kept in sync via webhooks and bulk sync.

How tenant is resolved#

Each request carries one credential. That credential determines the tenant:

Credential How tenant is resolved
API key (sg_live_* / sg_test_*) API key row has tenant_id; that's the tenant for every request using this key.
JWT JWT's tenant_id claim (may be a ScaiKey ID like tnt_abc123). Resolved to the local tenant record.

If the credential doesn't resolve to a tenant, the request returns 401. If the resolved tenant is suspended, the request returns 403.

Tenant-scoped objects#

The following are all scoped to the active tenant. You can never see or modify another tenant's:

  • API keys (/v3/api_keys)
  • Templates (/v3/templates)
  • Messages and events (/v3/messages)
  • Suppressions — bounces, spam reports, unsubscribes, groups (/v3/suppression/*, /v3/asm/*)
  • Statistics (/v3/stats*)
  • Webhook endpoints and signing secrets (/v3/user/webhooks)
  • Images (/v3/images)
  • Sender domains (/api/admin/domains)
  • Roles and role-to-user assignments (/api/admin/roles)
  • Tracking settings (/api/admin/tenants/{id}/tracking)

Partner-scoped visibility#

Users with a partner-level role can list tenants and aggregate stats across all tenants under their partner. This is for operators and resellers, not for sending mail.

bash
1
2
3
# As a partner-admin JWT
curl https://scaisend.scailabs.ai/api/admin/tenants?partner_id=prt_acme \
  -H "Authorization: Bearer $SCAISEND_JWT"

Returns every tenant under prt_acme. A tenant-scoped JWT gets only its own tenant back from this endpoint.

Creating tenants#

Tenants are created in ScaiKey, not in ScaiSend. ScaiKey emits a tenant.created webhook; ScaiSend receives it at /webhooks/scaikey and provisions the local record (including default roles and tracking settings).

If you need to bulk-import:

bash
1
scaisend sync --full

This walks the ScaiKey API and mirrors partners, tenants, users, and group memberships. Run it on first install and after any prolonged ScaiKey outage.

Group-to-role mapping#

ScaiKey groups can be mapped to ScaiSend roles. Every user in the group inherits the permissions of the mapped role, automatically, whenever their group membership changes.

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"}'

Nested groups inherit. If Backend Team is nested inside Engineering, and Engineering maps to the developer role, every member of Backend Team gets developer permissions without any additional mapping.

See Roles and Permissions for the roles themselves.

What tenants share#

Tenants share infrastructure but not data. In particular:

  • A shared sender domain (flagged with is_shared: true) can be used as a From: address by any tenant. Use this for transactional "noreply" addresses across your platform.
  • Global bounce suppression. If address X bounces hard for tenant A, tenant B can still send to X — each tenant maintains its own suppression list by default. (There's a platform-level bounce list for very large reputations; see Suppressions.)
  • The outbound SMTP IP pool is shared across the ScaiSend deployment. Use ip_pool_name on a send to route through a specific pool if your deployment is configured with multiple pools.

What tenants don't share#

  • API keys. A sg_live_* key for tenant A will never authenticate against tenant B's data.
  • Templates. Each tenant has its own template library.
  • Suppressions. Bounce, spam-report, and unsubscribe lists are independent per tenant.
  • Stats. Per-tenant daily counters, per-tenant category breakdowns.
  • Webhook endpoints. Each tenant subscribes its own endpoints.

Suspending a tenant#

Suspend in ScaiKey (not in ScaiSend). ScaiSend receives the tenant.suspended webhook and marks the tenant as suspended locally. From that point:

  • Every API request returns 403 TENANT_SUSPENDED.
  • Queued but unsent messages stay queued — they're not dropped — and resume on unsuspend.
  • Inbound bounces and FBL reports continue processing (you still want the suppression data).
  • Webhook deliveries continue draining to avoid losing visibility on in-flight mail.

What's next#

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