Invitations
Add users to an organization through email invites.
Flow
[admin / owner] [invitee]
│ │
│ POST /v1/orgs/:org_id/invitations │
│ { email, role } │
│ ──────────────────────────────────► │
│ │
│ email with link │
│ {public_base_url}/ │
│ accept-invite?token=... │
│ │
│ │ click link
│ │
│ │ GET /v1/auth/invitations/preview?token=...
│ │ → { organization_name, role, ... }
│ │
│ │ POST /v1/auth/invitations/accept
│ │ { token, password?, display_name? }
│ │ → session issued
Roles you can invite
admin, billing, member, viewer. owner is intentionally NOT
grantable through invitations — use the ownership-transfer flow for that.
Token storage
Raw token (32-byte hex) is delivered exactly once via email. The database
stores only SHA-256(token).
Lifecycle
| State | Meaning |
|---|---|
pending | Live; can be accepted. |
accepted | Consumed. |
revoked | Manually revoked via DELETE. |
expired | Past expires_at (set by application code, not enforced by DB). |
TTL: 7 days.
Re-sending
POST /v1/orgs/:org_id/invitations/:id/resend
mints a fresh token (overwriting the prior hash), resets the expiry, and
re-emails. The old token is dead immediately.
Accepting
The accept endpoint has two modes:
- New user: token + password (+ optional display_name). Creates the
user with
email_verified: true, adds the membership, returns a session. - Already signed in: token only. Adds membership in the invited org, returns 204. The caller's existing session continues.
If the caller is signed in but their email doesn't match the invite, 403.
Public routes
GET /v1/auth/invitations/preview and POST /v1/auth/invitations/accept
must be mounted OUTSIDE the auth middleware. The SDK exports them on a
separate router (routes::invitations::router_public()) for exactly this
reason.
Audit
members.invitedmembers.invitation_revokedmembers.invitation_resentmembers.invitation_accepted
Limits
- No "max pending invites per org" cap in v0.1. Hosts that want one should add their own pre-check before calling create.
- No throttling on
resend. Same — add your own rate-limit if needed.