Invitations

Organization owners and admins can invite new members by email. Invitations work for both existing users and people who haven't signed up yet.

API Routes

GET    /api/organizations/{org}/invitations          # list pending invitations
POST   /api/organizations/{org}/invitations          # send an invitation
DELETE /api/organizations/{org}/invitations/{id}     # cancel an invitation
GET    /api/invitations/{token}                      # public preview (no auth)
POST   /api/invitations/{token}/accept               # accept an invitation

Sending an Invitation

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

The system generates a unique 64-character token and sends an invitation email with a link.

Invitation Model

Key fields on the Invitation model:

  • organization_id — the org being joined
  • email — invitee's email address
  • role — the org role assigned on accept (e.g. member, admin)
  • token — 64-character unique token
  • invited_by — user ID of the person who sent the invite
  • accepted_at — timestamp when accepted (null while pending)
  • expires_at — expiration timestamp

Public Preview

GET /api/invitations/{token}

This endpoint requires no authentication. It returns a preview of the invitation including the organization name, so the invitee knows which team they're joining before logging in or registering.

Accepting an Invitation

POST /api/invitations/{token}/accept
  • Existing users: Added to the org immediately and receive an in-app notification
  • New users: Guided through the registration flow first, then auto-added to the org

Per-Seat Billing

When an invitation is accepted, the SeatSyncService is called automatically. If the org's plan uses per-seat billing, the subscription quantity is updated in Stripe to reflect the new member count.