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

# Change a member's role.

`PATCH /v1/orgs/{orgId}/members/{userId}`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/orgs/{orgId}/members/{userId}`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Sets the member's org role. RBAC is locally authoritative (ADR-0019 §2); WorkOS
is not written. Guards: only an owner may grant or revoke the owner role (an admin
cannot mint owners); you cannot change your own role; the last owner cannot be
demoted (409).

## Request body

- object
  - `role` 'viewer' | 'editor' | 'admin' | 'owner', required

## Response `200`

Updated member.

- object
  - `data` Member: One organization member — the membership joined with the user's identity and role.
    - `userId` string, uuid, required
    - `email` string, email, required
    - `displayName` string, required
    - `role` 'viewer' | 'editor' | 'admin' | 'owner', required
    - `status` 'active' | 'suspended', required
    - `joinedAt` string, date-time, required
    - `lastActiveAt` string, date-time, nullable: Most recent audited action of any kind in this org; absent if none.
    - `lastLoginAt` string, date-time, nullable: Most recent successful login; absent if the user has never logged in.
    - `lastAiEditAt` string, date-time, nullable: Most recent AI (agent) edit in this org; absent if none.
  - `meta` Meta: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Resource does not exist.
- `409`: The change would leave the org with no owner.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
