Backend Onboarding API Documentation
Overview
This document details the backend implementation of the user onboarding system, which collects essential information from doctors, clinic owners, and allied health professionals during the signup process. The onboarding data is stored temporarily in the user_flags table until the Kulcare team creates actual profiles.
API Endpoints
GET /user_onboarding
Retrieves the current user's onboarding progress and data.
Authentication: Required (Bearer token)
Purpose:
- Loads existing onboarding data when user refreshes the page
- Allows users to continue from where they left off
- Synchronizes frontend state with backend storage
Data Source:
- Retrieves data from
user_flags.onboarding_dataJSONB field - Returns structured onboarding progress including completed steps and step-specific data
POST /user_onboarding
Saves onboarding data for the current user.
Authentication: Required (Bearer token)
Purpose:
- Saves step completion data after each onboarding step
- Updates user's onboarding progress in real-time
- Stores all collected information in JSONB format
Data Storage:
- All data is stored in
user_flags.onboarding_dataas JSONB - Preserves complete onboarding state for later retrieval
- Enables seamless continuation after page refresh or re-login
Processing Logic:
- Validates incoming data structure
- Updates or creates user_flags record for the user
- Merges new step data with existing onboarding data
- Marks step as completed in the completed_steps array
- Updates engagement metrics (achievements, alumni count, etc.)
Onboarding Flow Overview
The onboarding flow consists of 10 steps:
- Welcome Step - User type selection (doctor, clinic_owner, allied_health)
- Value Discovery - Platform value propositions
- Basic Info - Personal information (name, email, phone, location, country)
- Professional Details - Registration details, specialization, experience, fees
- Qualifications - Education, work experience, certifications, memberships
- Quick Connect Preview - Profile preview and alumni discovery
- Practice Info - Practice settings, consultation types, languages, special interests
- Document Upload - Professional documents (licenses, certificates, etc.)
- Review & Consent - Terms acceptance and data review
- Celebration - Completion with next steps
Elasticsearch Integration
The onboarding system relies on Elasticsearch indices to provide dynamic, searchable data for various dropdowns and selections. All indices follow a naming pattern: {table_name}_{environment} (e.g., basic_degrees_staging).
1. basic_degrees
Purpose: Provides degree options for the Qualifications step
Index Structure:
{
id: integer,
name: string,
abbreviation: string,
description: string,
category: string # 'medical' or 'allied_health'
}Filtering:
- Category filter: Returns degrees matching user type
medicalfor doctorsallied_healthfor allied health professionals
Sorting: Alphabetical by name
Usage: Step 5 (Qualifications) - Degree selection dropdown
2. basic_universities
Purpose: Provides institution/university options for education entries
Index Structure:
{
id: integer,
name: string,
city: string,
state: string,
country: string,
alpha_2_code: string # Country code: 'IN', 'US', 'AE', etc.
}Filtering:
- Country filter: Uses
alpha_2_codeto match selected country from Step 3 - Provides autocomplete search functionality
Sorting: Alphabetical by name
Usage: Step 5 (Qualifications) - Institution selection with autocomplete
3. default_medical_councils
Purpose: Provides medical council/registration board options
Index Structure:
{
id: integer,
name: string,
alpha_2_code: string,
council_type: array # ['medical', 'allied_health'] or both
}Filtering:
- Country filter: Uses
alpha_2_code - Council type filter:
- Filters by
'medical'for doctors - Filters by
'allied_health'for allied health professionals
- Filters by
Key Features:
- Councils can support both medical and allied_health types (array field)
- Same council can appear for both user types if applicable
- Example: "Dubai Health Authority" has
council_type: ['medical', 'allied_health']
Sorting: Alphabetical by name
Usage: Step 4 (Professional Details) - Registration council selection
4. default_special_interests
Purpose: Provides special interest/focus area options
Index Structure:
{
id: integer,
name: string,
category: string,
alpha_2_code: string
}Filtering:
- Country filter: Uses
alpha_2_code - Category filter: Optional categorization of interests
Sorting: Alphabetical by name
Usage: Step 7 (Practice Info) - Special interests multi-select
5. default_professional_memberships
Purpose: Provides professional membership/association options
Index Structure:
{
id: integer,
name: string,
abbreviation: string,
description: text,
alpha_2_code: string,
profession_type: string, # 'medical' or 'allied_health'
specialization_category: string
}Filtering:
- Country filter: Uses
alpha_2_code - Profession type filter:
profession_type = 'medical'for doctorsprofession_type = 'allied_health'for allied health professionals
Sorting: Alphabetical by name
Usage: Step 5 (Qualifications) - Professional memberships multi-select
6. default_allied_health_professions
Purpose: Provides allied health profession options (for allied_health user type only)
Index Structure:
{
id: integer,
name: string,
alpha_2_code: string,
category: string
}Filtering:
- Country filter: Uses
alpha_2_code - Category filter: Optional profession categorization
Sorting: Alphabetical by name
Usage: Step 4 (Professional Details) - Profession selection for allied_health users
7. default_allied_health_specializations
Purpose: Provides specialization options for allied health professionals
Index Structure:
{
id: integer,
name: string
}Filtering: None (returns all specializations)
Sorting: Alphabetical by name
Usage: Step 4 (Professional Details) - Specialization selection for allied_health users
8. basic_languages
Purpose: Provides language options for the Practice Info step
Index Structure:
{
id: integer,
name: string,
default_name: string,
native_name: string,
alpha_2_code: string
}Filtering:
- Country filter: Uses
alpha_2_code - Returns languages commonly spoken in the selected country
Sorting: Alphabetical by name
Usage: Step 7 (Practice Info) - Languages spoken multi-select
Data Population and Seeding
All Elasticsearch indices are populated from JSON files and seeded into PostgreSQL tables, then indexed to Elasticsearch.
Seed File Locations
backend/lib/data/basic_degrees.jsonbackend/lib/data/basic_universities.jsonbackend/lib/data/medical_councils.jsonbackend/lib/data/special_interests.jsonbackend/lib/data/professional_memberships.jsonbackend/lib/data/allied_health_professions.jsonbackend/lib/data/allied_health_specializations.jsonbackend/lib/data/languages.json
Seeding Process
- Load JSON data: Read from
lib/data/*.json - Create database records: Insert into respective PostgreSQL tables
- Trigger Elasticsearch indexing: After-save callbacks reindex data
- Verify indices: Ensure all records are searchable
Reindexing Commands
# Reindex all onboarding-related indices
bundle exec rake es_reindex_basic_degrees
bundle exec rake es_reindex_basic_universities
bundle exec rake es_reindex_default_medical_councils
bundle exec rake es_reindex_default_special_interests
bundle exec rake es_reindex_default_professional_memberships
bundle exec rake es_reindex_default_allied_health_professions
bundle exec rake es_reindex_default_allied_health_specializations
bundle exec rake es_reindex_basic_languagesUser Type Handling
User Type Values
The system recognizes three user types set during Step 1 (Welcome):
-
doctor
- Full medical professional onboarding
- Access to medical councils, degrees, and memberships
- Shows registration number and council fields (country-dependent)
-
clinic_owner
- Clinic management focused onboarding
- Similar to doctor flow with clinic-specific fields
- Shows registration fields (country-dependent)
-
allied_health
- Allied health professional onboarding (therapists, nutritionists, etc.)
- Access to allied health specific professions, councils, and specializations
- Different data filtering throughout the flow
Conditional Data Fetching
The backend API and Elasticsearch queries adapt based on user_type:
Step 4 - Professional Details:
user_type === 'allied_health': Fetches from allied health indicesuser_type !== 'allied_health': Fetches from medical indices
Step 5 - Qualifications:
- Degrees filtered by category matching user_type
- Memberships filtered by profession_type matching user_type
Medical Councils:
- Filters council_type array based on user_type
user_type === 'allied_health': Returns councils with 'allied_health' in council_typeuser_type === 'doctor': Returns councils with 'medical' in council_type
Country-Based Dynamic Behavior
Several fields and data sources update dynamically based on the country selected in Step 3 (Basic Info).
Country Code Values
IN: IndiaUS: United StatesAE: United Arab Emirates
Dynamic Updates by Step
Step 4 (Professional Details):
- Registration number field visibility
- Registration council field visibility
- Medical councils list filtered by
alpha_2_code
Step 5 (Qualifications):
- Degrees list filtered by category
- Universities list filtered by
alpha_2_code - Professional memberships filtered by
alpha_2_codeandprofession_type
Step 7 (Practice Info):
- Languages list filtered by
alpha_2_code - Special interests filtered by
alpha_2_code
Step 9 (Review & Consent):
- Terms and agreements visibility
- HIPAA compliance (US-specific)
Achievement System
Achievements provide engagement milestones throughout the onboarding process.
Achievement Types
- profile-discoverable: Unlocked after completing Basic Info (Step 3)
- referral-eligible: Unlocked after completing Professional Details (Step 4)
- alumni-unlocked: Unlocked after completing Qualifications (Step 5)
Backend Storage
- Achievements are stored in
user_flags.onboarding_data.achievementsas an array - Each achievement is stored as a string identifier
Onboarding Completion
Completion Process
- User completes all 10 steps
- Final POST to
/user_onboardingwith celebration step data - System marks onboarding as complete in user_flags
- User authentication context updated with
cmsOnboardingComplete: true - User redirected to
/connectdashboard
Post-Completion State
- No Profiles Created: User accounts and profiles are NOT automatically created
- Data Preservation: All onboarding data stored in
user_flags.onboarding_data - Connect Platform Access: Users can access Connect with limited functionality
- Pending Status: Users are in "pending approval" state until Kulcare team creates profiles
Manual Profile Creation
When the Kulcare team is ready to create the actual profile:
- Retrieve onboarding data from
user_flags.onboarding_data - Create appropriate user_account record
- Create doctor/clinic profile with onboarding data
- Create associated records (educations, experiences, memberships, etc.)
- Mark profile as active
- Notify user of profile activation
Database Schema
user_flags Table
create_table :user_flags do |t|
t.bigint :user_id, null: false
t.jsonb :onboarding_data, default: {}
t.boolean :onboarding_complete, default: false
t.timestamps
end
add_index :user_flags, :user_id, unique: truedoctor_medical_councils Table
create_table :doctor_medical_councils do |t|
t.integer :doctor_id, null: false
t.string :name
t.integer :default_medical_council_id
t.timestamps
end
add_index :doctor_medical_councils, :doctor_id
add_index :doctor_medical_councils, :default_medical_council_id
add_foreign_key :doctor_medical_councils, :doctors
add_foreign_key :doctor_medical_councils, :default_medical_councilsRecent Implementation Changes
Elasticsearch Service Reorganization
- Location Change: Moved from
frontend/apps/shell/src/data/tofrontend/apps/shell/src/services/onboarding/ - Purpose: Better code organization and service-oriented architecture
Professional Memberships Filtering
- Enhancement: Added
profession_typeparameter to Elasticsearch queries - Backend Change: Updated
getProfessionalMembershipsmethod to acceptprofessionTypeparameter - Filtering Logic:
- Medical users:
profession_type = 'medical' - Allied health users:
profession_type = 'allied_health'
- Medical users:
Medical Councils Array Type
- Database Change: Converted
council_typefrom string to PostgreSQL array - Data Consolidation: Merged duplicate councils (e.g., Dubai Health Authority)
- Index Update: Added GIN index for array field queries
- Query Update: Modified scopes to use PostgreSQL array operators
Universities Alpha Code
- Seed Update: Added
alpha_2_codeto seed script - Issue Fixed: Universities were returning empty due to missing country codes
- Reindexing Required: Database reseed and Elasticsearch reindex needed
Doctor Medical Councils Table
- New Table: Created
doctor_medical_councilsjoin table - Purpose: Support multiple medical council registrations per doctor
- Associations: Belongs to doctor and optionally to default_medical_council
References
Related Documentation
- Frontend Onboarding Flow:
frontend/docs/features/onboarding/new_onboarding_flow.md - Authentication System:
backend/docs/04-features/authentication-system.md
Model Files
backend/parts/core/models/user_flag.rbbackend/parts/core/models/default_medical_council.rbbackend/parts/core/models/basic_language.rbbackend/parts/doctor/models/doctor.rbbackend/parts/doctor/models/doctor_medical_council.rb
Seed Files
backend/db/seeds/seed_basic_degrees.rbbackend/db/seeds/seed_universities.rbbackend/db/seeds/seed_default_medical_councils.rbbackend/db/seeds/seed_default_special_interests.rbbackend/db/seeds/seed_default_professional_memberships.rbbackend/db/seeds/seed_default_allied_health_professions.rbbackend/db/seeds/seed_default_allied_health_specializations.rb