Skip to main content

Feature: Org Onboarding

Last updated: 2026-08-18

This document is the single source of truth for org onboarding. Requirements and acceptance criteria were consolidated here on 2026-05-04.

Recent (2026-06-02): AM Setup Wizard slice 2 (#1319 β€” step 2 email_alias preview) and slice 3 (#1326 β€” OSA detail page Workspace section + Suspend/Restore, closes #1322) landed. Invite lifecycle locked in ADR-023 (#1286) β€” 7-day expiry, token rotation, user-initiated resend.

Recent (2026-06-05/06):

  • OTP verification now required before profile creation (#1949).
  • Optional onboarding steps no longer block activation; step-6 escape hatch added (#1807).
  • Waiting room flow rebuilt from first principles (#1904).
  • Mic permission flow: user gesture required, mic-only recovery path (#2029); URL-deeplink + mic-required / camera-optional permissions (#2010).
  • "Finish without invite" stranded-orgs banner safety net for AMs (#1698).

Overview​

Org onboarding is a two-phase process:

  1. Phase 1 (AM-assisted) β€” Internal Account Manager sets up the Org in the admin portal
  2. Phase 2 (Client confirmation) β€” Client Admin completes setup via a guided wizard at /onboarding (link delivered via invite email)

The result is an active Org with at least one Specialist assigned, Email channel configured, and a Client Admin who has confirmed the setup.

Actors​

ActorRoleWhat They Do
Account ManagerInternalCreates Org, assigns Specialists, sends invite
Client AdminClientConfirms setup (passwordless β€” no password step), configures channels
Super AdminInternalCan view/edit any onboarding; can impersonate AM

Phase 0: Validation Gate​

Before the invite is sent, the system hard-blocks until all pass:

  • All required company fields complete
  • AM's own profile complete

As of spec 2026-08-14, a Specialist assignment is no longer required β€” the client picks their own Specialist during Phase 2 onboarding, so the invite can go out with zero Specialists assigned. (Trial end date is optional, not gate-blocking β€” see am-setup-wizard.md Β§Step 3.)

Endpoint: GET /am/orgs/:orgId/phase0-status

Route note: AM-facing endpoints use the /am/orgs/ prefix internally (NestJS routing), but the frontend accesses them through /ops/clients/[id]/setup/* routes.

AM Wizard β€” 3 Steps​

Dedicated doc: the end-to-end AM Setup Wizard flow (step-by-step, with the email_alias preview and the OSA Workspace Suspend/Restore surface) now lives in am-setup-wizard.md. The summary below is retained for the onboarding-flow context.

2026-08-17: Specialist assignment (formerly Step 1.2) is no longer a wizard step β€” per the client self-service specialist selection spec (2026-08-14), the client now picks their own Specialist during Phase 2 onboarding instead of the AM pre-assigning one. See am-setup-wizard.md for the current 3-step flow and the AM-preassignment escape hatch (Reassign / Kaito-style launch links).

The AM wizard has 3 steps accessed from /ops/clients/new (step 1) and /ops/clients/[id]/setup/* (steps 2–3).

Phase 1: AM-Assisted Setup​

Numbering note: the headings below match the 3-step numbering used in am-setup-wizard.md (Company Info β†’ Expert Assignment β†’ Send Invite). Specialist Assignment sits between Step 1 and Step 2 but isn't itself numbered β€” see below.

Step 1 β€” Company Information​

AM enters on behalf of the client:

Required: company legal name, subdomain (slug), industry, primary corporate email domain(s), admin email address (optional), company website, country/timezone

The AM wizard labels this field "Subdomain" in the UI. The code and DB call it slug. After the client confirms in the onboarding workspace step, the slug is immutable.

Optional: company logo, billing email, company size, secondary email domains

Slug rules:

  • Format: ^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$ (3–63 chars)
  • Validated in real-time via GET /orgs/slug-check?slug=...
  • Reserved word list enforced (api, app, www, admin, etc.) β€” see config/reserved-slugs.ts
  • Not reserved until the client confirms in the workspace step

Email domain rules:

  • Personal domains blocked (gmail, yahoo, etc.) β€” see config/blocked-email-domains.ts
  • Admin email must match an approved corporate domain
  • No DNS verification required

Specialist Assignment (optional AM action, not a wizard step)​

As of 2026-08-17 this is no longer numbered Step 1.2 β€” the client picks their own Specialist during Phase 2 onboarding (see the specialist_selection step below) instead of the AM pre-assigning one. The mechanics below still apply whenever an AM does assign one directly (Reassign on an active client, or a preassignment engagement launch link).

AM selects 1+ Specialists from the platform pool. For each Specialist:

  • Confidence field (integer 0–101) is retained for schema/analytics compatibility and defaults to 0. Managed Hermes delivery never consults it.
  • Optional: designate a primary Specialist

Specialist constraints:

  • Specialist ↔ Org relationship is many-to-many via OrgSpecialistAssignment
  • Specialist profile must be complete (name, avatar, skill tags) β€” the Email field was dropped from the Specialist Create form in #1097 (ADR-0002 Phase 2)
  • Per-OSA email alias is auto-generated as {firstname}.{org-slug}@{HWORK_DOMAIN} at assignment time via the OrganizationsService.assignSpecialist hook (#1089). The alias is stored on OrgSpecialistAssignment.email_alias, not on the catalog Specialist.
  • The catalog Specialist no longer carries an email column.
  • Only SuperAdmin can create/modify Specialist personas. AMs assign existing ones.

Step 2 β€” Expert Assignment​

Route: /ops/clients/[id]/setup/step-3. AM grants Expert coverage for the Org through expert_access (org-scope or named-Specialist-scope). Coverage is informational, not gating (spec 2026-08-14): in the default flow there's no Specialist to cover yet β€” the client picks their own in Phase 2 β€” so this step is optional. Full detail: am-setup-wizard.md Β§Step 2.

The AM's own profile (name, avatar, email) is not edited as part of this wizard β€” it's a shared, cross-Org profile maintained elsewhere. Step 3 below just gates Send on it being complete already.

Step 3 β€” Send Client Invitation​

After Phase 0 validation passes:

  • System sends welcome email to admin email with AM name/avatar, org name, workspace URL, and CTA
  • Invitation token expires in 7 days; resending issues a fresh 7-day window
  • Trial clock does NOT start until Phase 2 completion
  • Org status: pending_client_confirmation
  • Stale orgs (90+ days in pending_client_confirmation) are auto-archived; restorable by Super Admin

Step 3 UX (as of PR #243):

  • Send button is enabled once company info, the trial date choice, and the AM profile gate all pass. Specialist assignment is not one of the gates (spec 2026-08-14) β€” an org with zero Specialists can send. The trialEndDateSet check is satisfied client-side (trial date is set on this very step).
  • "Copy Onboarding Link" button is available before and after sending β€” AM can share the link directly without waiting for the email.
  • "Complete Setup" CTA appears for orgs already in pending_client_confirmation status (re-entry after first send).

Expert onboarding email templates (#1554 / #1560)​

See also: docs/features/expert-application.md β€” the full Tavus-driven application β†’ interview β†’ promote flow these emails fire on.

In parallel with client onboarding, Expert onboarding now uses 5 dedicated email templates rendered through Resend (#1554 / #1560). The 5 templates:

Template keyTriggerAudience
expert.application_receivedNew marketing-lead Expert application submittedApplicant
expert.interview_scheduledInterview slot booked via the application flowApplicant
expert.application_approvedSuperAdmin promotes the marketing lead to an ExpertNew Expert
expert.application_declinedSuperAdmin declines the marketing leadApplicant
expert.welcomeFirst successful login to /workspaceNew Expert

Templates live under api/src/email/templates/expert/ and are version-pinned; copy changes go through PR review. AM/client-side onboarding emails are unaffected β€” those still use the client templates documented in Step 3 above.

Phase 2: Client Confirmation​

Client accesses the onboarding wizard via the invite email link (/onboarding route).

Week-1 slim-down (2026-07-20): the wizard shape is now server-owned and per-org. GET /onboarding/:token/resume-step returns the ordered step-id list for the org plus the resume index, and the frontend renders exactly that list (frontend/src/app/onboarding/start/page.tsx no longer hardcodes a sequence). The canonical step ids, in order:

Step idLabelPresent when
workspaceWorkspace setupAlways
agreementsAgreementsonboarding_agreements_required flag ON (default ON; org-scoped OFF for internal dogfood orgs β€” skips the wizard step, the completeOnboarding guard, and the re-consent banner)
business_contextClient infoAlways (skippable) β€” client reviews/corrects the AI-drafted company profile (web research seeded automatically, #6036)
specialist_selectionChoose specialistOrg has zero active Specialist assignments, or at least one active assignment has metadata.assigned_via === "client_onboarding" (spec 2026-08-14) β€” this is an "any match" check, not "only": an org with one AM-preassigned Specialist plus one client-picked Specialist still gets this step. Purely AM-preassigned orgs (no client-onboarding-origin assignment at all) skip it.
specialist_intakeOnboard your Specialist (Tavus video call)onboarding_video_intake_enabled flag ON (default ON; org-scoped OFF override available for tenants that need the step skipped)
doneDoneAlways

Order note (#6036 + spec 2026-08-14): business_context runs before specialist_selection and specialist_intake β€” the client reviews/corrects the company profile and picks their Specialist from it first, so the intake call is never held with a Specialist the client didn't choose.

The Business details (legal profile) and Your h.work team steps were removed from the wizard entirely β€” legal profile collection moves to post-onboarding client settings (the /:token/legal-profile endpoints remain for that surface).

Ops runbook β€” flag flips:

  • To onboard an internal dogfood org without agreements, set an org-scoped onboarding_agreements_required = false override (Ops β†’ Feature Flags) before sending the invite. The default is ON, so external clients are unaffected.
  • The shape is re-resolved at completeOnboarding, so flipping onboarding_agreements_required ON for an org that is already mid-wizard makes in-flight sessions get a 403 on their next completion attempt (fail-closed β€” we never activate a client with a missing acceptance-ledger row). Recovery is a page refresh: /onboarding/start re-fetches the shape and routes the client to the now-present agreements step. Expect this if you flip the flag on a live tenant.

UX Update (PRs #267-271, 2026-05-10): The onboarding wizard was simplified from 6 steps to 3 steps. Steps 2.4 (Set Up Channels) and 2.5 (Optional Steps) were removed as dedicated wizard steps. Channels and Billing are now accessed post-onboarding from /client/settings/*.

The subsections below walk through each step id from the table above, in order.

workspace β€” Workspace Setup​

Single combined step (folds the old separate "verify account" and "review company info" steps into one):

  • No separate email-verification step (#3488): clicking the invite link (emailed only to the admin) already proves email ownership, so the onboarding session is bootstrapped from the token via POST /:token/session when the wizard loads. The earlier emailed-OTP step re-verified the same address through the same channel and was removed as redundant double verification.
  • Client enters full name and reviews/edits company info. The slug is permanently reserved when this step is submitted, via a pessimistic write lock + server-side re-validation to prevent race conditions.
FieldClient Can Change?
Full nameYes β€” required
Company nameYes
SlugYes (once, here only) β€” immutable after this step
IndustryYes
TimezoneYes (pre-filled from the browser's own timezone)
WebsiteYes (optional)
Company domain(s)No β€” read-only, AM-controlled
Admin emailNo β€” read-only. Locked to the address the invite was sent to; the UI shows a lock icon and "can't be changed here."

Job title and phone are still accepted by POST /:token/profile (SetProfileDto.jobTitle / .phone) but the current step UI doesn't collect them β€” no step asks for them anymore, so they go unset unless some other caller sends them. Country isn't collected in this step either (it's AM-entered in Phase 1 and not re-editable here).

agreements β€” Agreements​

Modal presents four documents in tabs: Terms of Service, Privacy Policy, Acceptable Use Policy, Data Processing Addendum. Each tab requires scroll-to-bottom before the corresponding accept checkbox is enabled (#1046 / #1048 / #1068).

  • All four must be accepted before the wizard advances.
  • Acceptance writes to the legal acceptance ledger (org Γ— user Γ— document version Γ— timestamp) via POST /:token/agreements.
  • A re-consent banner re-prompts when any document version is bumped post-acceptance.

Cross-reference: frontend/legal/ and the TOS/Privacy/AUP/DPA source documents.

business_context β€” Client Info​

  • GET /:token/company-research runs public web research on the company and returns an AI-drafted profile (with citations) for the client to review (#6036).
  • Client accepts it (POST /:token/company-research β€” saves to the KB) or rejects/skips it (POST /:token/company-research/reject β€” no KB write, but recorded so the intake-call script knows the profile wasn't confirmed).
  • Client can also optionally upload supporting documents, URLs, or free-text prompts directly (POST /:token/business-context β€” up to 10 files, 10 MB/file cap, deliberately tighter than the KB's normal 50 MB limit).
  • Fully skippable either way; skipping is stamped onto org.metadata.business_context_skipped with a timestamp.

specialist_selection β€” Choose Specialist​

Client browses an ops-curated, client-selectable catalog β€” GET /:token/catalog for the initial "top picks" load, GET /:token/specialists/catalog?q=... for the "Show all specialists" search/browse expansion β€” and picks one Specialist (POST /:token/specialist, same provisioning as an AM assignment, minus billing). GET /:token/specialist-selection-status guards against re-picking if a resume bounces the client back through business_context (#6118). See the visibility rule in the step table above β€” this step doesn't show for every org.

specialist_intake β€” Onboard Your Specialist (video call)​

In-browser video call (Daily-embedded) to meet the chosen Specialist. Client can start the call immediately or defer it to schedule for later.

Read-only Specialist info (name, avatar, skill tags, bio, per-OSA contact alias {firstname}.{org-slug}@{HWORK_DOMAIN}) is shown alongside; the client does not see the Specialist's composition (AI Agent + Experts behind the persona). To request a different Specialist after onboarding: Settings (AM fulfills the request).

Two now-orphaned endpoints from an earlier "Meet Your Specialist" design: GET /:token/specialist and GET /:token/specialists (#887) are still served by the backend but nothing in the current wizard calls either one β€” see Dead/legacy endpoints below.

done β€” Onboarding Complete​

  • Org status: pending_client_confirmation β†’ active
  • Trial clock starts (org.trialStartedAt = now())
  • Client Admin redirected to /client/chat
  • AM receives email notification
  • Welcome summary email sent to Client Admin (Specialist contact info, workspace URL, next steps)
  • Onboarding wizard link becomes invalid (token status = 'accepted')

Post-Onboarding Configuration: Channels (Slack, WhatsApp, Email settings) and Billing are configured from /client/settings/* after onboarding completion. These were previously Steps 2.4 and 2.5 of the wizard but were moved to post-onboarding to reduce friction.

API Endpoints​

All onboarding endpoints are prefixed with /onboarding/:token/. There is no set-credentials / password endpoint β€” onboarding is fully passwordless (see workspace above); an earlier version of this doc listed one that never shipped this way.

EndpointDescription
POST /:token/sessionBootstrap session from token (#3488) β†’ session JWT
POST /:token/verify-emailSend OTP to admin email (legacy, superseded by /session)
POST /:token/confirm-otpVerify OTP β†’ session JWT (legacy, superseded by /session)
POST /:token/profileSave full name (+ optional job title, phone β€” unused by the current UI)
GET /:token/orgOrg details for the wizard
POST /:token/confirm-slugFinalize and reserve the slug
GET /:token/agreements/versionsCurrent required version per document β€” the wizard fetches this instead of a hardcoded version, so a version bump on any one document doesn't break the whole step
POST /:token/agreementsRecord acceptance of all required legal agreements. The required version for each kind is resolved server-side, never taken from the request body
POST /:token/business-contextUpload supporting files/URLs/prompts for the business_context step
GET /:token/company-researchRun web research, return an AI-drafted company profile
POST /:token/company-researchSave the client-approved profile to the KB
POST /:token/company-research/rejectRecord that the client rejected/skipped the researched profile
GET /:token/catalogClient-selectable Specialist catalog β€” "top picks"
GET /:token/specialists/catalog?q=Same catalog β€” search/browse ("Show all specialists")
POST /:token/specialistClient picks their Specialist
GET /:token/specialist-selection-statusWhether the client already picked one (resume guard, #6118)
GET /:token/resume-stepServer-owned wizard shape: ordered step ids + resume index
POST /:token/completeFinalize onboarding
POST /:token/legal-profileSave legal profile (post-onboarding Settings surface, not a wizard step)
GET /:token/legal-profileRead the saved legal profile

Dead/legacy endpoints​

Still served by the backend, but nothing in the current wizard calls them β€” left over from earlier wizard shapes:

EndpointOriginSuperseded by
GET /:token/specialistOriginal single-Specialist cardspecialist_intake's own display + GET /:token/catalog
GET /:token/specialists#887 "Your h.work team" step (removed in the week-1 slim-down)same
POST /:token/invite-membersOld "invite your team" wizard step (removed 2026-05-10, PRs #267-271)post-onboarding /client/settings/*
GET /:token/whitelistOld in-wizard whitelist management/am/orgs/:orgId/email-whitelist and /client/orgs/:orgId/email-whitelist
POST /:token/whitelistβ€³β€³
DELETE /:token/whitelist/:idβ€³β€³

AM-side endpoints are under /am/orgs/ (NestJS routing) β€” see docs/api/reference.md.

Status After Audit (2026-06-04)​

The 2026-04-30 snapshot table previously shown here is superseded by the weekly audit reports under docs/audits/. Open onboarding-area items as of 2026-06-04:

  • WhatsApp onboarding UX β€” issue #101
  • Slug churn / rebrand / cross-env enforcement β€” AC-SLUG-04 / 05 / 06
  • Expert application interview flow β€” covered by #1083, #1519, #1524, #1565; see the Expert-onboarding-doc-home open question flagged in the 2026-06-04 audit.