# Members and invitations

> List, add and remove the people of your organization, change their roles and manage invitations.

Members are the people with access to the organization, each with a [role](https://developers.openwaf.dev/api/authentication/#roles). Admins add members directly or invite them by email; an invitation is valid for 7 days.

| Operation | Endpoint |
|---|---|
| [List members](https://developers.openwaf.dev/api/members/#list-members) | `GET /v1/orgs/{org}/members` |
| [Add member](https://developers.openwaf.dev/api/members/#add-member) | `POST /v1/orgs/{org}/members` |
| [Change member role](https://developers.openwaf.dev/api/members/#update-member) | `PATCH /v1/orgs/{org}/members/{user}` |
| [Remove member](https://developers.openwaf.dev/api/members/#remove-member) | `DELETE /v1/orgs/{org}/members/{user}` |
| [List invitations](https://developers.openwaf.dev/api/members/#list-invitations) | `GET /v1/orgs/{org}/invitations` |
| [Create invitation](https://developers.openwaf.dev/api/members/#create-invitation) | `POST /v1/orgs/{org}/invitations` |
| [Delete invitation](https://developers.openwaf.dev/api/members/#delete-invitation) | `DELETE /v1/orgs/{org}/invitations/{id}` |

## List members

```http
GET /v1/orgs/{org}/members
```

Lists the members of the organization with their role and, when they are limited to one client, its `client_id`.

**Required role:** `readonly` or higher. Organization-wide.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |

#### Response

- `200`: Returns [MemberList](https://developers.openwaf.dev/api/schemas/#member-list).

| Field | Type | Description |
|---|---|---|
| `items[].id` | string (uuid) | Id. |
| `items[].email` | string | Email address. |
| `items[].name` | string | Name. |
| `items[].created_at` | string (date-time) | Creation time. |
| `items[].role` | string | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `items[].joined_at` | string (date-time) | When the member joined. |
| `items[].client_id` | string (uuid) or null | Client the member is limited to (roles tech and readonly only); null = the whole organization. |

#### Errors

| Status | Description |
|---|---|
| `403` | The token's role or limits do not allow it (`forbidden`). |

#### Example

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/members" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.listMembers();
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.list_members()
```



```json
{
  "items": [
    {
      "id": "5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
      "email": "dev@acme.example",
      "name": "Dev team",
      "created_at": "2026-04-10T12:00:00Z",
      "role": "tech",
      "joined_at": "2026-04-10T12:00:00Z",
      "client_id": null
    }
  ]
}
```

## Add member

```http
POST /v1/orgs/{org}/members
```

Adds a person to the organization directly by email, without an invitation. `client_id` limits the member to the zones of one client (roles `tech` and `readonly` only).

**Required role:** `admin` or higher. Only an owner can grant the `owner` role.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |

#### Request body

Content type `application/json`.

| Field | Type | Required | Description |
|---|---|---|---|
| `email` | string (email) | Yes | Email address. |
| `name` | string | No | Name. |
| `role` | string | Yes | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `client_id` | string (uuid) | No | Limits the member to the zones of one client (roles tech and readonly only). |

#### Response

- `201`: Added. Returns [Member](https://developers.openwaf.dev/api/schemas/#member).

| Field | Type | Description |
|---|---|---|
| `id` | string (uuid) | Id. |
| `email` | string | Email address. |
| `name` | string | Name. |
| `created_at` | string (date-time) | Creation time. |
| `role` | string | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `joined_at` | string (date-time) | When the member joined. |
| `client_id` | string (uuid) or null | Client the member is limited to (roles tech and readonly only); null = the whole organization. |

#### Errors

| Status | Description |
|---|---|
| `403` | The token's role or limits do not allow it (`forbidden`). |
| `409` | `already_member`: the person is already a member. |

#### Example

**curl**


```sh
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/members" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "email": "dev@acme.example",
    "role": "tech"
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.addMember({
  body: {
    email: "dev@acme.example",
    role: "tech"
  }
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.add_member(
    body={
        "email": "dev@acme.example",
        "role": "tech"
    },
)
```



```json
{
  "id": "5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
  "email": "dev@acme.example",
  "name": "Dev team",
  "created_at": "2026-04-10T12:00:00Z",
  "role": "tech",
  "joined_at": "2026-04-10T12:00:00Z",
  "client_id": null
}
```

## Change member role

```http
PATCH /v1/orgs/{org}/members/{user}
```

Changes the role of a member. The organization always keeps at least one owner. The member's tokens follow the new role at once, since a token never acts above its member.

**Required role:** `admin` or higher. Changing an owner or granting `owner` needs the `owner` role.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `user` | path | string (uuid) | Yes | User id of the member. |

#### Request body

Content type `application/json`.

| Field | Type | Required | Description |
|---|---|---|---|
| `role` | string | Yes | Role. One of `owner`, `admin`, `tech`, `readonly`. |

#### Response

- `200`: Returns [Member](https://developers.openwaf.dev/api/schemas/#member).

| Field | Type | Description |
|---|---|---|
| `id` | string (uuid) | Id. |
| `email` | string | Email address. |
| `name` | string | Name. |
| `created_at` | string (date-time) | Creation time. |
| `role` | string | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `joined_at` | string (date-time) | When the member joined. |
| `client_id` | string (uuid) or null | Client the member is limited to (roles tech and readonly only); null = the whole organization. |

#### Errors

| Status | Description |
|---|---|
| `403` | The token's role or limits do not allow it (`forbidden`). |
| `409` | `conflict`: the change would leave the organization without an owner. |

#### Example

**curl**


```sh
curl -X PATCH "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/members/5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "role": "admin"
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.updateMember({
  user: "5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
  body: {
    role: "admin"
  }
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.update_member(
    user="5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
    body={
        "role": "admin"
    },
)
```



```json
{
  "id": "5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
  "email": "dev@acme.example",
  "name": "Dev team",
  "created_at": "2026-04-10T12:00:00Z",
  "role": "tech",
  "joined_at": "2026-04-10T12:00:00Z",
  "client_id": null
}
```

## Remove member

```http
DELETE /v1/orgs/{org}/members/{user}
```

Removes a member from the organization. Their API tokens stop working at once. The last owner cannot be removed.

**Required role:** `admin` or higher. Removing an owner needs the `owner` role.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `user` | path | string (uuid) | Yes | User id of the member. |

#### Response

- `204`: Removed. No body.

#### Errors

| Status | Description |
|---|---|
| `403` | The token's role or limits do not allow it (`forbidden`). |
| `409` | `conflict`: the member is the last owner. |

#### Example

**curl**


```sh
curl -X DELETE "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/members/5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.removeMember({
  user: "5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.remove_member(user="5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c")
```



## List invitations

```http
GET /v1/orgs/{org}/invitations
```

Lists the open invitations of the organization.

**Required role:** `admin` or higher. Organization-wide.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |

#### Response

- `200`: Returns `items`, an array of [Invitation](https://developers.openwaf.dev/api/schemas/#invitation).

| Field | Type | Description |
|---|---|---|
| `items[].id` | string (uuid) | Id. |
| `items[].org_id` | string (uuid) | Organization id. |
| `items[].email` | string | Email address. |
| `items[].role` | string | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `items[].invited_by` | string | Who sent the invitation. |
| `items[].created_at` | string (date-time) | Creation time. |
| `items[].expires_at` | string (date-time) | Expiry time. |
| `items[].client_id` | string (uuid) or null | Client the invited member will be limited to; null = the whole organization. |

#### Example

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/invitations" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.listInvitations();
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.list_invitations()
```



```json
{
  "items": [
    {
      "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
      "org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
      "email": "ops@example.com",
      "role": "owner",
      "invited_by": "user:5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
      "created_at": "2026-10-27T09:30:00Z",
      "expires_at": "2026-10-27T09:30:00Z",
      "client_id": "65904271-c7f4-4ee6-a8a6-84f55f76a92a"
    }
  ]
}
```

## Create invitation

```http
POST /v1/orgs/{org}/invitations
```

Invites a person by email with a role. The invitation is valid for 7 days. The answer includes its `secret`, shown only once; the invited person accepts it from the link of the email.

**Required role:** `admin` or higher. Only an owner can invite with the `owner` role.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |

#### Request body

Content type `application/json`.

| Field | Type | Required | Description |
|---|---|---|---|
| `email` | string (email) | Yes | Email address. |
| `role` | string | Yes | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `client_id` | string (uuid) | No | Limits the invited member to the zones of one client (roles tech and readonly only). |

#### Response

- `201`: Created. Returns an object with the fields below.

| Field | Type | Description |
|---|---|---|
| `id` | string (uuid) | Id. |
| `org_id` | string (uuid) | Organization id. |
| `email` | string | Email address. |
| `role` | string | Role. One of `owner`, `admin`, `tech`, `readonly`. |
| `invited_by` | string | Who sent the invitation. |
| `created_at` | string (date-time) | Creation time. |
| `expires_at` | string (date-time) | Expiry time. |
| `client_id` | string (uuid) or null | Client the invited member will be limited to; null = the whole organization. |
| `secret` | string |  |

#### Errors

| Status | Description |
|---|---|
| `409` | `already_member` or an open invitation for the same email. |

#### Example

**curl**


```sh
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/invitations" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "email": "dev@acme.example",
    "role": "readonly"
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.createInvitation({
  body: {
    email: "dev@acme.example",
    role: "readonly"
  }
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.create_invitation(
    body={
        "email": "dev@acme.example",
        "role": "readonly"
    },
)
```



```json
{
  "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
  "org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
  "email": "ops@example.com",
  "role": "owner",
  "invited_by": "user:5b8e2a1c-7d3f-4a9e-b6c2-1e4d7f0a3b9c",
  "created_at": "2026-10-27T09:30:00Z",
  "expires_at": "2026-10-27T09:30:00Z",
  "client_id": "65904271-c7f4-4ee6-a8a6-84f55f76a92a",
  "secret": "owf_8Jx2…"
}
```

## Delete invitation

```http
DELETE /v1/orgs/{org}/invitations/{id}
```

Withdraws an open invitation; its link stops working.

**Required role:** `admin` or higher. Organization-wide.

#### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `org` | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise `404`). |
| `id` | path | string (uuid) | Yes | Id of the resource. |

#### Response

- `204`: Withdrawn. No body.

#### Errors

| Status | Description |
|---|---|
| `404` | Not found, or not visible to the token (`not_found`). |

#### Example

**curl**


```sh
curl -X DELETE "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/invitations/11111111-2222-4333-8444-555555555555" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.deleteInvitation({
  id: "11111111-2222-4333-8444-555555555555"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token from OPENWAF_TOKEN
result = owf.delete_invitation(id="11111111-2222-4333-8444-555555555555")
```
