# Members

> Workspace membership and roles (Owner, Admin, Developer, Viewer, Billing, Agent).

Workspace membership and roles (Owner, Admin, Developer, Viewer, Billing, Agent).

| Operation | Method and path |
| --- | --- |
| [List members and invitations](#list-members-and-invitations) | `GET /v1/workspaces/{workspace}/members` |
| [Invite a member](#invite-a-member) | `POST /v1/workspaces/{workspace}/members` |
| [Change a member's role](#change-a-members-role) | `PATCH /v1/workspaces/{workspace}/members/{member_id}` |
| [Remove a member or cancel an invitation](#remove-a-member-or-cancel-an-invitation) | `DELETE /v1/workspaces/{workspace}/members/{member_id}` |
| [Accept a workspace invitation](#accept-a-workspace-invitation) | `POST /v1/invitations/accept` |

## List members and invitations

```http
GET /v1/workspaces/{workspace}/members
```

- **Operation:** `listMembers`
- **Scopes:** `members:read`
- **Paginated:** `?limit=` and `?cursor=`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `status` | query | `invited`, `active` |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | A page of Member objects. | [MemberList](/docs/api/objects/member-list/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/workspaces/acme/members \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "data": [
    {
      "id": "mem_01jbx2m9k7q4r8t5v6w3y0z1pr",
      "workspace_id": "string",
      "user": {
        "id": "string",
        "name": "string",
        "avatar_url": "string",
        "has_passkey": false
      },
      "email": "ada@example.co.uk",
      "role": "owner",
      "status": "invited",
      "invited_by": {
        "type": "agent",
        "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
        "display": "Stewart Webb via agent claude-code-on-stewart-laptop",
        "via_agent": {
          "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
          "name": "claude-code-on-stewart-laptop"
        },
        "on_behalf_of": {
          "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
          "display": "Stewart Webb"
        }
      },
      "invite_expires_at": "2026-10-22T13:05:00Z",
      "joined_at": "2026-10-22T13:05:00Z",
      "created_at": "2026-10-22T13:05:00Z"
    }
  ],
  "next_cursor": "string",
  "has_more": false
}
```

## Invite a member

```http
POST /v1/workspaces/{workspace}/members
```

- **Operation:** `inviteMember`
- **Scopes:** `members:write`
- **Idempotency-Key:** required; supports `?dry_run=true`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |

**Request body** (`application/json`, required): [MemberInvite](/docs/api/objects/member-invite/)

| Field | Type | Description |
| --- | --- | --- |
| `email` * | string (email) |  |
| `role` * | `owner`, `admin`, `developer`, `viewer`, `billing` |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `201` Created | Created. | [Member](/docs/api/objects/member/) |

**Errors:** `400`, `401`, `402`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/workspaces/acme/members \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "ada@example.co.uk",
  "role": "owner"
}'
```

**Example response**

```json
{
  "id": "mem_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "workspace_id": "string",
  "user": {
    "id": "string",
    "name": "string",
    "avatar_url": "string",
    "has_passkey": false
  },
  "email": "ada@example.co.uk",
  "role": "owner",
  "status": "invited",
  "invited_by": {
    "type": "agent",
    "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
    "display": "Stewart Webb via agent claude-code-on-stewart-laptop",
    "via_agent": {
      "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
      "name": "claude-code-on-stewart-laptop"
    },
    "on_behalf_of": {
      "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
      "display": "Stewart Webb"
    }
  },
  "invite_expires_at": "2026-10-22T13:05:00Z",
  "joined_at": "2026-10-22T13:05:00Z",
  "created_at": "2026-10-22T13:05:00Z"
}
```

## Change a member's role

```http
PATCH /v1/workspaces/{workspace}/members/{member_id}
```

- **Operation:** `updateMember`
- **Scopes:** `members:write`
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `member_id` * | path | string |  |

**Request body** (`application/json`, required): [MemberUpdate](/docs/api/objects/member-update/)

| Field | Type | Description |
| --- | --- | --- |
| `role` * | `owner`, `admin`, `developer`, `viewer`, `billing` |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. With `dry_run=true`: a Plan (application/vnd.guv.plan+json) and nothing changes. | [Member](/docs/api/objects/member/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X PATCH https://api.guv.dev/v1/workspaces/acme/members/mem_01jbx2m9k7q4r8t5v6w3y0z1pr \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "role": "owner"
}'
```

**Example response**

```json
{
  "id": "mem_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "workspace_id": "string",
  "user": {
    "id": "string",
    "name": "string",
    "avatar_url": "string",
    "has_passkey": false
  },
  "email": "ada@example.co.uk",
  "role": "owner",
  "status": "invited",
  "invited_by": {
    "type": "agent",
    "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
    "display": "Stewart Webb via agent claude-code-on-stewart-laptop",
    "via_agent": {
      "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
      "name": "claude-code-on-stewart-laptop"
    },
    "on_behalf_of": {
      "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
      "display": "Stewart Webb"
    }
  },
  "invite_expires_at": "2026-10-22T13:05:00Z",
  "joined_at": "2026-10-22T13:05:00Z",
  "created_at": "2026-10-22T13:05:00Z"
}
```

## Remove a member or cancel an invitation

```http
DELETE /v1/workspaces/{workspace}/members/{member_id}
```

- **Operation:** `removeMember`
- **Scopes:** `members:write`
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `member_id` * | path | string |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `204` No Content | Done. No body. | none |

**Errors:** `400`, `401`, `403`, `404`, `409`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X DELETE https://api.guv.dev/v1/workspaces/acme/members/mem_01jbx2m9k7q4r8t5v6w3y0z1pr \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

## Accept a workspace invitation

```http
POST /v1/invitations/accept
```

- **Operation:** `acceptInvitation`
- **Scopes:** any signed-in principal
- **Idempotency-Key:** required

**Request body** (`application/json`, required): [InvitationAccept](/docs/api/objects/invitation-accept/)

| Field | Type | Description |
| --- | --- | --- |
| `token` * | string | Token from the invitation email. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [Member](/docs/api/objects/member/) |

**Errors:** `400`, `401`, `403`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/invitations/accept \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "token": "string"
}'
```

**Example response**

```json
{
  "id": "mem_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "workspace_id": "string",
  "user": {
    "id": "string",
    "name": "string",
    "avatar_url": "string",
    "has_passkey": false
  },
  "email": "ada@example.co.uk",
  "role": "owner",
  "status": "invited",
  "invited_by": {
    "type": "agent",
    "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
    "display": "Stewart Webb via agent claude-code-on-stewart-laptop",
    "via_agent": {
      "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
      "name": "claude-code-on-stewart-laptop"
    },
    "on_behalf_of": {
      "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
      "display": "Stewart Webb"
    }
  },
  "invite_expires_at": "2026-10-22T13:05:00Z",
  "joined_at": "2026-10-22T13:05:00Z",
  "created_at": "2026-10-22T13:05:00Z"
}
```
