post_roles
Calls POST /api/roles as the signed-in person · REST reference
Create a role
Requires execute:CreateRoleCommand; platform staff bypass that gate. The caller's own token is forwarded to the DAL, and this route sends no orgId, so the role belongs to no organization: the DAL creates it only for a caller with platform authority and refuses anyone else with 403. Duplicate names are refused up front with 409 ROLE_NAME_CONFLICT, matched against non-archived roles only — a name freed by archiving can be reused. TWO FIELDS DO NOT PERSIST. isSystem is validated, assigned to the model and echoed in the 201, but the DAL's CreateRoleInput has no such field, so the stored role is never marked as a system role. And the id in the response is a uuid the MODEL generated before the call — CreateRoleInput carries no id, so the DAL assigns its own and the returned id may not identify the stored role. Read the role back with GET /api/roles before using its id. 201 with a message, in the { success, data, message } envelope.
Arguments
| Argument | Type | Description | |
|---|---|---|---|
bodyJSON | object | required | The request body, as a JSON object. Its fields are listed below. |
Fields of body
| Field | Type | Description | |
|---|---|---|---|
description | string | Optional here, but sent to the DAL as '' when omitted. | |
isSystem | boolean | Accepted, echoed, and NOT persisted — see the endpoint description. | |
name | string | required | Trimmed before validation. Must not collide with a live role. |
permissions | array of string |
Example call
A client sends this over the MCP session; Claude and other agents build it for you. The values are placeholders — the shape is real.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "post_roles",
"arguments": {
"body": {
"name": "string",
"description": "string",
"isSystem": true,
"permissions": [
"string"
]
}
}
}
}Result
On success (HTTP 201) the tool returns one text item holding the API's JSON, pretty-printed.
The in-memory Role model, echoed before the DAL entity is read back. ISO-8601 timestamps.
{
"data": {
"createdAt": "2026-01-15T12:00:00Z",
"groupCount": 0,
"id": "11111111-1111-4111-8111-111111111111",
"isSystem": true,
"metadata": {},
"name": "string",
"permissions": [
"string"
],
"status": "ACTIVE",
"createdBy": "11111111-1111-4111-8111-111111111111",
"description": "string"
},
"message": "Role created successfully",
"success": true
}Fields
| Field | Type | Description |
|---|---|---|
data | object | |
data.createdAt | string (date-time) | |
data.createdBy | string (uuid) | null | |
data.description | string | null | |
data.groupCount | 0 | |
data.id | string (uuid) | Model-generated; NOT necessarily the stored role's id. |
data.isSystem | boolean | Echoes the request; the stored role is not marked. |
data.metadata | object | |
data.name | string | |
data.permissions | array of string | |
data.status | "ACTIVE" | |
message | "Role created successfully" | |
success | true |
When it fails
A failed call returns isError: true with the API's message as text. A change the person declines, or lets time out, also comes back this way and nothing changes.
400express-validator rejected the body — missing/oversized
name, oversizeddescription, non-booleanisSystem, or a non-array/non-string-elementpermissions— with an ARRAY-shapeddetails; or the DAL's own validation refused it (a name that is not 2-50 letters, digits and underscores, a permission name not of theexecute:/query:shape), carrying the DAL's sentence and nodetails.403Authenticated, but the caller holds none of the required permissions.
409A live role already has that name — found by the controller's lookup (
Role name already exists,code: ROLE_NAME_CONFLICT) or by the DAL's own check (the DAL's sentence, no code).
What the model is shown
This is the tool exactly as the server lists it in tools/list. The description starts with the REST call it makes.
{
"name": "post_roles",
"description": "POST /api/roles — Requires `execute:CreateRoleCommand`; platform staff bypass that gate. The caller's own token is forwarded to the DAL, and this route sends no `orgId`, so the role belongs to no organization: the DAL creates it only for a caller with platform authority and refuses anyone else with 403. Duplicate names are refused up front with 409 `ROLE_NAME_CONFLICT`, matched against non-archived roles only — a name freed by archiving can be reused. TWO FIELDS DO NOT PERSIST. `isSystem` is validated, assigned to the model and echoed in the 201, but the DAL's `CreateRoleInput` has no such field, so the stored role is never marked as a system role. And the `id` in the response is a uuid the MODEL generated before the call — `CreateRoleInput` carries no id, so the DAL assigns its own and the returned id may not identify the stored role. Read the role back with `GET /api/roles` before using its id. 201 with a `message`, in the `{ success, data, message }` envelope.\n\nThis changes data. The person is asked to confirm it before it runs, and nothing happens if they decline.",
"inputSchema": {
"type": "object",
"properties": {
"body": {
"type": "string",
"description": "The JSON request body: { description: string (Optional here, but sent to the DAL as '' when omitted.); isSystem: boolean (Accepted, echoed, and NOT persisted — see the endpoint description.); name*: string (Trimmed before validation.); permissions: array of string }"
}
},
"required": [
"body"
]
},
"annotations": {
"readOnlyHint": false,
"destructiveHint": true,
"idempotentHint": false,
"openWorldHint": false
}
}