Onboarding Analytics Admin
Base URL: https://api.fursahub.com/api/v1
Short Description: Onboarding flow endpoints for Fursa Hub. Guides new users through phone verification, preference selection, and profile completion. All content is translated based on user's preferredLanguage setting.
Hints:
- Onboarding steps must be completed in order - skipping ahead returns 412 PRECONDITION_FAILED
- Phone verification uses OTP sent via SMS (supports TZ, KE, UG, RW, BI country codes)
- Preference pages are dynamic - admin can add/remove/reorder without code changes
- User's preferredLanguage determines translation of all onboarding content
- Email verification is optional/skippable by default (Firebase handles actual verification)
Complete Onboarding Flow
┌─────────────────────────────────────────────────────────────────────────┐
│ COMPLETE ONBOARDING FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ After POST /auth/firebase/authenticate: │
│ └── Check response.data.onboarding.currentStep │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ STEP 1: PENDING_EMAIL_VERIFICATION (Optional/Skippable) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • Firebase handles actual email verification │ │
│ │ • Check: GET /onboarding/email-verification/status │ │
│ │ • Skip: POST /onboarding/email-verification/skip │ │
│ │ • Or wait for user to verify email in Firebase │ │
│ │ • Auto-transitions when Firebase reports email verified │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ STEP 2: PENDING_PHONE_VERIFICATION (Required) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • POST /onboarding/auth-phone/request-otp │ │
│ │ └── User enters phone number (+255...) │ │
│ │ └── Backend sends OTP via SMS │ │
│ │ └── Returns token for verification │ │
│ │ • POST /onboarding/auth-phone/verify │ │
│ │ └── User enters 6-digit OTP │ │
│ │ └── On success: phone saved, transitions to next step │ │
│ │ • POST /onboarding/auth-phone/resend-otp (if needed) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ STEP 3: PENDING_PREFERENCES (Required) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • GET /onboarding/pages?current=true │ │
│ │ └── Get current preference page │ │
│ │ • Loop through pages: │ │
│ │ └── Display options (translated to user's language) │ │
│ │ └── User selects options │ │
│ │ └── POST /onboarding/pages/{pageId}/response │ │
│ │ └── Or POST /onboarding/pages/{pageId}/skip (if skippable) │ │
│ │ └── Move to next page until all complete │ │
│ │ • Auto-transitions when all pages completed │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ STEP 4: PENDING_PROFILE_COMPLETION (Required) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • GET /profile │ │
│ │ └── Get current profile (some data from Firebase) │ │
│ │ • PUT /profile │ │
│ │ └── fullName (required) │ │
│ │ └── username (required, unique) │ │
│ │ └── bio (required) │ │
│ │ └── gender, link (optional) │ │
│ │ • On save: auto-completes onboarding │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ STEP 5: COMPLETED ✓ │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ • Navigate to home screen │ │
│ │ • User can now access all app features │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Phone Verification Endpoints
1. Request OTP
Purpose: Send OTP code to user's phone number for verification.
Endpoint: POST {base_url}/onboarding/auth-phone/request-otp
Access Level: 🔒 Protected
Authentication: Bearer Token
Prerequisite: User must be at PENDING_PHONE_VERIFICATION step
Request JSON Sample:
{
"phoneNumber": "+255712345678"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| phoneNumber | string | Yes | Phone number with country code | E.164 format: +255XXXXXXXXX |
Supported Country Codes:
+255- Tanzania+254- Kenya+256- Uganda+250- Rwanda+257- Burundi
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "OTP sent successfully",
"action_time": "2025-01-05T10:30:45",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"phoneNumber": "+255****678",
"expiresInSeconds": 600,
"resendAvailableIn": 120
}
}
Success Response Fields:
| Field | Description |
|---|---|
| token | Temporary token for verify/resend endpoints |
| phoneNumber | Masked phone number for display |
| expiresInSeconds | OTP validity period (10 minutes) |
| resendAvailableIn | Seconds until resend allowed (2 minutes) |
Error Responses:
Wrong Onboarding Step (412):
{
"success": false,
"httpStatus": "PRECONDITION_FAILED",
"message": "Onboarding step required",
"action_time": "2025-01-05T10:30:45",
"data": {
"message": "Complete email verification first",
"currentStep": "PENDING_EMAIL_VERIFICATION",
"requiredStep": "PENDING_EMAIL_VERIFICATION"
}
}
Rate Limit Exceeded (429):
{
"success": false,
"httpStatus": "TOO_MANY_REQUESTS",
"message": "Too many OTP requests. Try again in 10 minutes.",
"action_time": "2025-01-05T10:30:45",
"data": "Too many OTP requests. Try again in 10 minutes."
}
Phone Already Registered (409):
{
"success": false,
"httpStatus": "CONFLICT",
"message": "Phone number already registered",
"action_time": "2025-01-05T10:30:45",
"data": "Phone number already registered"
}
2. Verify OTP
Purpose: Verify OTP code and complete phone verification.
Endpoint: POST {base_url}/onboarding/auth-phone/verify
Access Level: 🔒 Protected
Authentication: Bearer Token
Request JSON Sample:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"otp": "123456"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| token | string | Yes | Token from request-otp response | Must be valid, non-expired |
| otp | string | Yes | 6-digit OTP code | Exactly 6 digits |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Phone verified successfully",
"action_time": "2025-01-05T10:35:00",
"data": {
"verified": true,
"phoneNumber": "+255****678",
"onboardingStatus": "PENDING_PREFERENCES",
"nextStep": "/api/v1/onboarding/pages"
}
}
Error Responses:
Invalid OTP (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "Invalid OTP. 2 attempt(s) remaining.",
"action_time": "2025-01-05T10:35:00",
"data": "Invalid OTP. 2 attempt(s) remaining."
}
Expired OTP (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "OTP has expired. Please request a new one.",
"action_time": "2025-01-05T10:35:00",
"data": "OTP has expired. Please request a new one."
}
Max Attempts Reached (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "Maximum attempts reached. Please request a new OTP.",
"action_time": "2025-01-05T10:35:00",
"data": "Maximum attempts reached. Please request a new OTP."
}
3. Resend OTP
Purpose: Request a new OTP code using the existing token.
Endpoint: POST {base_url}/onboarding/auth-phone/resend-otp
Access Level: 🔒 Protected
Authentication: Bearer Token
Request JSON Sample:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Success Response: Same as Request OTP endpoint
Email Verification Endpoints
4. Get Email Verification Status
Purpose: Check email verification status and options.
Endpoint: GET {base_url}/onboarding/email-verification/status
Access Level: 🔒 Protected
Authentication: Bearer Token
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Email verification status",
"action_time": "2025-01-05T10:00:00",
"data": {
"verified": false,
"email": "jo***@example.com",
"required": false,
"canSkip": true,
"currentStep": "PENDING_EMAIL_VERIFICATION"
}
}
5. Skip Email Verification
Purpose: Skip email verification step (if allowed by config).
Endpoint: POST {base_url}/onboarding/email-verification/skip
Access Level: 🔒 Protected
Authentication: Bearer Token
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Email verification skipped",
"action_time": "2025-01-05T10:05:00",
"data": {
"verified": false,
"skipped": true,
"nextStep": "PENDING_PHONE_VERIFICATION"
}
}
Preference Pages Endpoints
6. Get Onboarding Progress
Purpose: Get overall onboarding progress with all steps.
Endpoint: GET {base_url}/onboarding/progress
Access Level: 🔒 Protected
Authentication: Bearer Token
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Progress retrieved",
"action_time": "2025-01-05T10:40:00",
"data": {
"percentage": 45.0,
"currentStage": "PENDING_PREFERENCES",
"currentStageLabel": "Complete your preferences",
"steps": [
{
"key": "registration",
"label": "Registration",
"completed": true,
"weight": 15.0,
"skippable": false
},
{
"key": "phone_verification",
"label": "Phone Verification",
"completed": true,
"weight": 15.0,
"skippable": false
},
{
"key": "page_interests",
"label": "Your Interests",
"completed": true,
"weight": 13.33,
"skippable": false
},
{
"key": "page_goals",
"label": "Your Goals",
"completed": false,
"weight": 13.33,
"skippable": true
},
{
"key": "page_experience",
"label": "Your Experience",
"completed": false,
"weight": 13.33,
"skippable": false
},
{
"key": "profile_completion",
"label": "Complete Profile",
"completed": false,
"weight": 15.0,
"skippable": false
}
],
"nextStep": {
"key": "page_goals",
"label": "Your Goals",
"endpoint": "/api/v1/onboarding/pages?page=2",
"skippable": true
}
}
}
7. Get All Pages
Purpose: Get all preference pages with completion status.
Endpoint: GET {base_url}/onboarding/pages
Access Level: 🔒 Protected
Authentication: Bearer Token
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| all | boolean | No | Return all pages (default behavior) |
| page | integer | No | Get specific page by order (1, 2, 3...) |
| category | string | No | Get page by category key |
| current | boolean | No | Get first incomplete page |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "All pages retrieved",
"action_time": "2025-01-05T10:45:00",
"data": {
"totalPages": 3,
"completedPages": 1,
"isOnboardingComplete": false,
"pages": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"pageOrder": 1,
"categoryKey": "interests",
"title": "Maslahi Yako",
"description": "Chagua mambo yanayokuvutia",
"bannerImages": ["https://cdn.fursahub.com/onboarding/interests.jpg"],
"isSkippable": false,
"minSelections": 1,
"maxSelections": 5,
"options": [
{ "key": "jobs", "label": "Kazi", "icon": "briefcase" },
{ "key": "funding", "label": "Ufadhili", "icon": "dollar" },
{ "key": "events", "label": "Matukio", "icon": "calendar" },
{ "key": "skills", "label": "Ujuzi", "icon": "book" },
{ "key": "networking", "label": "Mitandao", "icon": "users" }
],
"isCompleted": true
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"pageOrder": 2,
"categoryKey": "goals",
"title": "Malengo Yako",
"description": "Unataka kufikia nini?",
"isSkippable": true,
"minSelections": 1,
"maxSelections": 3,
"options": [
{ "key": "find_job", "label": "Kupata kazi", "icon": "search" },
{ "key": "start_business", "label": "Kuanzisha biashara", "icon": "store" },
{ "key": "learn_skills", "label": "Kujifunza ujuzi", "icon": "graduation" },
{ "key": "get_funding", "label": "Kupata ufadhili", "icon": "money" }
],
"isCompleted": false
}
]
}
}
8. Get Current Page
Purpose: Get the first incomplete preference page.
Endpoint: GET {base_url}/onboarding/pages?current=true
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Current page retrieved",
"action_time": "2025-01-05T10:50:00",
"data": {
"page": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"pageOrder": 2,
"categoryKey": "goals",
"title": "Malengo Yako",
"description": "Unataka kufikia nini?",
"isSkippable": true,
"minSelections": 1,
"maxSelections": 3,
"options": [...]
},
"progress": {
"current": 2,
"total": 3,
"nextPage": 3,
"isLast": false,
"isCompleted": false
}
}
}
9. Submit Page Response
Purpose: Save user's selections for a preference page.
Endpoint: POST {base_url}/onboarding/pages/{pageId}/response
Access Level: 🔒 Protected
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| pageId | UUID | Yes | Page identifier |
Request JSON Sample:
{
"selectedOptions": ["find_job", "learn_skills"]
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| selectedOptions | array | Yes | Array of selected option keys | Must match page's min/max selections |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Response saved",
"action_time": "2025-01-05T10:55:00",
"data": {
"saved": true,
"progress": {
"current": 2,
"total": 3,
"nextPage": 3,
"isLast": false,
"isCompleted": false
}
}
}
Error Responses:
Too Few Selections (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Minimum 1 selection(s) required",
"action_time": "2025-01-05T10:55:00",
"data": "Minimum 1 selection(s) required"
}
Invalid Option (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Invalid option: unknown_key",
"action_time": "2025-01-05T10:55:00",
"data": "Invalid option: unknown_key"
}
10. Skip Page
Purpose: Skip a preference page (only if page is skippable).
Endpoint: POST {base_url}/onboarding/pages/{pageId}/skip
Access Level: 🔒 Protected
Authentication: Bearer Token
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Page skipped",
"action_time": "2025-01-05T11:00:00",
"data": {
"saved": true,
"progress": {
"current": 2,
"total": 3,
"nextPage": 3,
"isLast": false,
"isCompleted": false
}
}
}
Error Response (Page Not Skippable):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "This page cannot be skipped",
"action_time": "2025-01-05T11:00:00",
"data": "This page cannot be skipped"
}
Language Preference Endpoint
11. Set Language Preference
Purpose: Update user's language preference (can be called anytime during onboarding).
Endpoint: POST {base_url}/onboarding/language-preference
Access Level: 🔒 Protected
Authentication: Bearer Token
Request JSON Sample:
{
"code": "sw"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Language preference updated",
"action_time": "2025-01-05T11:05:00",
"data": {
"code": "sw",
"name": "Swahili",
"nativeName": "Kiswahili"
}
}
Admin: Manage Onboarding Pages
These endpoints allow admins to create, edit, reorder, and manage onboarding preference pages without code changes.
12. Get All Pages (Admin)
Purpose: Get all onboarding pages with full details for admin management.
Endpoint: GET {base_url}/onboarding/pages/manage
Access Level: 🔒 Protected (Admin/Moderator)
Authentication: Bearer Token (ROLE_ADMIN, ROLE_SUPER_ADMIN, ROLE_MODERATOR)
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Pages retrieved",
"action_time": "2025-01-05T12:00:00",
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"categoryKey": "interests",
"pageOrder": 1,
"isActive": true,
"isSkippable": false,
"minSelections": 1,
"maxSelections": 5,
"bannerImages": ["https://cdn.fursahub.com/onboarding/interests.jpg"],
"translations": {
"en": {
"title": "Your Interests",
"description": "Select what interests you"
},
"sw": {
"title": "Maslahi Yako",
"description": "Chagua mambo yanayokuvutia"
}
},
"options": [
{
"key": "jobs",
"icon": "briefcase",
"translations": {
"en": "Jobs",
"sw": "Kazi"
}
},
{
"key": "funding",
"icon": "dollar",
"translations": {
"en": "Funding",
"sw": "Ufadhili"
}
}
],
"createdAt": "2025-01-01T00:00:00",
"updatedAt": "2025-01-05T10:00:00"
}
]
}
13. Get Page by ID (Admin)
Purpose: Get single page details for editing.
Endpoint: GET {base_url}/onboarding/pages/manage/{pageId}
Access Level: 🔒 Protected (Admin/Moderator)
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| pageId | UUID | Yes | Page identifier |
Success Response: Same structure as single item in Get All Pages
14. Create Page
Purpose: Create a new onboarding preference page.
Endpoint: POST {base_url}/onboarding/pages/manage
Access Level: 🔒 Protected (Admin/Moderator)
Authentication: Bearer Token
Request JSON Sample:
{
"categoryKey": "location",
"pageOrder": 4,
"isActive": true,
"isSkippable": true,
"minSelections": 1,
"maxSelections": 1,
"bannerImages": ["https://cdn.fursahub.com/onboarding/location.jpg"],
"translations": {
"en": {
"title": "Your Location",
"description": "Where are you based?"
},
"sw": {
"title": "Mahali Ulipo",
"description": "Unaishi wapi?"
}
},
"options": [
{
"key": "dar_es_salaam",
"icon": "map-pin",
"translations": {
"en": "Dar es Salaam",
"sw": "Dar es Salaam"
}
},
{
"key": "arusha",
"icon": "map-pin",
"translations": {
"en": "Arusha",
"sw": "Arusha"
}
},
{
"key": "mwanza",
"icon": "map-pin",
"translations": {
"en": "Mwanza",
"sw": "Mwanza"
}
},
{
"key": "other",
"icon": "map",
"translations": {
"en": "Other",
"sw": "Nyingine"
}
}
]
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| categoryKey | string | Yes | Unique category identifier | Lowercase, no spaces |
| pageOrder | integer | Yes | Display order (1, 2, 3...) | Min: 1 |
| isActive | boolean | No | Page is active | Default: true |
| isSkippable | boolean | No | User can skip this page | Default: false |
| minSelections | integer | No | Minimum options to select | Default: 1 |
| maxSelections | integer | No | Maximum options to select | Default: 10 |
| bannerImages | array | No | Banner image URLs | Array of URLs |
| translations | object | Yes | Title/description per language | Must include "en" |
| translations.{lang}.title | string | Yes | Page title | Max 100 chars |
| translations.{lang}.description | string | No | Page description | Max 500 chars |
| options | array | Yes | Selectable options | Min 2 options |
| options[].key | string | Yes | Unique option key | Lowercase, no spaces |
| options[].icon | string | No | Icon name | From icon library |
| options[].translations | object | Yes | Label per language | Must include "en" |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "CREATED",
"message": "Page created",
"action_time": "2025-01-05T12:05:00",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440004",
"categoryKey": "location",
"pageOrder": 4,
"isActive": true,
...
}
}
Error Responses:
Duplicate Category Key (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Category key already exists: interests",
"action_time": "2025-01-05T12:05:00",
"data": "Category key already exists: interests"
}
15. Update Page
Purpose: Update an existing onboarding page.
Endpoint: PUT {base_url}/onboarding/pages/manage/{pageId}
Access Level: 🔒 Protected (Admin/Moderator)
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| pageId | UUID | Yes | Page identifier |
Request JSON Sample: Same as Create Page
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Page updated",
"action_time": "2025-01-05T12:10:00",
"data": { ... }
}
16. Delete Page
Purpose: Delete an onboarding page (soft delete recommended - use deactivate instead).
Endpoint: DELETE {base_url}/onboarding/pages/manage/{pageId}
Access Level: 🔒 Protected (Admin/Moderator)
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| pageId | UUID | Yes | Page identifier |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Page deleted",
"action_time": "2025-01-05T12:15:00",
"data": null
}
17. Activate Page
Purpose: Activate a deactivated page.
Endpoint: PATCH {base_url}/onboarding/pages/manage/{pageId}/activate
Access Level: 🔒 Protected (Admin/Moderator)
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Page activated",
"action_time": "2025-01-05T12:20:00",
"data": null
}
18. Deactivate Page
Purpose: Deactivate a page (hides from users without deleting).
Endpoint: PATCH {base_url}/onboarding/pages/manage/{pageId}/deactivate
Access Level: 🔒 Protected (Admin/Moderator)
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Page deactivated",
"action_time": "2025-01-05T12:25:00",
"data": null
}
19. Reorder Pages
Purpose: Change the display order of all pages at once.
Endpoint: PATCH {base_url}/onboarding/pages/manage/reorder
Access Level: 🔒 Protected (Admin/Moderator)
Request JSON Sample:
{
"pageIds": [
"550e8400-e29b-41d4-a716-446655440002",
"550e8400-e29b-41d4-a716-446655440001",
"550e8400-e29b-41d4-a716-446655440003",
"550e8400-e29b-41d4-a716-446655440004"
]
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| pageIds | array | Yes | Page IDs in new order | Must include all active page IDs |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Pages reordered",
"action_time": "2025-01-05T12:30:00",
"data": null
}
Admin Panel Implementation Guide
Page List View
GET /onboarding/pages/manage
├── Display table with: Order, Category, Title (en), Status, Actions
├── Actions: Edit, Activate/Deactivate, Delete
├── Drag-and-drop for reorder → PATCH /onboarding/pages/manage/reorder
└── "Add New Page" button
Create/Edit Page Form
Fields:
├── Category Key (text, required, unique)
├── Page Order (number)
├── Is Skippable (checkbox)
├── Min/Max Selections (numbers)
├── Banner Images (file upload → Files API)
├── Translations (tabs for each language)
│ ├── Title (text)
│ └── Description (textarea)
└── Options (repeater)
├── Key (text, unique within page)
├── Icon (icon picker)
└── Translations (text per language)
On Save:
├── New: POST /onboarding/pages/manage
└── Edit: PUT /onboarding/pages/manage/{pageId}
Quick Actions
Activate: PATCH /onboarding/pages/manage/{pageId}/activate
Deactivate: PATCH /onboarding/pages/manage/{pageId}/deactivate
Delete: DELETE /onboarding/pages/manage/{pageId} (with confirmation)
Frontend Implementation Guide
Phone Verification Screen
1. Show phone input with country code selector
2. On submit: POST /onboarding/auth-phone/request-otp
3. Save token from response
4. Navigate to OTP input screen
5. Show countdown for resendAvailableIn
6. On OTP submit: POST /onboarding/auth-phone/verify
7. On success: Navigate based on nextStep
Preference Pages Loop
1. GET /onboarding/pages?current=true
2. If page is null → All done, navigate to profile
3. Display page with:
├── Title, description (translated)
├── Banner image
├── Options as selectable chips/cards
└── Skip button (if isSkippable)
4. On submit: POST /onboarding/pages/{pageId}/response
5. Check progress.isCompleted
├── true → Navigate to profile completion
└── false → GET /onboarding/pages?current=true (loop)
Progress Indicator
GET /onboarding/progress
├── Use percentage for progress bar
├── Show steps as dots/icons
├── Highlight current step
└── Show completed steps with checkmarks
No comments to display
No comments to display