# Members and invitations API

> List an organization's members, change roles, remove members, invite by email, accept invitations, transfer ownership, delete an organization, and choose the active organization. These routes need a browser session.

- [List members](#list-members): `GET /v1/organizations/{organization_id}/members`
- [Change a member's role](#change-a-members-role): `PATCH /v1/organizations/{organization_id}/members/{user_id}`
- [Remove a member](#remove-a-member): `DELETE /v1/organizations/{organization_id}/members/{user_id}`
- [List pending invitations](#list-pending-invitations): `GET /v1/organizations/{organization_id}/invitations`
- [Invite a member by email](#invite-a-member-by-email): `POST /v1/organizations/{organization_id}/invitations`
- [Revoke an invitation](#revoke-an-invitation): `DELETE /v1/organizations/{organization_id}/invitations/{invitation_id}`
- [Preview an invitation](#preview-an-invitation): `GET /v1/invitations`
- [Accept an invitation](#accept-an-invitation): `POST /v1/invitations/accept`
- [Transfer ownership](#transfer-ownership): `POST /v1/organizations/{organization_id}/transfer`
- [Delete an organization](#delete-an-organization): `DELETE /v1/organizations/{organization_id}`
- [Choose the active organization](#choose-the-active-organization): `PUT /v1/me/active-organization`

## List members

`GET /v1/organizations/{organization_id}/members`

- **API key:** `Authorization: Bearer vmy_…` with preset `read_only`, `use_existing`, `create_and_use`, `full_access`.
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Any member.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`MemberList`](/docs/api/schemas#memberlist) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not a member, or another organization than the API key's | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/members" \
  -H "Authorization: Bearer $VMYARD_API_KEY"
```

```json title="Response 200"
{
  "items": [
    {
      "email": "ada@example.com",
      "joined_at": "2026-10-12T05:51:01Z",
      "max_running_machines": 0,
      "role": "owner",
      "running_machines": 0,
      "usage_this_month": {
        "cost_micros": 0,
        "seconds": 0
      },
      "user_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
    }
  ]
}
```

## Change a member's role

`PATCH /v1/organizations/{organization_id}/members/{user_id}`

An admin can't change an owner or make anyone an owner, and the only owner can't be demoted (`409 last_owner`).

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |
| `user_id` | path | `string (uuid)` | Yes |  |

**Request body** (`application/json`, [`UpdateMemberRequest`](/docs/api/schemas#updatememberrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `max_running_machines` | `integer \| null` | No | 0-10000, or `null` to remove the member's cap; absent leaves it. at most 10000. |
| `role` | [`null \| RoleDto`](/docs/api/schemas#roledto) | No |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | The member after the change; `usage_this_month` is zero here, read it from the list | [`MemberDto`](/docs/api/schemas#memberdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, `session_required`, or `forbidden`: neither owner nor admin, or an admin changing an owner or granting the owner role | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `last_owner`: the organization's only owner cannot be demoted | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X PATCH "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/members/$USER_ID" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json title="Response 200"
{
  "email": "ada@example.com",
  "joined_at": "2026-10-12T05:51:01Z",
  "max_running_machines": 0,
  "role": "owner",
  "running_machines": 0,
  "usage_this_month": {
    "cost_micros": 0,
    "seconds": 0
  },
  "user_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Remove a member

`DELETE /v1/organizations/{organization_id}/members/{user_id}`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |
| `user_id` | path | `string (uuid)` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`MemberRemovedDto`](/docs/api/schemas#memberremoveddto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, `session_required`, or `forbidden`: neither owner nor admin (anyone may remove themselves), or an admin removing an owner | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `last_owner` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X DELETE "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/members/$USER_ID" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF"
```

```json title="Response 200"
{
  "api_keys_revoked": 0,
  "channels_revoked": 0,
  "tickets_revoked": 0,
  "user_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## List pending invitations

`GET /v1/organizations/{organization_id}/invitations`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Owner or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`InvitationList`](/docs/api/schemas#invitationlist) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | Neither owner nor admin, or an API key (`session_required`) | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/invitations" \
  -b "vmyard_session=$VMYARD_SESSION"
```

```json title="Response 200"
{
  "items": [
    {
      "created_at": "2026-10-12T05:51:01Z",
      "email": "ada@example.com",
      "expires_at": "2026-10-12T05:51:01Z",
      "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "invited_by": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "role": "owner"
    }
  ]
}
```

## Invite a member by email

`POST /v1/organizations/{organization_id}/invitations`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |

**Request body** (`application/json`, [`CreateInvitationRequest`](/docs/api/schemas#createinvitationrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | `string` | Yes |  |
| `role` | [`RoleDto`](/docs/api/schemas#roledto) | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `201` | Emailed with a single-use link valid 7 days; an open invitation to the same address is revoked | [`InvitationDto`](/docs/api/schemas#invitationdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, `session_required`, or `forbidden` (neither owner nor admin, or an admin inviting an owner) | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `already_member` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `429` | `rate_limited`: 50 invitations per organization per 24 hours | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `502` | `email_unavailable`: the email could not be sent; the invitation was revoked | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `503` | `invitations_unavailable`: email or `ui_base_url` is not configured | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/invitations" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "ada@example.com",
  "role": "owner"
}'
```

```json title="Response 201"
{
  "created_at": "2026-10-12T05:51:01Z",
  "email": "ada@example.com",
  "expires_at": "2026-10-12T05:51:01Z",
  "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "invited_by": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "role": "owner"
}
```

## Revoke an invitation

`DELETE /v1/organizations/{organization_id}/invitations/{invitation_id}`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |
| `invitation_id` | path | `string (uuid)` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `204` | Revoked (revoking a revoked invitation changes nothing) | — |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | Refused for this caller: role, key preset, CSRF token, or organization (`forbidden`, `insufficient_scope`, `csrf_invalid`, `organization_mismatch`, `session_required`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `410` | `invitation_closed`: already accepted | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X DELETE "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/invitations/$INVITATION_ID" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF"
```

## Preview an invitation

`GET /v1/invitations`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie.
- **Who:** The signed-in user.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | query | `string` | Yes | The token from the invitation email. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`InvitationPreviewDto`](/docs/api/schemas#invitationpreviewdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | No invitation has this token | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/invitations?token=token" \
  -b "vmyard_session=$VMYARD_SESSION"
```

```json title="Response 200"
{
  "email": "ada@example.com",
  "expires_at": "2026-10-12T05:51:01Z",
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "organization_name": "string",
  "role": "owner",
  "state": "open"
}
```

## Accept an invitation

`POST /v1/invitations/accept`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** The signed-in user.

**Request body** (`application/json`, [`AcceptInvitationRequest`](/docs/api/schemas#acceptinvitationrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | `string` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | The caller is a member with the invited role, and it is their active organization | [`InvitationAcceptedDto`](/docs/api/schemas#invitationaccepteddto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, `session_required`, or `invitation_email_mismatch`: the caller's sign-in addresses do not include the invited one | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `already_member` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `410` | `invitation_closed`: accepted, revoked, or expired | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/invitations/accept" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "Content-Type: application/json" \
  -d '{
  "token": "string"
}'
```

```json title="Response 200"
{
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "role": "owner"
}
```

## Transfer ownership

`POST /v1/organizations/{organization_id}/transfer`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |

**Request body** (`application/json`, [`TransferOwnershipRequest`](/docs/api/schemas#transferownershiprequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `user_id` | `string (uuid)` | Yes | A member who becomes the owner; the caller becomes an admin. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `204` | The member is an owner and the caller an admin | — |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, `session_required`, or not the owner | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | The caller or the new owner is not a member | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID/transfer" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "Content-Type: application/json" \
  -d '{
  "user_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}'
```

## Delete an organization

`DELETE /v1/organizations/{organization_id}`

Needs a sign-in within the last 15 minutes (`403 recent_auth_required`). Its machines are stopped (`stop_reason: organization_deleted`).

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Owner.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `organization_id` | path | `string (uuid)` | Yes |  |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`OrganizationDeletedDto`](/docs/api/schemas#organizationdeleteddto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, `session_required`, `recent_auth_required` (sign in again within 15 minutes), or not the owner | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `409` | `subscription_active`: cancel the renewing plan first | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X DELETE "$VMYARD_API_URL/v1/organizations/$ORGANIZATION_ID" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF"
```

```json title="Response 200"
{
  "api_keys_revoked": 0,
  "channels_revoked": 0,
  "machines_stopping": 0,
  "members_removed": 0,
  "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "tickets_revoked": 0
}
```

## Choose the active organization

`PUT /v1/me/active-organization`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** The signed-in user.

**Request body** (`application/json`, [`ActiveOrganizationRequest`](/docs/api/schemas#activeorganizationrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `organization_id` | `string (uuid) \| null` | No | An organization the caller belongs to, or `null` to clear it. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | — | [`ActiveOrganizationDto`](/docs/api/schemas#activeorganizationdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure or `session_required` | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not a member of that organization | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X PUT "$VMYARD_API_URL/v1/me/active-organization" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json title="Response 200"
{
  "active_organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```
