Dynamic Onboarding Engine — API Contract
Goal
Backend stores all onboarding flows, steps, questions, and options. Frontend is a dumb renderer — it requests the next step, displays whatever the BE returns, collects answers, and posts them back. BE controls all branching logic.
This means:
Adding/changing a question = BE config change only (no FE release)
Adding a new flow (e.g., new user type) = BE only
FE only needs to know how to render question types (text, select, multi-select, etc.)
Flows Identified (from screenshots)
Common: Data Diri (all flows start here)
What's your name? (First Name required, Last Name optional)
When's your date of birth?
Where are you based? (city dropdown, open to remote Y/N, remote preference)
Gender
Flow Branching
How do you want to use ConnectX? ├── 1. I'm a Builder │ ├── Founder -> FLOW 1 │ │ ├── Looking for Co-Founder → Sub Flow A │ │ ├── Looking for Team Members → Sub Flow B │ │ └── Looking for Both → Sub Flow C │ ├── Co-Founder (join a startup) → Flow 2 │ └── Team Member (join a startup) → Flow 3 └── 2. I represent a Startup
Questions per flow
Common steps (all flows after DataDiri):
Prior startup experience? (Built/Founded/Worked in/No experience)
Industries interest (multi-select, up to 5, searchable)
Availability (Full-time/Part-time/Flexible)
Location based (Remote, Jakarta, Singapore, Bangalore, HCMC, Dubai, Anywhere)
Open to remote? (Yes/No) + preference (Hybrid/Onsite/Remote preferred/Remote only)
Willing to relocate? (Yes/No)
Which role best describes you primarily? (dropdown + years of experience)
LinkedIn URL
"You're all set!" completion
Flow A (Founder → Co-Founder): What are you looking for = Co-Founder; What kind of co-founder do you need? (Technical, Business, Product, Growth)
Flow B (Founder → Team Member): What are you looking for = Team Members; What roles do you need? (CTO/Technical Lead, Product Designer, Growth Marketer, Full-Stack Engineer, Operations Lead, etc.)
Flow C (Founder → Both): Combined: co-founder types + team roles
Flow D (Co-Founder joining): What type of co-founder are you?; Cash-equity expectation; Minimum salary (strict/flexible/no minimum); Currency + Amount
Flow E (Team Member joining): What's your skillset?; Cash-equity expectation; Minimum salary; Currency + amount
Flow F (Startup): Tell us about your startup (Name + Stage: Idea/MVP/Live); What are you looking for; What industry is your startup in; What kind of co-founder/roles; Cash-equity offered; Minimum salary offered
Core Concept: Form Engine
Each API response returns a Step containing 1+ Questions. FE renders the step, collects answers, POSTs back. BE computes next step based on branching rules.
Step object
{ "id": "step_personal_name", "flow_key": "common_data_diri", "section": "Let's build your general profile", "section_progress": "1/4", "overall_progress": { "current": 1, "total": 12 }, "title": "What's your name?", "subtitle": null, "questions": [ { "id": "q_first_name", "type": "text", "label": "First Name", "required": true, "validation": { "min_length": 1, "max_length": 50 } }, { "id": "q_last_name", "type": "text", "label": "Last Name", "required": false, "helper_text": "Last name is optional, and only shared after connection" } ], "cta": { "label": "Continue", "enabled_when": "valid" }, "can_go_back": true }
Question types (FE must support)
Type
Description
text
Single line text input
textarea
Multi-line text
number
Number input
date
Date picker
Email input
url
URL input
phone
Phone number
single_select_card
Cards with single selection (auto-advance optional)
single_select_radio
Radio buttons
multi_select_card
Cards with multi selection
multi_select_chip
Chips multi selection
dropdown
Dropdown list
searchable_dropdown
Dropdown with search + grouped options
grouped_list
List with section headers
currency_amount
Currency selector + amount input
segmented
Segmented control
Question object schema
{ "id": "string", "type": "single_select_card", "label": "string", "sub_label": "string (optional)", "helper_text": "string (optional)", "placeholder": "string (optional)", "required": true, "validation": { "min_length": 1, "max_length": 50, "min_selections": 1, "max_selections": 5 }, "options": [ { "id": "opt_technical", "label": "Technical Co-Founder", "sub_label": "Engineering & architecture", "value": "technical", "icon": "icon_name", "group": "Advanced Technologies & Software" } ], "depends_on": { "question_id": "q_open_to_remote", "operator": "equals", "value": "yes" }, "meta": { "auto_advance": true, "searchable": true, "layout": "grid_2" } }
depends_on enables conditional rendering within a step (e.g., "What is your preference in working remotely?" only shows if "Are you open to working remotely?" = Yes).
API Endpoints
OK 1. Start onboarding session
POST /api/v1/onboarding/sessions
Request: { "locale": "en" }
Response 201:
{ "session_id": "ses_abc123", "status": "in_progress", "current_step": { } }
OK 2. Get current step
GET /api/v1/onboarding/sessions/:session_id/current
Returns the current step (for resume or reload).
- Submit answer + get next step
POST /api/v1/onboarding/sessions/:session_id/answer
Request:
{ "step_id": "step_role_selection", "answers": { "q_use_connectx": "founder" } }
Response 200 (next step):
{ "next_step": { }, "progress": { "current": 3, "total": 12 }, "can_go_back": true }
Response 200 (completed):
{ "next_step": null, "completed": true, "profile_id": "prof_xyz789", "redirect_to": "/home" }
Response 422 (validation error):
{ "message": "The given data was invalid.", "errors": { "q_first_name": [ "'Nama Depan' terlalu pendek (Minimum 3 huruf)." ] } }
- Go back
POST /api/v1/onboarding/sessions/:session_id/back
- Get full session state
GET /api/v1/onboarding/sessions/:session_id
- (Admin) Manage flows
Standard CRUD for flows/steps/questions/options. Can be built later.
Database Schema
CREATE TABLE onboarding_flows ( id VARCHAR PRIMARY KEY, name VARCHAR NOT NULL, description TEXT, is_entry BOOLEAN DEFAULT false, created_at TIMESTAMP );
CREATE TABLE onboarding_steps ( id VARCHAR PRIMARY KEY, flow_id VARCHAR REFERENCES onboarding_flows(id), order_index INT NOT NULL, section VARCHAR, title VARCHAR NOT NULL, subtitle VARCHAR, cta_label VARCHAR DEFAULT 'Continue', auto_advance BOOLEAN DEFAULT false, can_go_back BOOLEAN DEFAULT true );
CREATE TABLE onboarding_questions ( id VARCHAR PRIMARY KEY, step_id VARCHAR REFERENCES onboarding_steps(id), order_index INT NOT NULL, type VARCHAR NOT NULL, label VARCHAR NOT NULL, sub_label VARCHAR, helper_text VARCHAR, placeholder VARCHAR, required BOOLEAN DEFAULT false, validation JSONB, depends_on JSONB, meta JSONB );
CREATE TABLE onboarding_options ( id VARCHAR PRIMARY KEY, question_id VARCHAR REFERENCES onboarding_questions(id), order_index INT NOT NULL, label VARCHAR NOT NULL, sub_label VARCHAR, value VARCHAR NOT NULL, icon VARCHAR, group_name VARCHAR );
CREATE TABLE onboarding_transitions ( id SERIAL PRIMARY KEY, from_step_id VARCHAR REFERENCES onboarding_steps(id), condition JSONB, to_step_id VARCHAR REFERENCES onboarding_steps(id), to_flow_id VARCHAR REFERENCES onboarding_flows(id), priority INT DEFAULT 0 );
CREATE TABLE onboarding_sessions ( id VARCHAR PRIMARY KEY, user_id VARCHAR REFERENCES users(id), current_step_id VARCHAR, status VARCHAR, started_at TIMESTAMP, completed_at TIMESTAMP );
CREATE TABLE onboarding_responses ( id SERIAL PRIMARY KEY, session_id VARCHAR REFERENCES onboarding_sessions(id), step_id VARCHAR, question_id VARCHAR, value JSONB, answered_at TIMESTAMP );
Branching Logic
-
Linear (default): Next step = current step's order_index + 1 within same flow.
-
Conditional transition: If current step has entries in onboarding_transitions, evaluate in priority order:
{ "from_step_id": "step_role_selection", "condition": { "question_id": "q_use_connectx", "operator": "equals", "value": "startup" }, "to_step_id": "step_startup_details", "priority": 1 }
Operators: equals, not_equals, in, not_in, contains, exists
- Flow jump: A transition can point to a different flow's first step.
Completion flow
When last step is submitted:
BE validates all responses
BE creates user profile from responses (mapping response values to profile fields)
BE marks session as completed
BE returns next_step: null, completed: true, profile_id, redirect_to
FE navigates to home
Response-to-profile mapping is stored BE-side.
Implementation Phases
Phase 1 (this sprint)
Core engine: sessions, steps, questions, options, responses
Seed 6 flows from screenshots
5 core endpoints (1-5)
FE renders all question types
Phase 2 (post-launch)
Admin UI to manage flows
Analytics: drop-off per step
A/B testing support
Open Questions for FE/BE meeting
Do we need FE to cache the flow definition, or fetch per step? (recommend fetch per step)
How does "auto-advance" work for single-select cards? (FE auto-POST on tap)
searchable_dropdown for industries — options paginated or all loaded?
Where does "data diri" flow start — after signup/verification or before?
Should users be allowed to skip optional steps?
Localization baked into options (EN/ID) or just English first?
Acceptance
BE: all 6 flows seeded with correct steps, questions, options
BE: all 5 session endpoints working
BE: branching logic handles all 6 flows correctly
FE: renders all question types
FE: handles depends_on (conditional question display)
FE: handles auto_advance on single-select cards
E2E: user can complete each of the 6 flows and land on home with valid profile