Inviting people into an organization, done properly: choose who (an existing account-holder or
an external person), how to reach them (email or WhatsApp), their role and their
member type (employee or customer); a brand-new invitee resolves the link, signs up (incl. Google /
Facebook) and is auto-bound to the org; and the root owner invites external customers directly.
These frames freeze the flows, screens, states and build inventory before any UI is written. Rendered in the
fuse-seam design system (tokens only). No identity-vendor name appears — the browser only ever sees
app.fuzefront.com and, for social sign-up, accounts.google.com / facebook.com.
Invitee kind (existing / external), channel (email / WhatsApp), role (admin / member / viewer), member type (employee / customer), live review → POST /invitations.
Per-org pending list on the Members tab: status pills, resend (rate-limited), revoke. GET /invitations.
Public landing resolves the token and previews org + role + member type before sign-up. GET /invitations/resolve.
Create account (Google / Facebook / email, locked to the invited address) → POST /invitations/accept auto-binds → confirms org + role + member type.
Root owner invites an external person (Gmail / WhatsApp) as a customer; member type fixed, role defaults to Viewer. POST /invitations.
Loading / empty / error, channel & validation variants, and the fail-closed contract: 410 expired-revoked, 403 mismatch, 409 already-accepted, 409 last-owner, 403 forbidden.
/settings/access · approved: false/invitations/accept · approved: false/root/customers · approved: false@fuzefront/identity-ui (beside authz-admin,
member-directory, employee-console). Reuses SocialButton from @fuzefront/auth-ui and
the generated @fuzefront/security-client. Orchestrator decision: keep in identity-ui unless
the multi-channel + public accept-bind scope earns an independent @fuzefront/org-invitations-ui
package — flagged here, not assumed.frontend-engineer adds these to @fuzefront/design-system as a
foundation PR before the feature UI depends on them — drawn here from tokens only, never one-offed:backend/src/routes/invitations.ts, organizations.ts). This approval commissions
contract-designer to freeze the following in packages/security/openapi.yaml →
@fuzefront/security-client:POST /v1/security/tenants/{tenantId}/invitations — create (InvitationCreate → 201 Invitation).GET /v1/security/tenants/{tenantId}/invitations — list pending (InvitationPage, cursor).POST /v1/security/tenants/{tenantId}/invitations/{invitationId}/resend — resend (202; rate-limited).DELETE /v1/security/tenants/{tenantId}/invitations/{invitationId} — revoke (204, idempotent).GET /v1/security/invitations/resolve?token= — public resolve (InvitationPreview; 410 expired/revoked, 409 already-accepted).POST /v1/security/invitations/accept — accept + bind ({token} → 200 Membership; 403 IDENTITY_MISMATCH, 410 expired/revoked, 409 already-accepted).Invitation.channel (email | whatsapp), Invitation.memberType (employee | customer) — member_type is net-new (today the distinction is structural, not a field).services/email-service org-invite template) — WhatsApp is net-new.identity.invitation.created / accepted / revoked (Kafka Zod in shared/); accept also emits the existing identity.membership.added.LAST_OWNER invariant on ownership-affecting actions (extends authz-admin's LAST_ADMIN to the owner tier).