### 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.

