---
title: "Change a member's role"
method: PATCH
path: "/v1/members/{membership_id}"
tags: ["members"]
---

# Change a member's role

`PATCH /v1/members/{membership_id}`

## Request

- Base URL: `https://api.patricia.app` (the document declares no server; this is the origin it was published from)
- URL: `https://api.patricia.app/v1/members/{membership_id}`
- Auth: HTTP bearer

Change a member's role within the authenticated tenant.

Safety rules:
- ``role_code`` must be a valid role.
- Only principals with ``role_code == 'owner'`` may grant or revoke the
  ``owner`` role (admins cannot create or demote owners).
- Demoting the last ACTIVE owner is rejected with a 409, and so is demoting the last restorable
  owner row when no owner is ACTIVE. The staff role route shares that guard,
  :class:`services.last_owner_guard.LastOwnerGuard`, which owns the rule and its locks.
- A membership a security action or a merge holds cannot have its role changed
  at all: ``MembershipRepository.set_role`` asks the transition table and answers
  409. That covers promotion INTO ``owner``, which those rows would otherwise
  accept while nothing about them is an admin's to decide.

**Both authority rules are decided twice: once on the unlocked read, and again on the
row this transaction has LOCKED.** The first pass only fails fast. The one that decides
is the second, because the target's ``role_code`` is exactly what a concurrent PATCH on
this same route can change: a request that read the row as ``admin`` computed
``touching_owner = False``, skipped the owner gate, and never counted owners at all — so
a promotion committing in that window let a non-owner admin demote an owner with the
last-owner protection not merely passed but never evaluated, and the workspace could be
left with none. ``reactivate_member`` below re-checks its own owner rule against the
locked row for the same reason.

The role moves and the status does not. A member promoted while ``pending`` stays
``pending``; activating them is the separate, audited
``POST /v1/members/{id}/reactivate``.

## Path parameters

- `membership_id` string, uuid, required

## Headers

- `X-Tenant-Id` string, nullable

## Request body

- UpdateRoleBody: PATCH /members/{id} request body.
  - `role_code` string, required

## Response `200`

Successful Response

- MemberRow: A single member returned after a mutation.
  - `id` string, uuid, required
  - `email` string, required
  - `name` string, required
  - `handle` string, nullable, required
  - `role_code` string, required
  - `status_code` string, required
  - `first_interacted_at` string, date-time, nullable
  - `first_interacted_platform_code` string, nullable, required
  - `first_signed_in_at` string, date-time, nullable, required
  - `email_disabled` boolean
  - `guest_tier` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/patricia/apis/patricia-api.md) · [All operations](https://skmtc.dev/patricia/apis/patricia-api/llms.txt) · [OpenAPI document](https://skmtc.dev/patricia/apis/patricia-api/revisions/0dfb44495ea1?raw)
