# Roles - X-Radius API

> Base URL: https://x-radius.com/api/v1
> Auth: Authorization: Bearer xrt_...  (a manager API token)
> Envelope: {"data": ...}; lists add {"meta":{page,page_size,total,has_next}}
> Errors: {"error":{"code","message","request_id"}} - branch on code, never on message
> Timestamps: yyyy-MM-dd HH:mm:ss, UTC
> Money: a bare JSON number in major units, with an ISO-4217 currency code beside it
> Idempotency: redeem and activate endpoints take a client-supplied request_id (UUID)
>
> This page: https://x-radius.com/docs/api/roles
> Every group: https://x-radius.com/llms.txt

7 endpoints in 1 resource groups. 6 carry a hand-written reference entry with examples; the remaining 1 are generated from the running router and carry method, path, authentication, permission and rate-limit bucket, but no request or response example.

### List roles

`GET /api/v1/roles`

Every role visible to the tenant, with its permission count and the exact code set it grants. System roles sort first.

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger

#### Response — 200 OK

```json
{
  "data": [
    {
      "id": 2,
      "tenant_id": 12,
      "name": "support",
      "description": "Read subscribers, reply to tickets",
      "is_system": false,
      "template_key": "support",
      "immutable": false,
      "permission_count": 14,
      "permissions": ["prm_tickets_reply", "prm_tickets_view", "prm_users_index"]
    }
  ]
}
```

#### Note

Un-paginated. template_key is the provisioning slug a seeded role came from, and is null for a hand-made one — it is populated by this list only, not by the role pickers.


### Create a role

`POST /api/v1/roles`

Creates a named permission set. A caller may only grant codes they hold themselves, so a role cannot be used to mint authority its author does not have.

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes | Unique within the tenant. Reserved names are refused. |
| `permissions` | body | array | no | Permission codes from the catalogue. Unknown codes are rejected, not dropped. |

#### Request

```json
{
  "name": "front-desk",
  "description": "Sell cards, read subscribers",
  "permissions": ["prm_users_index", "prm_cards_verify", "prm_pos_sell"]
}
```

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_VALIDATION` | 400 | an unknown permission code, or a reserved role name |
| `ERR_FORBIDDEN` | 403 | the role would grant a permission the caller does not hold |
| `ERR_VALIDATION` | 409 | the role name already exists in this tenant |

#### Note

The containment rule is the point of this endpoint. You cannot author a role carrying a permission you lack, and you cannot assign someone else's role that carries one either — the assign path runs the same check, because otherwise the author check would be trivially bypassed.


### Manage roles & permissions

`POST /api/v1/roles/bulk-delete`

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger
- Rate limit bucket: `t_mutate`

_This endpoint has no hand-written reference entry yet. The method, path, authentication, permission and rate limit above are generated from the running router and are accurate; there is no request or response example._


### Delete a role

`DELETE /api/v1/roles/{id}`

Removes the role. System and immutable roles are refused, and so is a role that is still assigned.

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Role id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_CONFLICT` | 409 | the role is still assigned to a manager |


### Fetch one role

`GET /api/v1/roles/{id}`

The role and the exact set of permission codes it grants.

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Role id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_NOT_FOUND` | 404 | no such role in this tenant |


### Update a role

`PATCH /api/v1/roles/{id}`

Edits the name, description and permission set. The immutable administrator role cannot be edited at all.

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Role id. |

#### Errors

| Code | Status | When |
| --- | --- | --- |
| `ERR_FORBIDDEN` | 403 | the edit would add a permission the caller does not hold |

#### Note

Changing a role changes the authority of every manager holding it, and every API token those managers own, on the next request. There is no notification and no record against the affected tokens.


### Set a role's subscriber-portal permissions

`PUT /api/v1/roles/{id}/portal-permissions`

The separate set that governs what a SUBSCRIBER may do in the self-service portal, not what a manager may do in the admin API. Different catalogue, different meaning.

- Authentication: manager session (JWT) or API token
- Permission: `prm_roles_manage` (Manage roles & permissions)
- Risk: danger
- Rate limit bucket: `t_mutate`

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | Role id. |

#### Note

These are not `prm_*` codes and they do not affect the admin API at all. Confusing the two sets is the usual cause of "I gave the role the permission and nothing changed".


