Log in Create workspace
Skip to content

API reference

Operator - Invitations

Invite people into the current workspace and manage outstanding invitation links.

Jump to 4 endpoints
On this page

Overview

Workspace invitations carry an email, initial role, inviter, expiration, and acceptance state. The list intentionally returns only pending, unexpired invitations; accepted or expired records do not remain actionable there.

Invitation scope is explicit
The workspace_id path must equal the session's selected workspace. A different workspace returns 404 even when the same user belongs to it; switch workspaces first instead of editing the URL.
Resend refreshes, revoke removes
Resending a still-pending invitation refreshes its seven-day expiry and sends the email again. Revocation deletes the outstanding invitation; neither action changes an already accepted membership.

Before you begin

  • A signed-in browser session with a selected workspace backed by an active membership is required; an archived workspace remains readable but rejects mutations. Every /api/v1/operator endpoint rejects API credentials and bearer tokens, regardless of their scopes.
  • Any role on an active membership can list current-workspace invitations. Only owners and admins can create, resend, or revoke them.
  • Use the current workspace UUID in the route and confirm the email and least-privileged initial role before sending.

How it works

  1. Check for pending work

    List invitations for the selected workspace and search by normalized email. Creating an invite for an existing active member is rejected, so check Members before assuming delivery failed.

  2. Close the lifecycle

    Resend only a pending, unexpired invitation; create a fresh invitation after expiry. Revoke mistaken invites promptly, and manage accepted users through the membership API.

Examples

These examples are illustrative. Replace the sample values with your own. Keep credentials in environment variables or a secret manager, never in client-side code.

List outstanding invitations

Paste the UUID of the workspace currently selected in the application; merely belonging to another workspace is not enough for this route.

javascript
const workspaceId = window.prompt("Current workspace UUID");
if (!workspaceId) throw new Error("A workspace UUID is required");
const response = await fetch(`/api/v1/operator/workspaces/${encodeURIComponent(workspaceId)}/invitations`, {
  credentials: "same-origin",
  headers: {"Accept": "application/json"}
});
const body = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${body.detail}`);
console.log(body);
What to expect

200 OK with {"invitations": []} when no pending unexpired invitations remain. Populated rows include email, role, expiry, inviter, and workspace.

Troubleshooting

The workspace path returns 404
The path workspace must be the session's current workspace, not merely another workspace visible in /operator/me. Switch through the application first.
Resend fails
Only pending, unexpired invitations can be resent. Accepted invitations are membership records; expired ones require a new invitation.

Endpoint reference

Ready to integrate? Open an endpoint for its required permissions, request fields, response format, and status codes. The examples above explain the workflow; these details define each individual request.

GET
/api/v1/operator/workspaces/:workspace_id/invitations Web Session

List workspace invitations

Description

Returns unexpired pending invitations only (`accepted_at` is null and `expires_at` is in the future). Accepted and expired rows are omitted. JSON never includes the invite token — that secret is emailed and shown once on `/app/members`. `:workspace_id` must be the session workspace. Browser session only; CSRF is not required for this GET. Machine Bearer tokens are 401.

Auth

Web Session
Parameters
Name In Type Required Description
workspace_id path string Yes Must be the session's current workspace UUID
Responses
200

Pending unexpired invitations

Returns: InvitationList

Response Body
Field Type Required Description
invitations array<object> Yes Invitation records
invitations[].id string Yes Invitation UUID
invitations[].email string Yes Invitee email
invitations[].role string (member, admin, auditor) Yes Assigned role
invitations[].inserted_at datetime (ISO 8601) Yes Created at
invitations[].updated_at datetime (ISO 8601) Yes Updated at
invitations[].expires_at datetime (ISO 8601) Yes Expiry (default now+7 days; resend refreshes by 7 days)
invitations[].workspace object Yes Workspace summary
invitations[].workspace.id string Yes Workspace UUID
invitations[].workspace.name string Yes Workspace name
invitations[].accepted_at datetime (ISO 8601) No When the invite was accepted (null while pending)
invitations[].accepted_by object No Accepting user (null while pending)
invitations[].accepted_by.id string Yes User UUID
invitations[].accepted_by.email string Yes User email
invitations[].invited_by object No Inviting operator
invitations[].invited_by.id string Yes User UUID
invitations[].invited_by.email string Yes User email
Example
{
  "invitations": [
    {
      "accepted_at": null,
      "accepted_by": null,
      "email": "invitee@acme.com",
      "expires_at": "2026-04-03T10:00:00Z",
      "id": "7b8c9d0e-1234-4abc-9def-0123456789ab",
      "inserted_at": "2026-03-27T10:00:00Z",
      "invited_by": {
        "email": "admin@acme.com",
        "id": "0f1cdac9-305d-4d11-a53d-3fca310c0635"
      },
      "role": "member",
      "updated_at": "2026-03-27T10:00:00Z",
      "workspace": {
        "id": "4ce4d170-e6d5-4118-8f4d-fac69dc099ea",
        "name": "Acme Corp"
      }
    }
  ]
}
401

No operator browser session

404

workspace_id is not the session's current workspace

POST
/api/v1/operator/workspaces/:workspace_id/invitations Web Session

Create invitation

Description

Invites an email to the current workspace. `role` is optional and defaults to `member` (`admin` and `auditor` are also valid; `owner` is not). The row expires 7 days from creation. The one-time token is not in the JSON — the invitee joins at `/users/register?invite_token=…` (same URL the email and `/app/members` console copy). Owner/admin only. Marketplace-linked workspaces refuse invites (`400` `marketplace_managed_workspace`). Inviting an existing active member is `409` `already_member`. Sending is throttled per recipient and per workspace; see `429`.

Auth

Web Session
Request Body
Field Type Required Description
invitation object Yes Invitation attributes
invitation.email string Yes Email address to invite
invitation.role string (member, admin, auditor) No Role to assign. Defaults to member. owner is not assignable.
Parameters
Name In Type Required Description
workspace_id path string Yes Must be the session's current workspace UUID
Responses
201

Invitation created. Token is omitted; invitee uses /users/register?invite_token=…

Returns: Invitation

Response Body
Field Type Required Description
id string Yes Invitation UUID
email string Yes Invitee email
role string (member, admin, auditor) Yes Assigned role
inserted_at datetime (ISO 8601) Yes Created at
updated_at datetime (ISO 8601) Yes Updated at
expires_at datetime (ISO 8601) Yes Expiry (default now+7 days; resend refreshes by 7 days)
workspace object Yes Workspace summary
workspace.id string Yes Workspace UUID
workspace.name string Yes Workspace name
accepted_at datetime (ISO 8601) No When the invite was accepted (null while pending)
accepted_by object No Accepting user (null while pending)
accepted_by.id string Yes User UUID
accepted_by.email string Yes User email
invited_by object No Inviting operator
invited_by.id string Yes User UUID
invited_by.email string Yes User email
Example
{
  "accepted_at": null,
  "accepted_by": null,
  "email": "invitee@acme.com",
  "expires_at": "2026-04-03T10:00:00Z",
  "id": "7b8c9d0e-1234-4abc-9def-0123456789ab",
  "inserted_at": "2026-03-27T10:00:00Z",
  "invited_by": {
    "email": "admin@acme.com",
    "id": "0f1cdac9-305d-4d11-a53d-3fca310c0635"
  },
  "role": "member",
  "updated_at": "2026-03-27T10:00:00Z",
  "workspace": {
    "id": "4ce4d170-e6d5-4118-8f4d-fac69dc099ea",
    "name": "Acme Corp"
  }
}
400

Marketplace-managed workspace (`marketplace_managed_workspace`) or other atom error

401

No operator browser session

403

Owner or admin membership required

404

workspace_id is not the session's current workspace

409

That email is already an active workspace member

422

Invalid email, invalid role, or a pending invite already exists

429

Invitation email throttle exhausted (5 emails per recipient per workspace per hour, 100 per workspace per 24 hours, shared with resend). Nothing is created; retry after the seconds in the Retry-After header / extra.retry_after_seconds.

PATCH
/api/v1/operator/workspaces/:workspace_id/invitations/:id/resend Web Session

Resend invitation

Description

Re-delivers the invitation email and refreshes `expires_at` by another 7 days from now. The token is unchanged and is still omitted from JSON. Refuses expired rows (`400` `expired`) and already-accepted rows (`400` `already_accepted`). Owner/admin only. Sending is throttled per recipient and per workspace; see `429`.

Auth

Web Session
Parameters
Name In Type Required Description
workspace_id path string Yes Must be the session's current workspace UUID
id path string Yes Invitation UUID
Responses
200

Invitation resent; expires_at refreshed by 7 days

Returns: Invitation

Response Body
Field Type Required Description
id string Yes Invitation UUID
email string Yes Invitee email
role string (member, admin, auditor) Yes Assigned role
inserted_at datetime (ISO 8601) Yes Created at
updated_at datetime (ISO 8601) Yes Updated at
expires_at datetime (ISO 8601) Yes Expiry (default now+7 days; resend refreshes by 7 days)
workspace object Yes Workspace summary
workspace.id string Yes Workspace UUID
workspace.name string Yes Workspace name
accepted_at datetime (ISO 8601) No When the invite was accepted (null while pending)
accepted_by object No Accepting user (null while pending)
accepted_by.id string Yes User UUID
accepted_by.email string Yes User email
invited_by object No Inviting operator
invited_by.id string Yes User UUID
invited_by.email string Yes User email
Example
{
  "accepted_at": null,
  "accepted_by": null,
  "email": "invitee@acme.com",
  "expires_at": "2026-04-03T10:00:00Z",
  "id": "7b8c9d0e-1234-4abc-9def-0123456789ab",
  "inserted_at": "2026-03-27T10:00:00Z",
  "invited_by": {
    "email": "admin@acme.com",
    "id": "0f1cdac9-305d-4d11-a53d-3fca310c0635"
  },
  "role": "member",
  "updated_at": "2026-03-27T10:00:00Z",
  "workspace": {
    "id": "4ce4d170-e6d5-4118-8f4d-fac69dc099ea",
    "name": "Acme Corp"
  }
}
400

`expired` or `already_accepted`

401

No operator browser session

403

Owner or admin membership required

404

Invitation not found, or workspace_id is not the current workspace

429

Invitation email throttle exhausted (5 per recipient per workspace per hour, 100 per workspace per 24 hours, shared with create). Retry-After gives the wait in seconds.

DELETE
/api/v1/operator/workspaces/:workspace_id/invitations/:id Web Session

Cancel invitation

Description

Deletes a pending invitation. Owner/admin only. `:workspace_id` must be the session workspace.

Auth

Web Session
Parameters
Name In Type Required Description
workspace_id path string Yes Must be the session's current workspace UUID
id path string Yes Invitation UUID
Responses
204

Invitation cancelled

401

No operator browser session

403

Owner or admin membership required

404

Invitation not found, or workspace_id is not the current workspace

Where to go next

see also