> Documentation index: [/llms.txt](/llms.txt). Every page as one file: [/llms-full.txt](/llms-full.txt).

# Members and invites

Members and invites [#members-and-invites]

Add people to a workspace and manage their roles with a workspace key, a platform key or a signed-in session.

Requires: a role with the Workspace members permissions (Admin by default) in that workspace, or a platform key.

Endpoints [#endpoints]

| Method   | URL                                               | Input                | Does                   | Auth          |
| -------- | ------------------------------------------------- | -------------------- | ---------------------- | ------------- |
| `GET`    | `/api/v1/spaces/current/members/`                 |                      | List members           | Key or bearer |
| `POST`   | `/api/v1/spaces/current/members/`                 | `{email, role_slug}` | Add an existing user   | Key or bearer |
| `PATCH`  | `/api/v1/spaces/current/members/{membership_id}/` | `{role_slug}`        | Change a member's role | Key or bearer |
| `DELETE` | `/api/v1/spaces/current/members/{membership_id}/` |                      | Remove a member        | Key or bearer |
| `POST`   | `/api/v1/spaces/current/invites/`                 | `{email, role_slug}` | Add or invite by email | Key or bearer |
| `GET`    | `/api/v1/spaces/current/invites/`                 |                      | List invites           | Key or bearer |
| `DELETE` | `/api/v1/spaces/current/invites/{invite_id}/`     |                      | Revoke an invite       | Key or bearer |

`role_slug` defaults to `member`. Roles are described in [Roles and permissions](./roles-and-permissions).

Invite someone [#invite-someone]

1. Send the invite with the role they should get:

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/spaces/current/invites/" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"email": "ben@acme.com", "role_slug": "editor"}'
   ```

2. Read the response. A person without an account gets an invite email:

   ```json
   {
     "id": null,
     "added_directly": false,
     "membership": null,
     "invite": {
       "id": 28,
       "created_by_id": null,
       "updated_by_id": null,
       "created_at": "2026-10-13T19:22:20.128528Z",
       "updated_at": "2026-10-13T19:22:20.128529Z",
       "space_id": 280,
       "invited_email": "ben@acme.com",
       "role_slug": "editor",
       "token_preview": "sk-Uq.....KsUE",
       "status": "pending",
       "expires_at": "2026-10-20T19:22:20.127604Z"
     }
   }
   ```

   A person who already has an account is added at once and emailed: `added_directly` is `true` and `membership` is set. Custom roles such as `analyst` exist only in workspaces of an [organization](./organizations-and-workspaces).

3. Verify: `GET /api/v1/spaces/current/invites/` lists the invite as `pending`.

Invite lifecycle [#invite-lifecycle]

| Status     | Meaning                                                                                                                  |
| ---------- | ------------------------------------------------------------------------------------------------------------------------ |
| `pending`  | Sent and not yet used. Expires after 7 days by default; an expired invite still shows `pending`, so compare `expires_at` |
| `accepted` | The person joined the workspace                                                                                          |
| `revoked`  | Cancelled with `DELETE …/invites/{invite_id}/`                                                                           |

* `GET …/invites/` returns only `pending` invites; accepted and revoked invites do not appear.
* The invitee accepts with `POST /api/v1/spaces/invites/accept` and `{"token": "<token from the invite link>"}`, signed in (bearer only); accepting an expired invite returns `400` `This invite has expired`.

Change a member's role [#change-a-members-role]

1. Find the member's `id` with `GET /api/v1/spaces/current/members/`.
2. Send the new role:

   ```bash
   curl -X PATCH "$CENTRAL_HUB_URL/api/v1/spaces/current/members/312/" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"role_slug": "editor"}'
   ```

Remove a member [#remove-a-member]

1. Find the member's `id` with `GET /api/v1/spaces/current/members/`.
2. Send `DELETE /api/v1/spaces/current/members/{membership_id}/`. The response is `204`.
3. The person's API keys for this workspace then get `403` `User does not have access to the selected space`.

Troubleshooting [#troubleshooting]

| Response                                                      | Cause                                                                                                                                                   | Fix                                                                                                                 |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `409` `User is already a member of this space`                | The email already belongs to a member                                                                                                                   | Change their role instead                                                                                           |
| `409` `OUTSIDE_ORGANIZATION`                                  | The person's home organization does not let its members join workspaces outside it, and this workspace is outside it; also on accepting an older invite | Invite them to one of their organization's workspaces, or have its admin set `members_can_join_other_organizations` |
| `404` `User not found` from `POST …/members/`                 | No account has that email                                                                                                                               | Use `POST …/invites/`                                                                                               |
| `403` `You can only assign a role whose permissions you hold` | The role grants permissions you do not have                                                                                                             | Ask someone with those permissions, or pick a smaller role                                                          |
| `422` `Role does not exist in the selected space`             | The `role_slug` is not a role in this workspace                                                                                                         | List roles with `GET /api/v1/spaces/current/roles/`                                                                 |
