> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sublay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Member

> Edit a member's access or profile fields, with two capability tiers

Edits a member, with **two capability tiers by field sensitivity**:

* **Powerful fields** (`capabilities`, `permissions`, and rank — as either `rank` or `relativeRank`) require **`edit-member-access`**, plus the [rank rule](/v8/data-models/workspace-member#rank-semantics) and the [no-escalation](/v8/data-models/workspace-member#no-privilege-escalation) guard.
* **Cosmetic fields** (`title`, `metadata`) require only **`edit-member-profile`**. A member editing **their own `title`** needs no capability.

Rank moves in **either coordinate** — `rank` (absolute) or `relativeRank` (an offset from the actor) — and they are **mutually exclusive**; sending both is a `400`. Omitting **both** means **rank unchanged**: unlike [Create Invite](/v8/api-reference/workspaces/invitations/create-invite), this endpoint has no value default, so editing someone's capabilities never moves them on the ladder. See [choosing a rank](/v8/sdk/workspaces/overview#choosing-a-rank).

**Grant vs strip asymmetry:** you may only *add* a capability/permission you hold on the target node (resolved set, including via reach); *stripping* is governed by **rank alone**. **Cross-node rank is not compared** — the rank guard runs only when actor and target share a member row on the same workspace.

## Path Parameters

<ParamField path="id" type="string" required>
  The workspace UUID.
</ParamField>

<ParamField path="userId" type="string" required>
  The target member's user id.
</ParamField>

## Body Parameters

All optional; the controller enforces which tier each touched field belongs to. At least one must be provided.

<ParamField body="capabilities" type="string[]">
  New capability set (powerful — `edit-member-access`).
</ParamField>

<ParamField body="permissions" type="string[]">
  New permission set (powerful — `edit-member-access`).
</ParamField>

<ParamField body="rank" type="number">
  New **absolute** rank, `0` – `2147483647` (powerful — `edit-member-access` + rank rules). Mutually exclusive with `relativeRank`. Omitting both leaves rank unchanged.
</ParamField>

<ParamField body="relativeRank" type="number">
  New rank expressed as an **offset from the acting user**: `1` = one rung below me. Must be `1` – `2147483647` — `0` (a peer) and negatives are a `400`. The **resolved** value is bounded too, so an in-range offset that overflows once anchored is a `400`, not a `500`. Mutually exclusive with `rank`, and with **no default**: omitting both leaves rank unchanged.

  Anchored on the actor's **own rank if they hold a member row on this workspace**, apex (one step above rank 0) otherwise — the anchor turns on the row, not on what kind of actor they are. Resolved to an absolute number at write time and stored absolute; it is a **snapshot** and does not follow the actor's own rank afterwards.

  Because an in-ladder anchor is `>= 0` and the offset is `>= 1`, a direct-member actor can never reach rank `0` this way — and neither can they by naming absolute `rank: 0`, which the assign rule below refuses for exactly the same reason (nothing is strictly below `0`). There is no separate rank-0 guard: rank `0` is effectively owner-only because the owner skips the rank block, not because a check names them. An actor with **no** member row here — an ancestor owner, or a cross-node reach holder — also skips it.

  A service/master key is **not** on that list. Unlike [Create Invite](/v8/api-reference/workspaces/invitations/create-invite), this route has no unbounded path: `actingUserId` is required, so a key is always *somebody*, and it faces exactly the floors that somebody faces. A key that names nobody does not skip the guard — it gets a `400 workspace/missing-user-id`.
</ParamField>

<ParamField body="title" type="string">
  New cosmetic title (`edit-member-profile`, or self for own title). Nullable.
</ParamField>

<ParamField body="metadata" type="object">
  New cosmetic metadata (`edit-member-profile`).
</ParamField>

<ParamField body="actingUserId" type="string" required>
  Service/master keys only — the **acting** user (sent in the body; the path `:userId` is the target). **Required for a key:** this route has **no unbounded path**, so a key that names nobody is refused with `400 workspace/missing-user-id`. Every check below — the two capability tiers, the rank guard, the assign rule and no-escalation — runs against the named user. Act as the **owner** for an unrestricted edit.

  A plain user token omits it — the actor is the token's own user. The field is **not** ignored for such a token: sending your *own* user id is a harmless no-op, but sending **anyone else's** is a `403 workspace/unauthorized`. See [Acting on behalf of a user](/v8/sdk/workspaces/overview#acting-on-behalf-of-a-user).
</ParamField>

## Response

Returns the updated [WorkspaceMember](/v8/data-models/workspace-member) object.

## Error Responses

<AccordionGroup>
  <Accordion title="Unauthorized — 403">
    One message per tier, depending on which fields the request touched. Powerful fields (`capabilities`, `permissions`, `rank`, `relativeRank`):

    ```json theme={null}
    { "error": "The 'edit-member-access' capability is required to edit access fields.", "code": "workspace/unauthorized" }
    ```

    Cosmetic fields (`title`, `metadata`) — not raised when a member is editing only their own `title`. Editing your own `metadata` still requires `edit-member-profile`:

    ```json theme={null}
    { "error": "The 'edit-member-profile' capability is required to edit profile fields.", "code": "workspace/unauthorized" }
    ```
  </Accordion>

  <Accordion title="Insufficient Rank — 403">
    Two distinct messages share this code. The **act** rule, on the target:

    ```json theme={null}
    { "error": "You may only manage members ranked strictly below you.", "code": "workspace/insufficient-rank" }
    ```

    …and the **assign** rule, on the rank you asked for — checked against the resolved absolute value, so `rank` and `relativeRank` face it identically. This is also what refuses `rank: 0` to anyone holding a row on this workspace:

    ```json theme={null}
    { "error": "You may only set ranks strictly below your own.", "code": "workspace/insufficient-rank" }
    ```
  </Accordion>

  <Accordion title="Rank out of range — 400">
    ```json theme={null}
    { "error": "The resolved rank (2147483648) is out of range — ranks are integers from 0 to 2147483647.", "code": "workspace/invalid-body" }
    ```

    Checked after the offset is resolved. Out-of-range `rank` / `relativeRank` input is refused earlier by the schema, with the same code.
  </Accordion>

  <Accordion title="Both rank coordinates supplied — 400">
    ```json theme={null}
    { "error": "relativeRank: Provide either rank (absolute) or relativeRank (offset from you), not both", "code": "workspace/invalid-body" }
    ```

    A schema rejection, so the message is prefixed with the field it was reported against.
  </Accordion>

  <Accordion title="No Escalation — 403">
    The offending values are named. Capabilities:

    ```json theme={null}
    { "error": "Cannot grant capabilities you do not hold on this workspace: edit-member-access.", "code": "workspace/no-escalation" }
    ```

    …and permissions:

    ```json theme={null}
    { "error": "Cannot grant permissions you do not hold on this workspace: deploy.", "code": "workspace/no-escalation" }
    ```

    Only **added** values are checked — removing a capability or permission is governed by rank alone.
  </Accordion>

  <Accordion title="Missing Acting User — 400">
    ```json theme={null}
    { "error": "Missing user ID", "code": "workspace/missing-user-id" }
    ```

    A service/master key called this route without naming an `actingUserId`. Every gate on this route is measured against the editor's own standing, so the edit must be performed *as* somebody — act as the owner for an unrestricted edit.
  </Accordion>

  <Accordion title="Acted as Another User — 403">
    ```json theme={null}
    { "error": "Unauthorized", "code": "workspace/unauthorized" }
    ```

    A plain user token sent an `actingUserId` naming a **different** user. Only a service/master key may act as someone else.
  </Accordion>

  <Accordion title="Invalid Path Parameter — 400">
    ```json theme={null}
    { "error": "Invalid workspace id: expected a UUID.", "code": "workspace/invalid-params" }
    ```

    Every path id on the workspaces bundle is checked for UUID shape before the route runs, so a malformed one is a plain `400` rather than a `500` from the database.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.