Replace an organization role's permissions
/api/v1/organizations/{id}/roles/{roleId}/permissionsReplaces the permissions of a role the organization owns. A permission the role already carries may stay or go; one being added must be held by the caller at the organization node when the caller is an identity (403 organization.role_permission_not_held). Adding canopy:roles.manage to a role held beneath the organization node is refused (400 rbac.roles_manage_below_organization). The role's version moves with the change; send If-Match to refuse a stale write. Requires rbac.manage_roles or the organization's roles delegation.
Authentication
Authorization Option A JWT access token. Never send alongside X-API-Key: a request carrying both is refused.
X-API-Key Option B API key for management-tier access. Never send alongside an Authorization header: a request carrying both is refused.
Path Parameters
idstring Required roleIdstring Required Headers
If-MatchOptimistic-lock version of the resource. Send the `version` field returned on read; the server rejects with 409 if the row has changed since.
Request body
application/json
permission_keysstring[] Required Responses
application/json
items *PermissionResponseDto[]
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
Errors
When the request can't be completed, the response body includes a stable error code you can branch on, and a correlation_id (also sent as the X-Correlation-Id header). Quote the correlation id when you contact support: it identifies the exact request in Canopy's logs.
rbac.roles_manage_below_organizationBad requestA role carrying <code>canopy:roles.manage</code> would be held beneath the organization node: granted at an office or team, moved there, or given the key while someone holds it there. The organization's roles belong to the whole organization, so the key counts only at its node.
Grant the role at the organization node, or use a role without <code>canopy:roles.manage</code> beneath it.
organization.role_permission_not_heldForbiddenThe role would carry a permission the editor does not hold at the organization node. <code>data.permission_keys</code> names each one. A permission the role already carries may stay; only one being added must be held.
Leave those permissions out, or ask someone who holds them, or the developer, to add them.
organization.role_not_ownedForbiddenThe role is one of the Environment's, which every organization uses and only the developer changes. An organization changes, re-permissions or deletes only the roles it owns.
Copy the role (<code>POST /api/v1/organizations/{org_id}/roles</code> with <code>copy_from_role_id</code>) and change the copy.
version_mismatchConflictThe request sent <code>If-Match</code> with a version older than the resource's current one: somebody changed it since it was read. The error's <code>data</code> carries the resource as it now stands under <code>current</code>.
Read the resource again, show the reader what changed, and retry with the current <code>version</code>.
Returned object
curl -X PUT "https://auth.canopy-io.com/api/v1/organizations/value/roles/value/permissions" \ -H "If-Match: "3"" \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "permission_keys": [ "string" ] }'
{ "items": [ { "id": "string", "application_id": "string", "key": "string", "name": "string", "description": "string", "category": "string", "source": "system", "created_at": "2026-04-20T12:00:00.000Z", "version": 0 } ] }
Tell us how we can improve this guide.