Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Membership & Invitations

Users join organizations through email invitations. An admin sends an invite, the recipient signs in (or signs up), accepts, and becomes a member with the assigned role.

In the Dashboard

  1. Open an organization's detail page.
  2. Switch to the Invitations tab.
  3. Click Invite member, enter the recipient's email, and pick a role.
  4. The recipient receives a branded email with an Accept Invitation link.
  5. When they accept, they appear in the Members tab and the invitation is marked accepted.

Admins can revoke pending invitations from the same tab.

Invitation Flow

Admin sends invitation

POST /api/organizations/:id/invitations creates the invitation row, then sends the email. If the email send fails, the row is rolled back so the admin can retry.

Recipient receives email

The email links to {appPublicBaseUrl}/accept-org-invite?token=<64-hex-token>.

Recipient signs in

If the recipient isn't authenticated, they're sent through the normal sign-in flow with a redirect back to the accept page.

Recipient accepts

The frontend POSTs /api/organizations/accept-invitation with the token. The server verifies the token matches a pending, unexpired invitation and that the caller's email matches the invitation, then adds the membership atomically.

API

Invite a member

POST /api/organizations/:id/invitations
{ "email": "newmember@example.com", "role": "member" }

Admin-only. Returns 409 if there's already a pending invitation for that email or the user is already a member. Returns 502 if the invitation row was created but the email failed to send (the row is rolled back).

List invitations

GET /api/organizations/:id/invitations?status=pending&page=1&limit=20

Admin-only. status may be pending, accepted, or expired. Tokens are never returned.

Revoke an invitation

DELETE /api/organizations/:id/invitations/:invitation_id

Admin-only.

Accept an invitation

POST /api/organizations/accept-invitation
{ "token": "<64-hex-character token>" }

Requires the caller to be authenticated. The caller's email (lower-cased) must match the invitation's email. Returns 400 for unknown/expired tokens (anti-enumeration), 403 for email mismatch, 409 if the caller is already a member.

List members

GET /api/organizations/:id/members?page=1&limit=20

Any member can read. Returns { user_id, email, name, role, created_at, updated_at } per row.

Update a member's role

PUT /api/organizations/:id/members/:user_id
{ "role": "admin" }

Admin-only. Returns 409 (last-admin guard) if the change would leave the organization with zero admins.

Remove a member

DELETE /api/organizations/:id/members/:user_id

Admin-only. Same 409 last-admin guard applies.

Multiple Organizations

A user can belong to multiple organizations within the same application. A freelancer could be a member of several client organizations simultaneously; an active-organization session claim and switcher UI are on the roadmap.