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

Admin Reference

Admin endpoints — tenants, partners, roles, permissions, group mappings, sender domains, and tracking settings. These are ScaiSend-native surfaces, not SendGrid-compatible.

Base path: /api/admin/ Auth: JWT-only for most endpoints. API keys can read a narrow subset (domains.read, domains.write for domain endpoints; admin.settings for tracking settings).

Partners#

GET /api/admin/partners#

List partners. A tenant-scoped JWT returns only its own partner.

Response (200):

json
1
2
3
4
5
{
  "data": [
    {"id": "prt_acme", "name": "Acme Inc", "slug": "acme"}
  ]
}

GET /api/admin/partners/{partner_id}#

Get a single partner. partner_id can be the local UUID or the ScaiKey ID (prt_xxx).

Response (200): {"data": {...}}.

Tenants#

GET /api/admin/tenants#

List tenants.

Query parameters:

Parameter Notes
partner_id Filter by partner (UUID or prt_xxx)

Non-admin users see only their own tenant. Partner-admin users see every tenant in their partner.

Response (200):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "data": [
    {
      "id": "tnt_01HXYZ",
      "scaikey_id": "tnt_acme",
      "name": "Acme Corp",
      "slug": "acme-corp",
      "partner_id": "prt_01HXYZ",
      "settings": {}
    }
  ]
}

GET /api/admin/tenants/{tenant_id}#

Get a tenant. tenant_id may be the local UUID or the ScaiKey ID.

Response (200): {"data": {...}}.

PATCH /api/admin/tenants/{tenant_id}#

Update a tenant's name or settings.

Request body:

Field Notes
name New display name
settings Arbitrary JSON

GET /api/admin/tenants/{tenant_id}/tracking#

Get tracking settings for a tenant.

Response (200):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "data": {
    "open_tracking_enabled": true,
    "click_tracking_enabled": true,
    "click_tracking_enable_text": false,
    "subscription_tracking_enabled": false,
    "subscription_tracking_text": "",
    "subscription_tracking_html": "",
    "subscription_tracking_substitution_tag": "",
    "subscription_tracking_landing_html": "",
    "ganalytics_enabled": false,
    "ganalytics_utm_source": "",
    "ganalytics_utm_medium": "",
    "ganalytics_utm_campaign": "",
    "ganalytics_utm_term": "",
    "ganalytics_utm_content": "",
    "image_embed_mode": "proxy"
  }
}

PUT /api/admin/tenants/{tenant_id}/tracking#

Update tracking settings. Partial — unspecified fields keep their value.

Request body: any subset of the response fields above.

Response (200): updated settings.

Permission: admin.settings.

Roles and permissions#

GET /api/admin/permissions#

List all permissions.

Query parameters:

Parameter Notes
category Filter by category

Response (200):

json
1
2
3
[
  {"id": "perm_mail_send", "name": "mail.send", "description": "Send emails", "category": "mail"}
]

GET /api/admin/permissions/{permission_id}#

Get a single permission.

GET /api/admin/roles#

List roles.

Query parameters:

Parameter Notes
include_permissions If true, embed the permissions array

Response (200):

json
1
2
3
4
5
6
7
8
9
[
  {
    "id": "role_admin",
    "name": "admin",
    "description": "Full administrative access",
    "is_system": false,
    "permissions": [...]
  }
]

POST /api/admin/roles#

Create a role.

Request body:

Field Type Required
name string Yes
description string No
permission_ids array of string No

Response (201): role object.

GET /api/admin/roles/{role_id}#

Get a role with permissions.

PATCH /api/admin/roles/{role_id}#

Update name or description.

DELETE /api/admin/roles/{role_id}#

Delete a role. Users holding this role lose these permissions.

PUT /api/admin/roles/{role_id}/permissions#

Replace the role's permission set.

Request body: {"permission_ids": ["perm_mail_send", "perm_stats_read"]}

Response (200): list of permissions now on the role.

POST /api/admin/roles/{role_id}/permissions/{permission_id}#

Add a single permission.

DELETE /api/admin/roles/{role_id}/permissions/{permission_id}#

Remove a single permission.

User roles#

GET /api/admin/users/{user_id}/roles#

List roles held by a user.

Response (200): [RoleResponse].

POST /api/admin/users/{user_id}/roles/{role_id}#

Assign a role to a user.

Response (204): no body.

DELETE /api/admin/users/{user_id}/roles/{role_id}#

Remove a role assignment.

Response (204): no body.

Group mappings#

GET /api/admin/group-mappings#

List ScaiKey group-to-role mappings.

Response (200):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[
  {
    "id": "gm_01HXYZ",
    "group_id": "grp_01HXYZ",
    "group_name": "Engineering",
    "scaikey_group_id": "grp_engineering",
    "role_id": "role_developer",
    "role_name": "developer",
    "created_at": "2026-04-23T10:00:00Z"
  }
]

POST /api/admin/group-mappings#

Create a mapping.

Request body:

Field Required
group_id Yes
role_id Yes

DELETE /api/admin/group-mappings/{mapping_id}#

Delete a mapping.

Sender domains#

GET /api/admin/domains#

List domains.

Query parameters:

Parameter Notes
verified_only Return only verified domains
page 1-indexed
page_size Items per page

Response (200):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "id": "dom_01HXYZ",
    "domain": "mail.example.com",
    "verified": true,
    "verified_at": "2026-04-23T10:00:00Z",
    "dkim_selector": "scaisend",
    "dmarc_policy": "quarantine",
    "dmarc_rua_email": "dmarc@example.com",
    "is_shared": false,
    "is_active": true,
    "created_at": "2026-04-23T09:00:00Z"
  }
]

POST /api/admin/domains#

Add a domain. ScaiSend generates DKIM keypair and returns DNS records to publish.

Request body:

Field Required Notes
domain Yes Fully qualified domain
is_shared No If true, usable by any tenant under this partner
dmarc_policy No none, quarantine, or reject
dmarc_rua_email No Where DMARC aggregate reports go

Response (201):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "id": "dom_01HXYZ",
  "domain": "mail.example.com",
  "verified": false,
  "dkim_selector": "scaisend",
  "dns_records": [
    {"type": "TXT", "host": "scaisend._domainkey.mail.example.com", "value": "v=DKIM1; ..."},
    {"type": "TXT", "host": "mail.example.com", "value": "v=spf1 ..."},
    {"type": "TXT", "host": "_dmarc.mail.example.com", "value": "v=DMARC1; ..."}
  ]
}

Permission: domains.write.

GET /api/admin/domains/{domain_id}#

Get a domain with DNS records.

PATCH /api/admin/domains/{domain_id}#

Update domain fields.

Request body: any subset of:

Field Notes
is_active Deactivate to prevent further sends
is_shared Toggle shared flag
dmarc_policy Change policy
dmarc_rua_email Change report destination

DELETE /api/admin/domains/{domain_id}#

Delete a domain. Future sends from this domain fail.

POST /api/admin/domains/{domain_id}/verify#

Run the DNS verification check.

Response (200):

json
1
2
3
4
5
6
7
8
{
  "domain": "mail.example.com",
  "verified": true,
  "dkim_verified": true,
  "spf_verified": true,
  "txt_verified": true,
  "errors": []
}

Permission: domains.write.

POST /api/admin/domains/{domain_id}/rotate-dkim#

Generate a new DKIM keypair under a new selector. Old selector remains active until explicitly removed.

Response (200):

json
1
2
3
4
5
6
{
  "domain": "mail.example.com",
  "new_selector": "scaisend2",
  "dkim_public_key": "v=DKIM1; k=rsa; p=...",
  "dns_records": [...]
}

GET /api/admin/domains/{domain_id}/dns-records#

Get the list of DNS records currently required. Idempotent — use if you lost the original POST response.

Response (200):

json
1
2
3
4
5
[
  {"type": "TXT", "host": "scaisend._domainkey.mail.example.com", "value": "v=DKIM1; ..."},
  {"type": "TXT", "host": "mail.example.com", "value": "v=spf1 ..."},
  {"type": "TXT", "host": "_dmarc.mail.example.com", "value": "v=DMARC1; ..."}
]
Updated 2026-05-17 01:33:27 View source (.md) rev 1