Invitation tokens are random plaintext values whose SHA-256 hashes are stored in the database. The plaintext value appears only in the creation response and the queued invitation link. Never log it or put it in analytics.
Organization-management routes
All routes in this section require an authenticated caller with the
organization.members.manage capability for the active organization.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/organizations/{org_slug}/invitations |
Create an invitation |
GET |
/api/organizations/{org_slug}/invitations |
List organization invitations |
POST |
/api/organizations/{org_slug}/invitations/{invitation_id} |
Cancel a pending invitation |
POST |
/api/organizations/{org_slug}/invitations/{invitation_id}/accept |
Accept as a member manager |
Create with JSON { "email": "person@example.com", "role": "member" }.
role may be member, admin, or an existing custom organization-role slug;
assigning a non-member role also requires role-management capability. Duplicate
pending invitations and invitations for existing members are rejected.
Creation returns:
{
"invitation": {
"id": "inv-uuid",
"org_id": "org-uuid",
"email": "person@example.com",
"role": "member",
"invited_by": "user-uuid",
"status": "pending",
"expires_at": "2026-08-09T12:00:00Z",
"created_at": "2026-08-02T12:00:00Z"
},
"inviter": {
"id": "user-uuid",
"email": "owner@example.com",
"created_at": "2026-01-01T00:00:00Z"
},
"token": "one-time-plaintext-token"
}
The nested invitation never contains its stored token hash, and the inviter
object never contains account credentials. Organization listing accepts page
and limit (default 50, maximum 100) and returns an array of
{ invitation, inviter } objects without any token. The currently accepted
status query parameter is reserved and does not filter results.
Authenticated invitee routes
| Method | Path | Body |
|---|---|---|
GET |
/api/invitations |
none |
POST |
/api/invitations/accept |
{ "token": "..." } |
POST |
/api/invitations/{invitation_id}/accept |
none |
POST |
/api/invitations/{invitation_id}/decline |
none |
GET /api/invitations returns pending invitations whose email equals the
authenticated user’s email. Each item contains id, email, role,
expires_at, created_at, organization_slug, and organization_name.
Token acceptance is authenticated and additionally binds the invitation to the
caller’s email. ID-based acceptance and decline enforce the same email binding.
Acceptance checks expiry and organization user limits inside the transaction.
Successful mutation returns JSON null.
Public routes
| Method | Path | Purpose |
|---|---|---|
POST |
/api/invitations/decline |
Decline with { "token": "..." } |
GET |
/invitations/accept/{token} |
Redirect to the hosted acceptance UI |
The public decline route is possession-based, single-use, and accepts only a
pending token. The redirect does not consume the token; it sends the browser to
/invitations/accept?token=... on the configured web client.
Failure behavior
Missing organization access returns 403; cross-tenant or already-processed
resources return 404 where appropriate. Invalid roles, expired invitations,
existing members, duplicates, and exhausted user limits return a client error.
Treat all invitation failures as non-enumerating in user-facing interfaces.
The Complete Endpoint Catalog is the authoritative method/path inventory.