---
title: "Update a role"
description: "Every field optional, and `permissions` replaces the whole list."
url: "https://openemail.uk/docs/api/roles/update"
area: "API"
category: "Roles & access"
---

# Update a role

Every field optional, and `permissions` replaces the whole list.

`PATCH /roles/{id}`

## PATCH /roles/{id}

Every field optional, and `permissions` replaces the whole list.

## Example

Needs `roles:write`. Omitting a field leaves it alone, which is what PATCH means.

**curl**

```
curl -X PATCH "$OE/roles/role_2b81de079c1f0a4b7e05d386" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "permissions": ["emails:read", "threads:read", "labels:read", "contacts:read"] }'
```

**Response**

```
{
  "object": "role",
  "id": "role_2b81de079c1f0a4b7e05d386",
  "name": "Support",
  "description": "Answers the shared inboxes and nothing else.",
  "permissions": ["emails:read", "threads:read", "labels:read", "contacts:read"],
  "builtin": null,
  "editable": true,
  "deletable": true,
  "members": 3,
  "apiKeys": 1,
  "createdAt": "2026-08-30T10:41:02.000Z",
  "updatedAt": "2026-08-30T13:02:19.000Z"
}
```

> `permissions` REPLACES the whole list. There is no grant-one call and there will not be one: a list is what gets audited, and an index-addressed patch is a lost update the moment two tabs are open. Read the role, change the entry you meant, send all of them back. Sending one permission does not add a permission. It leaves the role holding exactly that one, plus whatever it implies.

> `description` is nullable as well as optional, and the difference is the whole point of a patch: omitting it keeps the stored sentence, sending `null` clears it. Without the nullable there would be no way to remove a description except by replacing it with a space.

> The owner is the only role a PATCH refuses, and it refuses every field of one: `role_immutable`, a 409 with `param: "roleId"`. Everything else takes a new name as readily as a new permission list, the seeded roles included. `builtin` records where a role came from, not what may be done to it. A name another role already holds is `role_name_taken` instead, a 409 with `param: "name"`.

> The edit lands on the NEXT request made by anybody holding the role, API keys included, because the ceiling is resolved per request rather than cached. Narrowing a role is therefore a live revocation that takes effect without the keys under it being rotated. Widening one is live too, which is the half worth remembering.
