Skip to the documentation
API

Change a member’s role

The role and nothing else. Addresses are the other axis and have their own calls.

PATCH/members/{userId}

Runs the real call on your workspace.

PATCH /members/{userId}

The role and nothing else. Addresses are the other axis and have their own calls.

Example

Needs members:write. roleId is the only field, and it is required. This call names the state it wants rather than a change to apply, so sending it twice lands on the same person holding the same role.

curl
curl -X PATCH "$OE/members/7fQ2mN8vBz1aRd4tYwKx7fQ2mN8vBz1a" -H "$AUTH" \  -H "Content-Type: application/json" \  -d '{ "roleId": "role_2b81de079c1f0a4b7e05d386" }'
Response
{  "object": "member",  "userId": "7fQ2mN8vBz1aRd4tYwKx7fQ2mN8vBz1a",  "email": "wren@acme.com",  "name": null,  "image": null,  "role": {    "id": "role_2b81de079c1f0a4b7e05d386",    "name": "Support",    "builtin": null  },  "implied": false,  "permissions": [    "emails:send",    "emails:read",    "threads:read",    "threads:write",    "labels:read",    "labels:write",    "contacts:read"  ],  "addresses": [    {      "addressId": "c40a95f2-1cc6-4d31-82a8-9e075d31c2a8",      "address": "billing@acme.com",      "access": "viewer"    }  ],  "createdAt": "2026-08-30T15:44:02.000Z"}

Addresses are NOT patchable from here, and the omission is deliberate: they are a set with a per-element access level, and a PATCH carrying an array would have to mean replace-whole, which is a silent mass revocation every time a client sends a stale list. The two address calls add and remove one at a time, so what happened is always what was asked for.

This is also how a legacy grant holder stops being implied: they have no member row, this upserts one, and from then on their permissions are what somebody chose rather than what their address access happened to imply. Note implied flipping to false and createdAt appearing in the response above. Nothing about their addresses changes.

The owner role cannot be handed out (role_immutable, a 409) whatever the caller holds. Making somebody an owner is a workspace transfer, which has different consequences and does not exist on this API.

Their new role takes effect on their next request, and on the next request of every API key capped by it.

The new role cannot hold more than the key does: one with a permission the key lacks is insufficient_authority, a 403. No key or token holds a console-only permission, so moving somebody onto a role with addresses:all in it, which reaches every address without a single grant, is done in the app.

An OAuth access token needs a verification code for this call. Until the app has verified one in the last 60 minutes, the call answers 403 step_up_required and changes nothing. An API key is never asked. The Authentication page shows how to ask for a code and verify it.

Reference