---
title: Custom Roles
path: advanced/custom-roles
status: published
---

# Custom Roles

The six built-in roles cover 80% of needs. For the remaining 20% — cross-cutting access patterns, module-specific personas, tenant-specific hierarchies — use **custom roles**. Compose exactly the permissions you want from the platform's core + module permission set, assign to users.

For basics, see [Roles and Permissions](../03-core-concepts/02-roles-and-permissions.md).

## Anatomy of a custom role

```json
{
  "id": "role_abc",
  "tenant_id": "tenant_acme",
  "name": "Analytics Team",
  "slug": "analytics",
  "description": "Read-only usage + export, no admin access",
  "core_permissions": [
    "accounting:view_tenant",
    "models:list"
  ],
  "module_permissions": [
    "scaimatrix:view",
    "scaimatrix:search"
  ],
  "created_by": "user_tenant_admin",
  "created_at": "2026-04-22T10:00:00Z",
  "updated_at": "..."
}
```

- **Tenant-scoped.** Custom roles live inside a tenant. A super admin could create a shared template, but the role instances are per-tenant.
- **Composable.** Mix core permissions with module permissions freely. Must be a subset of what the tenant has enabled.
- **Additive.** Assigning a custom role doesn't revoke built-in role permissions; both stack. User's effective permission set is the union.

## Design patterns

### Pattern 1: Read-only specialist

Most common pattern — give a team read access to something but not write:

```json
{
  "name": "Support Read-Only",
  "slug": "support-ro",
  "core_permissions": ["models:list", "accounting:view_own"],
  "module_permissions": [
    "scaibot:bots:read",
    "scaibot:conversations:read",
    "scaimatrix:search"
  ]
}
```

Support team can look at bots, see conversation histories, search the knowledge base — but can't modify anything. Useful for first-line support who observe but don't configure.

### Pattern 2: Domain-scoped power user

Grant full control over one module, no other admin:

```json
{
  "name": "Knowledge Administrator",
  "slug": "knowledge-admin",
  "core_permissions": ["models:use"],
  "module_permissions": [
    "scaimatrix:view",
    "scaimatrix:search",
    "scaimatrix:ingest",
    "scaimatrix:manage",
    "scaimatrix:graph_edit",
    "scaimatrix:access"
  ]
}
```

The owner of your knowledge base can do anything within ScaiMatrix but can't touch bots, accounting, users, other modules.

### Pattern 3: Service account with narrow scope

For a specific integration's API key — narrow it to exactly what it needs:

```json
{
  "name": "Analytics Service",
  "slug": "svc-analytics",
  "core_permissions": ["accounting:view_tenant", "models:list"],
  "module_permissions": []
}
```

Create a user for the service, assign this role, create an API key under that user. The key can read usage; it can't do anything else even if leaked.

### Pattern 4: Escalation handler

Route human-in-the-loop decisions:

```json
{
  "name": "Checkpoint Resolver",
  "slug": "resolver",
  "core_permissions": [],
  "module_permissions": [
    "scaicore:view",
    "scaicore:checkpoint_resolve",
    "scaiqueue:view",
    "scaiqueue:consume"
  ]
}
```

People with this role handle human-in-the-loop tasks queued by agents. They can see what's pending, resolve it, but can't change how agents themselves are configured.

## Managing custom roles

### Creating

```bash
curl -X POST https://scaigrid.scailabs.ai/v1/custom-roles \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d @role-definition.json
```

Validates that all requested permissions exist and that the tenant has the relevant modules enabled.

### Listing available permissions

Before building a role, see what's available:

```bash
curl https://scaigrid.scailabs.ai/v1/custom-roles/available-permissions \
  -H "Authorization: Bearer $ADMIN_TOKEN"
```

Returns core permissions plus permissions from every enabled module, grouped by module. The admin UI has a visual picker built on this.

### Assigning to users

```bash
curl -X PUT https://scaigrid.scailabs.ai/v1/users/{user_id}/roles \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "roles": ["tenant_user"],
    "custom_role_ids": ["role_analytics", "role_knowledge_admin"]
  }'
```

A user can have multiple custom roles. Effective permissions = union of all assigned roles + any directly-granted module permissions.

### Updating

```bash
curl -X PUT https://scaigrid.scailabs.ai/v1/custom-roles/{role_id} \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"module_permissions": [...updated list...]}'
```

Changes apply to all users with the role immediately, on their next request.

### Deleting

```bash
curl -X DELETE https://scaigrid.scailabs.ai/v1/custom-roles/{role_id} \
  -H "Authorization: Bearer $ADMIN_TOKEN"
```

Users who had the role lose its permissions. Their built-in roles remain.

## Precedence and conflicts

A user's effective permissions come from:

1. Built-in roles (union of permission sets)
2. Custom roles (union)
3. Direct module permission grants

All three contribute. There's no negation — you can't have a role that revokes a permission. If a user has `tenant_user` (which includes `models:use`) plus a custom role without it, they still have `models:use`.

To restrict a user, give them only what they need — don't try to "subtract" permissions.

## Anti-patterns to avoid

- **One role per user.** If you're creating a unique role for every user, use direct module permission grants instead — they're per-user by design.
- **Mirror of built-in roles.** Don't re-create `tenant_admin` as a custom role. Use the built-in.
- **Too many roles, too similar.** If you have `analytics-ro`, `analytics-export`, `analytics-dashboard`, consider whether one role covers all three cases.

## Group mappings + custom roles

Group mappings can target custom roles as well as built-in:

```bash
curl -X POST https://scaigrid.scailabs.ai/v1/role-mappings \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scaikey_group": "acme-support-team",
    "scaigrid_role": "support-ro",
    "tenant_id": "tenant_acme"
  }'
```

Users in the ScaiKey group automatically get the custom role on authentication. Remove from the group → auto-revoke (if configured).

## Related

- [Roles and Permissions](../03-core-concepts/02-roles-and-permissions.md)
- [Users and Access Reference](../06-reference/02-users-and-access.md)
- [Authentication](../02-getting-started/02-authentication.md)
