Invite Lifecycle
User-facing reference for the team-invite lifecycle: how an invite is created, delivered, accepted, resent, and expired across the surfaces that touch it.
The lifecycle rules are locked by ADR-023 (expiry, token rotation, self-service resend). This doc is the user-/operator- facing view; ADR-023 is the authoritative spec and the source of truth for any behaviour described here.
Surfacesโ
The same invite lifecycle is reached from three places:
- AM onboarding โ
/ops/clients/[id]/setup/step-4: an Account Manager invites the first client admin while standing up a new client. - Client self-service โ
/client/settings/team: a clientorg_admin/ owner invites and manages their own colleagues. - Ops โ
/ops/team: a SuperAdmin manages invites/members across any org.
All three drive the same API endpoints and the same invites row lifecycle.
State machineโ
An invite is one row in invites (scoped to org_id + email):
POST /orgs/:orgId/invites
(none) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโถ pending
โ
POST /auth/accept-invite-by-token โ accepted โ User row active
pending โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโถ โ (used_at set)
โ
7 days elapse (expires_at < now) โ expired
pending โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโถ โ (link dead; row remains for audit)
- Expiry: 7 days, hardcoded.
INVITE_TTL_MS = 7 * 24 * 60 * 60 * 1000(api/src/auth/invite-lifecycle.ts). Long enough for "I'll get to it tomorrow", short enough that a leaked link doesn't grant access forever. (ADR-023 D1.) - Acceptance is one-shot. Delivery of the invite UUID to the recipient's mailbox is the email-ownership proof, so accepting does not demand a second OTP.