Roles API
CRUD operations for RBAC roles within an application, plus endpoints to assign and revoke roles on users and retrieve a user's computed permissions.
Authentication
All endpoints require a valid JWT access token:
Authorization: Bearer {access_token}
Role read operations require the roles:read permission on the token. Role write operations require roles:manage.
List Roles
GET /api/v1/applications/{applicationId}/roles
List all roles for the application. Supports search and pagination.
Authentication
Requires jwt.permission:roles:read.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
search | string | Filter by role name or display_name |
include_permissions | boolean | When true, includes the full permissions array for each role |
per_page | integer | Records per page. Default 15, max 100. |
page | integer | Page number |
Response
200 OK
{
"data": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "admin",
"display_name": "Administrator",
"description": "Full access to all resources",
"is_system_role": true,
"permissions_count": 12,
"created_at": "2026-01-01T00:00:00Z"
}
],
"current_page": 1,
"last_page": 1,
"per_page": 15,
"total": 3
}
Create Role
POST /api/v1/applications/{applicationId}/roles
Create a new role and attach a set of permissions to it. Permission names follow resource:action format.
Authentication
Requires jwt.permission:roles:manage.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique machine-readable identifier within the application. Max 100 characters. |
display_name | string | Yes | Human-readable name shown in the UI. Max 255 characters. |
description | string | No | Optional description of the role's purpose. |
permissions | array of strings | Yes | At least one permission in resource:action format. Wildcards supported: posts:*, *:read. |
is_system_role | boolean | No | Marks role as system-managed. System roles cannot be deleted. Default false. |
Response
201 Created
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "editor",
"display_name": "Editor",
"description": "Can create and edit content",
"is_system_role": false,
"permissions": ["posts:read", "posts:create", "posts:update"],
"permissions_count": 3,
"created_at": "2026-02-25T12:00:00Z"
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_MULTIPLE_ERRORS | One or more fields are invalid |
| 409 | RESOURCE_ALREADY_EXISTS | A role with that name already exists in this application |
Get Role
GET /api/v1/applications/{applicationId}/roles/{role}
Retrieve a single role with its full permissions list and user count.
Authentication
Requires jwt.permission:roles:read.
Response
200 OK
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "editor",
"display_name": "Editor",
"description": "Can create and edit content",
"is_system_role": false,
"permissions": [
{
"id": "a1b2c3d4-...",
"name": "posts:read"
},
{
"id": "e5f6g7h8-...",
"name": "posts:create"
}
],
"permissions_count": 2,
"users_count": 14,
"created_at": "2026-02-25T12:00:00Z",
"updated_at": "2026-02-25T12:00:00Z"
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 404 | RESOURCE_NOT_FOUND | Role not found |
Update Role
PUT /api/v1/applications/{applicationId}/roles/{role}
Update a role's display name, description, and permissions. The role name is immutable after creation. System roles cannot be modified.
PATCH /api/v1/applications/{applicationId}/roles/{role} is also accepted.
Authentication
Requires jwt.permission:roles:manage.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
display_name | string | No | New display name |
description | string | No | New description |
permissions | array of strings | No | Replaces the entire permissions set for this role |
Response
200 OK
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "editor",
"display_name": "Content Editor",
"description": "Updated description",
"is_system_role": false,
"permissions_count": 5,
"created_at": "2026-02-25T12:00:00Z",
"updated_at": "2026-02-25T14:00:00Z"
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 403 | — | Role is a system role and cannot be modified |
| 404 | RESOURCE_NOT_FOUND | Role not found |
Delete Role
DELETE /api/v1/applications/{applicationId}/roles/{role}
Delete a role. System roles and roles that are currently assigned to users cannot be deleted.
Authentication
Requires jwt.permission:roles:manage.
Response
204 No Content
Error Responses
| Status | Code | Description |
|---|---|---|
| 403 | — | Role is a system role |
| 404 | RESOURCE_NOT_FOUND | Role not found |
| 409 | — | Role is currently assigned to one or more users |
List User Roles
GET /api/v1/applications/{applicationId}/users/{userId}/roles
List all roles assigned to a specific user. Optionally filter by scope.
Authentication
Requires jwt.permission:roles:read.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
scope | string | Filter by assignment scope (e.g., org_123) |
Response
200 OK
{
"data": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "editor",
"display_name": "Editor",
"scope": null,
"expires_at": null,
"assigned_at": "2026-02-20T10:00:00Z"
}
],
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"scope": null
}
Assign Role to User
POST /api/v1/applications/{applicationId}/users/{userId}/roles
Assign a role to a user. Optionally scoped and with an expiry date.
Authentication
Requires jwt.permission:roles:manage.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
role_id | string (UUID) | Yes | The ID of the role to assign |
scope | string | No | Optional scope string (e.g., a resource ID or organization ID) |
expires_at | string (ISO 8601) | No | Optional expiry date for the role assignment. Must be in the future. |
Response
201 Created
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"role_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"scope": null,
"expires_at": null,
"assigned_at": "2026-02-25T12:00:00Z"
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
| 409 | AUTHZ_ROLE_ALREADY_ASSIGNED | The role is already assigned to this user (with the same scope) |
Revoke Role from User
DELETE /api/v1/applications/{applicationId}/users/{userId}/roles/{roleId}
Remove a role assignment from a user. If the role was assigned with a scope, pass the same scope as a query parameter.
Authentication
Requires jwt.permission:roles:manage.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
scope | string | Required if the role was assigned with a scope |
Response
204 No Content
Error Responses
| Status | Code | Description |
|---|---|---|
| 404 | AUTHZ_ROLE_ASSIGNMENT_NOT_FOUND | No matching role assignment found |
Get User Permissions
GET /api/v1/applications/{applicationId}/users/{userId}/permissions
Get the full set of permissions computed from all roles assigned to a user. Optionally filtered by scope.
Authentication
Requires jwt.permission:roles:read.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
scope | string | Filter by assignment scope |
Response
200 OK
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"scope": null,
"permissions": [
"posts:read",
"posts:create",
"posts:update",
"comments:read"
],
"roles": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "editor"
}
]
}
}