Update a permission grant
Change a grant's relation, its extra or refused permissions, or its expiry. The subject and the resource are the grant's identity and can't change. To move access to another subject or resource, delete this grant and create a new one. Body keys other than the four below are ignored.
PATCH is partial. A field you omit keeps its current value. A list you send replaces the whole list, so send the complete set you want.
You need the <resource_type>:manage permission on the grant's resource. Otherwise the response is 403. A grant that has expired or been deleted returns 404.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
pk:requiredstring (uuid)The id of the grant to update.
Body Parameters
Every field is optional. Send the subset you're changing.
relation:optionalstringThe new role slug, for example contributor. Maximum 50 characters. Plane doesn't check the value against the workspace's roles. Read valid slugs from List roles.
permissions_grant:optionalarray of stringThe complete list of extra permissions. Each entry is a permission such as workitem:archive, or one of the wildcards * and <resource_type>:*. Send [] to remove them all. An entry that isn't a real permission returns 400.
permissions_deny:optionalarray of stringThe complete list of refused permissions. Same format and validation as permissions_grant. Send [] to remove them all.
expires_at:optionalstring (date-time)When the grant expires. Send null to make the grant permanent.
Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Requestable here: created_at, expires_at, id, permissions_deny, permissions_grant, relation, resource_id, resource_type, subject_id, subject_type, updated_at.
See Sparse fields.
Scopes
workspaces.permissions:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | A permission string is invalid, relation is too long, or expires_at isn't a date-time. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. |
403 | forbidden | You can't manage access to the grant's resource, or your token scope doesn't allow this. |
404 | not_found | No such grant in this workspace, or it has expired or been deleted. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | Not raised by this endpoint's own checks. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X PATCH \
"https://api.plane.so/api/v2/workspaces/my-team/permissions/grants/e2f9c4a7-1b8d-4c63-9e05-7a3b6d1f8c94/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"relation": "contributor",
"permissions_grant": [],
"expires_at": null
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/permissions/grants/e2f9c4a7-1b8d-4c63-9e05-7a3b6d1f8c94/",
headers={"X-Api-Key": "your-api-key"},
json={
"relation": "contributor",
"permissions_grant": [],
"expires_at": None,
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/permissions/grants/e2f9c4a7-1b8d-4c63-9e05-7a3b6d1f8c94/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
relation: "contributor",
permissions_grant: [],
expires_at: null,
}),
},
);
const data = await response.json();{
"id": "e2f9c4a7-1b8d-4c63-9e05-7a3b6d1f8c94",
"subject_type": "user",
"subject_id": "a8d3e5f1-6b2c-4e97-8d40-1c5f7a9b3e26",
"relation": "contributor",
"resource_type": "project",
"resource_id": "4af68566-94a4-4eb3-94aa-50dc9427067b",
"permissions_grant": [],
"permissions_deny": [],
"expires_at": null,
"created_at": "2026-01-20T11:05:17.204918Z",
"updated_at": "2026-02-03T16:45:12.904117Z"
}{
"type": "not_found",
"code": "not_found",
"detail": "The requested resource was not found."
}
