post_roles_by_id_permissions
Calls POST /api/roles/{id}/permissions as the signed-in person · REST reference
Grant permissions to a role
Requires execute:AddPermissionToRoleCommand; platform staff bypass that gate. The caller's own token is forwarded, and the write is the DAL's UpdateRoleCommand: it requires execute:UpdateRoleCommand in the role's own organization and refuses — 403 — a system role, a role of the platform organization, or a role that belongs to no organization to anyone without platform authority. For such a caller, a role in an organization where they do not hold admin.roles.view is a 404. Permission names are stored only on a non-system role that belongs to no organization. For an organization's role, whose grants are :OrgPermission edges, the DAL refuses the write with 400; for platform staff on a system role, with 409. ADDITIVE AND IDEMPOTENT: permissions already held are filtered out, and if every submitted permission is already present the model skips the save entirely — the response is the same 200 either way, so it does not distinguish "granted" from "no change". Each new name must be execute: or query: followed by a PascalCase name ending in Command or Query; the DAL refuses any other string (400). Names are not checked against a catalogue. The response carries data: null — no role and no resulting permission set is returned.
Arguments
| Argument | Type | Description | |
|---|---|---|---|
idpath | string (uuid) | required | |
bodyJSON | object | required | The request body, as a JSON object. Its fields are listed below. |
Fields of body
| Field | Type | Description | |
|---|---|---|---|
permissions | array of string | required | Required and must be an array of strings. An empty array is accepted and is a no-op. |
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_by_id_permissions",
"arguments": {
"id": "11111111-1111-4111-8111-111111111111",
"body": {
"permissions": [
"string"
]
}
}
}
}Result
On success (HTTP 200) the tool returns one text item holding the API's JSON, pretty-printed.
Acknowledgement. data is explicitly null; nothing is returned.
{
"data": {},
"message": "Permissions added to role successfully",
"success": true
}Fields
| Field | Type | Description |
|---|---|---|
data | any | null | Always JSON null — the key is present because the controller passes an explicit null rather than omitting the argument. |
message | "Permissions added to role 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.
400idis not a UUID,permissionsis missing or not an array, or an element is not a string, with an ARRAY-shapeddetails; or the DAL refused the write — a permission name not of theexecute:/query:shape, or an organization's role — carrying the DAL's sentence.403Authenticated, but the caller holds none of the required permissions.
404No such role —
Role not foundwithcode: ROLE_NOT_FOUND— or a DAL NOT_FOUND refusal, which carries the DAL sentence and no code.409The resource is not in a state that allows this operation.
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_by_id_permissions",
"description": "POST /api/roles/{id}/permissions — Requires `execute:AddPermissionToRoleCommand`; platform staff bypass that gate. The caller's own token is forwarded, and the write is the DAL's `UpdateRoleCommand`: it requires `execute:UpdateRoleCommand` in the role's own organization and refuses — 403 — a system role, a role of the platform organization, or a role that belongs to no organization to anyone without platform authority. For such a caller, a role in an organization where they do not hold `admin.roles.view` is a 404. Permission names are stored only on a non-system role that belongs to no organization. For an organization's role, whose grants are `:OrgPermission` edges, the DAL refuses the write with 400; for platform staff on a system role, with 409. ADDITIVE AND IDEMPOTENT: permissions already held are filtered out, and if every submitted permission is already present the model skips the save entirely — the response is the same 200 either way, so it does not distinguish \"granted\" from \"no change\". Each new name must be `execute:` or `query:` followed by a PascalCase name ending in `Command` or `Query`; the DAL refuses any other string (400). Names are not checked against a catalogue. The response carries `data: null` — no role and no resulting permission set is returned.\n\nThis changes data. The person is asked to confirm it before it runs, and nothing happens if they decline.",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"body": {
"type": "string",
"description": "The JSON request body: { permissions*: array of string (Required and must be an array of strings.) }"
}
},
"required": [
"id",
"body"
]
},
"annotations": {
"readOnlyHint": false,
"destructiveHint": true,
"idempotentHint": false,
"openWorldHint": false
}
}