Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesLeads CrmLeads & CRM

Leads & CRM

What is this

A lead is a prospective customer — someone who filled out a minisite form, walked in, came from Instagram, etc. — who isn’t yet a paying member. FitKit splits leads into two layers:

  • leads — the canonical lead row: name, email, phone, locale, source, status, note. Used for both platform leads (people interested in FitKit-the-platform) and organization leads (people interested in a specific studio).
  • organization_leads — the per-studio link that attaches a lead to an organization, an optional assignee (staff user), an optional trial date, and the converted membership (when the lead becomes a member).
  • platform_leads — the FitKit-marketing-site waitlist; no org attached.
  • lead_status_events — full audit trail of status transitions with from_status, to_status, changed_by_user_id.

The CRM is gated behind the lead_management platform feature (@RequiresFeature('lead_management') decorator on the org-leads controller) — only orgs on tiers that include this feature can use it.

Who uses it

PersonaWhy
Public (unauthed)Submits a minisite contact form (POST /leads/organization/:orgId) or platform waitlist (POST /leads).
OwnerReviews + assigns + converts leads; lead-management is the conversion funnel.
AdminSame as owner; can convert.
CoachSame as owner/admin for read/update; cannot convert (convert requires owner/admin in convertLead).
MemberNo access.

Persona impact

  • Public minisite forms are server-side de-duplicated against this org’s existing leads by email+phone. Duplicates return 409 with existingLeadId.
  • Auto-task creation — on every new org-lead, a tasks row of type contact_lead, priority high, source auto is created with dueDate=tomorrow and assigneeId=org owner.userId. Surfaces in the dashboard task widget.
  • Conversion creates a membership with source_lead_id set and emits MEMBERSHIP_ACTIVATED { source: 'lead_converted' } — kicks off downstream onboarding (compliance forms fan-out, etc.).

High-level capabilities

  1. Platform leadsPOST /leads (@Public). Inserts leads + platform_leads (waitlist tag).
  2. Minisite lead submitPOST /leads/organization/:orgId (@Public). Org-side dedup, inserts leads + organization_leads. Source forced to minisite.
  3. List / search leadsGET /organizations/:orgId/leads (staff only). Paginated, filterable by status, source, free-text search over name/email/phone.
  4. Lead detailGET /organizations/:orgId/leads/:leadId. Returns the lead + the assignee user info + status events history.
  5. Manual createPOST /organizations/:orgId/leads (staff). Same dedup as minisite path, but with arbitrary source (default manual).
  6. Update leadPATCH /organizations/:orgId/leads/:leadId. Fields: name, email, phone, note, status, assignedToUserId, trialDate. Status transitions emit a lead_status_events row with the actor.
  7. Convert to memberPOST /organizations/:orgId/leads/:leadId/convert (owner/admin). Creates user (or finds by email) + membership with source_lead_id. Marks lead converted. Emits MEMBERSHIP_ACTIVATED.
  8. AnalyticsGET /organizations/:orgId/leads/analytics. Returns total, newThisMonth, converted, conversionRate (rounded %), bySource[], byStatus[].

Lead source / status enums

lead_source: minisite | qr | instagram | whatsapp | facebook | website | referral | walk_in | phone_call | manual | course_purchase lead_status: new | contacted | trial_booked | converted | lost

Relationship to other features

  • membershipsconvertLead inserts a membership with source_lead_id. Emits MEMBERSHIP_ACTIVATED { source:'lead_converted' }.
  • users-authconvertLead finds-or-creates a user shell by email.
  • organizationsorganization_leads.organization_id is the direct org boundary.
  • Tasks (out-of-bucket) — contact_lead auto-task on new lead; cancellation_review, reengagement, etc. are separate task types.
  • Platform tiers — controller-level @RequiresFeature('lead_management') decorator. Orgs without this feature get 403 on every endpoint.
  • Minisite (out-of-bucket) — public contact form posts here.

Current status

Shipped. The status history (lead_status_events) is the recent addition; getLeadById returns the full event list.

Known gaps

  • convertLead is the only place where a members row of type lead_converted is created. There is no “convert without a paying plan” subscription — converted members start with status='active', payment_status='none'. The first subscription/plan must be assigned separately.
  • Auto-task creation is best-effort — wrapped in a try/catch that swallows errors so the lead creation never fails.
  • No bulk operations on leads (bulk-assign, bulk-status, bulk-convert).
  • No “merge leads” workflow — the dedup check returns 409 with existingLeadId; the UI must direct the user to the existing record.
  • Status events are insert-only (no edit/delete of history).