Skip to main content

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

FieldTypeDescription
idUUIDUnique role identifier
application_idUUIDThe application this role belongs to
namestringMachine-readable identifier (e.g., editor)
display_namestringHuman-readable label shown in your UI
descriptionstringOptional description
is_system_rolebooleanSystem roles cannot be modified or deleted
permissions_countintegerNumber of permissions attached
created_atISO 8601Creation timestamp
updated_atISO 8601Last modification timestamp

List Roles

text
GET /api/v1/applications/{applicationId}/roles

Requires JWT scope: roles:read

Query parameters:

ParameterTypeDescription
searchstringFilter by name or display_name
include_permissionsbooleanInclude full permissions array in response
per_pageintegerResults per page (max 100, default 15)
bash
curl -X GET \
  "https://api.yorauth.com/api/v1/applications/your-application-id/roles?include_permissions=true" \
  -H "Authorization: Bearer <jwt>"
javascript
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:

json
{
  "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

text
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:

json
{
  "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

text
POST /api/v1/applications/{applicationId}/roles

Requires JWT scope: roles:manage

Request body:

FieldRequiredValidation
nameYesUnique per application, max 100 chars
display_nameYesMax 255 chars
descriptionNoAny string
permissionsYesArray of at least 1 resource:action strings
is_system_roleNoBoolean, defaults to false

Permissions are created automatically if they do not already exist for the application.

bash
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"]
  }'
javascript
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:

json
{
  "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., duplicate name, invalid permission format)
  • 402 Payment Required — Role limit reached for your plan

Update a Role

text
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:

json
{
  "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

text
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 role
  • 409 Conflict — Role is assigned to one or more users or teams; remove all assignments first

Assign a Role to a User

text
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:

FieldRequiredDescription
role_idYesUUID of the role to assign
scopeNoOptional scope string (e.g., org:acme-corp)
expires_atNoISO 8601 datetime; role expires and is ignored after this point
bash
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"
  }'
javascript
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:

json
{
  "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 scope
    json
    { "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

text
GET /api/v1/applications/{applicationId}/users/{userId}/roles

Requires JWT scope: roles:read

Query parameters:

ParameterDescription
scopeFilter assignments to a specific scope

Response 200 OK:

json
{
  "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

text
DELETE /api/v1/applications/{applicationId}/users/{userId}/roles/{roleId}

Requires JWT scope: roles:manage

Query parameters:

ParameterDescription
scopeRequired if the role was assigned with a scope; omit for global assignments
bash
# 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 found
    json
    { "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)

text
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:

ParameterDescription
scopeIf provided, returns permissions from global roles plus roles matching this scope

Response 200 OK:

json
{
  "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.