---
title: Permissions and Access
path: concepts/permissions-and-access
status: published
---

# Permissions and Access

ScaiDNS uses a role-based access control model, scoped by platform / tenant / domain, with optional access grants for per-domain delegation. This page explains how authorization resolves when a request arrives.

## Four scopes

Every permission assignment is attached to exactly one of these scopes:

| Scope | Reach | Who has it |
|-------|-------|------------|
| `platform` | Across all tenants | Operators of the ScaiDNS instance |
| `tenant` | Everything in one tenant | Customer admins |
| `domain` | One specific zone | Users delegated individual zones |
| `record` | Specific records within a zone | Granular delegation via [access grants](../tutorials/access-grants.md) |

Higher scopes include lower ones. A platform admin automatically has tenant-admin rights on every tenant; a tenant admin automatically has domain admin rights on every zone in their tenant.

## System roles

ScaiDNS ships with six system roles. They're created in the database on first boot and can be assigned to users or groups at any applicable scope.

| Role | Typical scope | Permissions |
|------|---------------|-------------|
| `platform_admin` | `platform` | Full platform control — tenant management, platform config, cross-tenant audit |
| `tenant_admin` | `tenant` | Full tenant control — all zones, records, users, roles, API keys, audit within one tenant |
| `domain_admin` | `domain` | Full control over one zone — records, DNSSEC, access grants for that zone |
| `domain_manager` | `domain` | Manage records; can't change DNSSEC or delegate further |
| `record_editor` | `domain` | Create and modify records; no delete |
| `read_only` | any | Read access only |
| `validation_bypass` | `tenant` | Create domains without going through validation (trusted internal use) |

Custom roles can be created by tenant admins (`POST /api/v1/roles/`). They can only grant subsets of permissions that the creator has — you cannot create a role that grants more than you have.

## How a request is authorized

When a request arrives, ScaiDNS:

1. Authenticates the caller (JWT or API key).
2. Resolves the tenant context.
3. Computes **effective permissions** for the caller within that tenant and (optionally) that specific domain.
4. Checks whether the required permission for the requested action is present.

The `EffectivePermissions` object contains:

- `is_platform_admin` — true if any platform-scoped assignment grants it
- `is_tenant_admin` — true if any tenant-scoped assignment grants it
- `permissions` — an object like `{"domains": ["read", "create"], "records": ["read", "write"], ...}`

Endpoints check these flags or specific permissions. For example, `POST /api/v1/domains/{id}/records` requires `records:write` on that domain.

## Direct role assignment vs access grants

Two mechanisms give a user (or group) access to a domain:

### Role assignments

Attach a role at a scope:

```bash
# Give a user tenant_admin for their tenant
curl -X POST https://scaidns.scailabs.ai/api/v1/roles/users/$USER_ID \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "role_id": "r_tenant_admin_uuid",
    "scope": "tenant"
  }'

# Give a user domain_manager for one zone
curl -X POST https://scaidns.scailabs.ai/api/v1/roles/users/$USER_ID \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "role_id": "r_domain_manager_uuid",
    "scope": "domain",
    "scope_resource_id": "d_zone_uuid"
  }'
```

Role assignments live in the `user_roles` table. They're coarse-grained and durable.

### Access grants

More granular. Grant a user (or group) a specific role on a specific domain, optionally limited to record types or name patterns, optionally expiring:

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/access-grants \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "user",
    "grantee_id": "u_abc123",
    "role_id": "r_record_editor_uuid",
    "record_types": ["A", "AAAA", "CNAME"],
    "record_pattern": "*.staging",
    "expires_at": "2026-12-31T23:59:59Z",
    "notes": "Staging delegation for Q4"
  }'
```

Access grants are scoped to a single domain. Use them when:

- A contractor needs access to *one* zone for a bounded time.
- A team should only touch records matching a pattern (`*.dev`, `api.*`).
- An external service needs to manipulate only certain record types.

See [Access Grants](../tutorials/access-grants.md) for the full surface.

## Groups

Groups simplify management at scale. Assign a role to the group, and all members inherit it:

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/roles/groups/$GROUP_ID \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"role_id": "r_domain_manager_uuid", "scope": "tenant"}'
```

When a user is added to or removed from the group (managed in ScaiKey), their effective permissions update automatically.

## Checking what a user can do

```bash
curl https://scaidns.scailabs.ai/api/v1/roles/users/$USER_ID/permissions \
  -H "Authorization: Bearer $JWT"
```

Returns:

```json
{
  "is_platform_admin": false,
  "is_tenant_admin": true,
  "roles": [
    {"role_name": "tenant_admin", "scope": "tenant", "scope_resource_id": null}
  ],
  "permissions": {
    "domains": ["read", "create", "update", "delete"],
    "records": ["read", "create", "update", "delete"],
    "dnssec": ["read", "enable", "disable", "rotate"],
    "access_grants": ["read", "create", "update", "delete"]
  }
}
```

Add `?domain_id=<uuid>` to factor in per-domain grants.

## API key permissions

API keys don't carry their own permissions — they inherit from a **permission source**, either a user or a group:

- `user` — key inherits from the named user.
- `group` — key inherits from the named group.

If you revoke the user's access, all their API keys lose that access too. Keys are effectively shadow sessions for their permission source.

Keys **cannot be platform admins.** Even if the owning user is a platform admin, API keys are treated as non-platform. This is a deliberate constraint — platform-admin actions must flow through a human session.

## Permission categories

What exists in the `permissions` object:

| Category | Actions |
|----------|---------|
| `domains` | `read`, `create`, `update`, `delete` |
| `records` | `read`, `create`, `update`, `delete` |
| `dnssec` | `read`, `enable`, `disable`, `rotate` |
| `access_grants` | `read`, `create`, `update`, `delete` |
| `platform` | `config`, `audit`, `bypass_validation`, `manage_tenants` |

Custom roles define their own mix of these.

## What's next

- [Access Grants](../tutorials/access-grants.md) — granular per-domain delegation.
- [Users, Groups, and Roles](../reference/users-and-access.md) — endpoint reference.
- [API Keys](../reference/api-keys.md) — permission sources and scoping.
