Roles
Roles are named collections of permissions assigned to your application's users. Every role belongs to a single application and can hold any number of permissions in resource:action format.
Role Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Unique role identifier |
application_id | UUID | The application this role belongs to |
name | string | Machine-readable identifier (e.g., editor) |
display_name | string | Human-readable label shown in your UI |
description | string | Optional description |
is_system_role | boolean | System roles cannot be modified or deleted |
permissions_count | integer | Number of permissions attached |
created_at | ISO 8601 | Creation timestamp |
updated_at | ISO 8601 | Last modification timestamp |
List Roles
GET /api/v1/applications/{applicationId}/roles
Requires JWT scope: roles:read
Query parameters:
| Parameter | Type | Description |
|---|---|---|
search | string | Filter by name or display_name |
include_permissions | boolean | Include full permissions array in response |
per_page | integer | Results per page (max 100, default 15) |
curl -X GET \
"https://api.yorauth.com/api/v1/applications/your-application-id/roles?include_permissions=true" \
-H "Authorization: Bearer <jwt>"
const response = await fetch(
'https://api.yorauth.com/api/v1/applications/your-application-id/roles?include_permissions=true',
{
headers: {
'Authorization': 'Bearer <jwt>'
}
}
);
const data = await response.json();
Response 200 OK:
{
"data": [
{
"id": "018e5f3a-1234-7000-8000-abcdef123456",
"application_id": "your-application-id",
"name": "editor",
"display_name": "Editor",
"description": "Can create and edit content",
"is_system_role": false,
"permissions_count": 3,
"permissions": [
{ "id": "...", "name": "posts:create", "resource": "posts", "action": "create", "description": null },
{ "id": "...", "name": "posts:update", "resource": "posts", "action": "update", "description": null },
{ "id": "...", "name": "posts:delete", "resource": "posts", "action": "delete", "description": null }
],
"created_at": "2026-02-01T10:00:00+00:00",
"updated_at": "2026-02-01T10:00:00+00:00"
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": null },
"meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 1 }
}
Get a Role
GET /api/v1/applications/{applicationId}/roles/{roleId}
Requires JWT scope: roles:read
Returns a single role with its full permissions list and the count of users assigned to it.
Response 200 OK:
{
"data": {
"id": "018e5f3a-1234-7000-8000-abcdef123456",
"application_id": "your-application-id",
"name": "editor",
"display_name": "Editor",
"description": "Can create and edit content",
"is_system_role": false,
"permissions_count": 3,
"permissions": [
{ "id": "...", "name": "posts:create", "resource": "posts", "action": "create", "description": null },
{ "id": "...", "name": "posts:update", "resource": "posts", "action": "update", "description": null },
{ "id": "...", "name": "posts:delete", "resource": "posts", "action": "delete", "description": null }
],
"users_count": 12,
"created_at": "2026-02-01T10:00:00+00:00",
"updated_at": "2026-02-01T10:00:00+00:00"
}
}
Create a Role
POST /api/v1/applications/{applicationId}/roles
Requires JWT scope: roles:manage
Request body:
| Field | Required | Validation |
|---|---|---|
name | Yes | Unique per application, max 100 chars |
display_name | Yes | Max 255 chars |
description | No | Any string |
permissions | Yes | Array of at least 1 resource:action strings |
is_system_role | No | Boolean, defaults to false |
Permissions are created automatically if they do not already exist for the application.
curl -X POST \
https://api.yorauth.com/api/v1/applications/your-application-id/roles \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "content_moderator",
"display_name": "Content Moderator",
"description": "Can review and moderate user-generated content",
"permissions": ["posts:read", "posts:delete", "comments:moderate"]
}'
const response = await fetch(
'https://api.yorauth.com/api/v1/applications/your-application-id/roles',
{
method: 'POST',
headers: {
'Authorization': 'Bearer <jwt>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'content_moderator',
display_name: 'Content Moderator',
description: 'Can review and moderate user-generated content',
permissions: ['posts:read', 'posts:delete', 'comments:moderate']
})
}
);
const data = await response.json();
Response 201 Created:
{
"data": {
"id": "018e5f3a-abcd-7000-8000-000000000001",
"application_id": "your-application-id",
"name": "content_moderator",
"display_name": "Content Moderator",
"description": "Can review and moderate user-generated content",
"is_system_role": false,
"permissions_count": 3,
"permissions": [
{ "id": "...", "name": "posts:read", "resource": "posts", "action": "read", "description": null },
{ "id": "...", "name": "posts:delete", "resource": "posts", "action": "delete", "description": null },
{ "id": "...", "name": "comments:moderate", "resource": "comments", "action": "moderate", "description": null }
],
"created_at": "2026-02-25T14:30:00+00:00",
"updated_at": "2026-02-25T14:30:00+00:00"
}
}
Error responses:
422 Unprocessable Entity— Validation failed (e.g., duplicatename, invalid permission format)402 Payment Required— Role limit reached for your plan
Update a Role
PUT /api/v1/applications/{applicationId}/roles/{roleId}
PATCH /api/v1/applications/{applicationId}/roles/{roleId}
Requires JWT scope: roles:manage
All fields are optional. When permissions is provided, it replaces the current permission set entirely (sync, not append). Omit permissions to leave permissions unchanged.
System roles (is_system_role: true) cannot be updated. The request will be rejected with 403 Forbidden.
Request body:
{
"display_name": "Senior Content Moderator",
"description": "Updated description",
"permissions": ["posts:read", "posts:delete", "comments:moderate", "reports:view"]
}
Response 200 OK: Returns the updated role object.
Updating permissions invalidates the permission cache for all users with this role immediately.
Delete a Role
DELETE /api/v1/applications/{applicationId}/roles/{roleId}
Requires JWT scope: roles:manage
Response 204 No Content on success.
Error responses:
403 Forbidden— Cannot delete a system role409 Conflict— Role is assigned to one or more users or teams; remove all assignments first
Assign a Role to a User
POST /api/v1/applications/{applicationId}/users/{userId}/roles
Requires JWT scope: roles:manage
The user_id is your external user identifier — the same string you use in auth tokens.
Request body:
| Field | Required | Description |
|---|---|---|
role_id | Yes | UUID of the role to assign |
scope | No | Optional scope string (e.g., org:acme-corp) |
expires_at | No | ISO 8601 datetime; role expires and is ignored after this point |
curl -X POST \
https://api.yorauth.com/api/v1/applications/your-application-id/users/user-123/roles \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"role_id": "018e5f3a-abcd-7000-8000-000000000001",
"scope": "org:acme-corp",
"expires_at": "2027-01-01T00:00:00Z"
}'
const response = await fetch(
'https://api.yorauth.com/api/v1/applications/your-application-id/users/user-123/roles',
{
method: 'POST',
headers: {
'Authorization': 'Bearer <jwt>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
role_id: '018e5f3a-abcd-7000-8000-000000000001',
scope: 'org:acme-corp',
expires_at: '2027-01-01T00:00:00Z'
})
}
);
Response 201 Created:
{
"data": {
"id": "018e5f3a-0001-7000-8000-000000000099",
"application_id": "your-application-id",
"user_id": "user-123",
"role_id": "018e5f3a-abcd-7000-8000-000000000001",
"role_name": "content_moderator",
"role_display_name": "Content Moderator",
"scope": "org:acme-corp",
"granted_at": "2026-02-25T14:30:00+00:00",
"expires_at": "2027-01-01T00:00:00+00:00"
}
}
Error responses:
409 Conflict— Role is already assigned to this user with the same scopejson{ "error": { "code": "AUTHZ_ROLE_ALREADY_ASSIGNED", "message": "Role already assigned to this user." } }
Side effects: the role.assigned webhook event fires, an audit log entry is created, and the user's permission cache is invalidated.
List User Roles
GET /api/v1/applications/{applicationId}/users/{userId}/roles
Requires JWT scope: roles:read
Query parameters:
| Parameter | Description |
|---|---|
scope | Filter assignments to a specific scope |
Response 200 OK:
{
"data": [
{
"id": "018e5f3a-0001-7000-8000-000000000099",
"role_id": "018e5f3a-abcd-7000-8000-000000000001",
"role_name": "content_moderator",
"role_display_name": "Content Moderator",
"scope": "org:acme-corp",
"granted_at": "2026-02-25T14:30:00+00:00",
"expires_at": "2027-01-01T00:00:00+00:00"
}
],
"user_id": "user-123",
"scope": "org:acme-corp"
}
Revoke a Role from a User
DELETE /api/v1/applications/{applicationId}/users/{userId}/roles/{roleId}
Requires JWT scope: roles:manage
Query parameters:
| Parameter | Description |
|---|---|
scope | Required if the role was assigned with a scope; omit for global assignments |
# Revoke scoped role
curl -X DELETE \
"https://api.yorauth.com/api/v1/applications/your-application-id/users/user-123/roles/018e5f3a-abcd-7000-8000-000000000001?scope=org:acme-corp" \
-H "Authorization: Bearer <jwt>"
Response 204 No Content on success.
Error responses:
404 Not Found— Assignment not foundjson{ "error": { "code": "AUTHZ_ROLE_ASSIGNMENT_NOT_FOUND", "message": "Role assignment not found." } }
Side effects: the role.removed webhook event fires, an audit log entry is created, and the user's permission cache is invalidated.
Get User Permissions (Computed)
GET /api/v1/applications/{applicationId}/users/{userId}/permissions
Requires JWT scope: roles:read
Returns the full set of permissions a user holds across all their assigned roles, including permissions inherited via team membership.
Query parameters:
| Parameter | Description |
|---|---|
scope | If provided, returns permissions from global roles plus roles matching this scope |
Response 200 OK:
{
"data": {
"user_id": "user-123",
"scope": "org:acme-corp",
"permissions": [
"posts:read",
"posts:delete",
"comments:moderate",
"reports:view"
],
"roles": [
{ "id": "...", "name": "content_moderator", "display_name": "Content Moderator" }
]
}
}
This endpoint returns computed permissions — the union of all permissions from all active (non-expired) roles. It is useful for seeding client-side permission checks, but for server-side enforcement always use the authorization check endpoint.