Skip to main content

Onboarding

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-01-02
Version: v1.0

Base URL: https://api.fursahub.com/api/v1

Short Description: The Onboarding API manages the complete user onboarding flow for Fursa Hub. It handles phone verification via OTP, language preference selection, dynamic preference pages with multilingual support, progress tracking, and administrative page management. The system uses a state machine approach to ensure users complete all required steps before accessing the main platform.

Hints:

  • Onboarding follows a strict sequence: Phone Verification → Language Preference → Preference Pages → Profile Completion
  • All preference pages are admin-configurable with multilingual support (EN, SW, FR, ZH)
  • Phone verification uses internal OTP system via Textify Africa SMS gateway
  • Progress endpoint provides weighted percentage completion across all steps
  • Skip is only allowed on pages marked as isSkippable: true
  • User's Accept-Language header or preferredLanguage determines response language

Onboarding Flow Overview

Complete Onboarding Journey

┌─────────────────────────────────────────────────────────────────────────┐
│                        ONBOARDING STATE MACHINE                         │
└─────────────────────────────────────────────────────────────────────────┘

  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
  │   PENDING    │     │   PENDING    │     │   PENDING    │     │              │
  │    PHONE     │────▶│ PREFERENCES  │────▶│   PROFILE    │────▶│  COMPLETED   │
  │ VERIFICATION │     │              │     │  COMPLETION  │     │              │
  └──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
         │                    │                    │                    │
         ▼                    ▼                    ▼                    ▼
   Phone OTP Flow      Preference Pages      Profile Update       Main App
   (request/verify)    (dynamic pages)       (auto-complete)      Access

Onboarding Status Values

Status Description User Action Required
PENDING_PHONE_VERIFICATION Initial state after Firebase auth Verify phone number via OTP
PENDING_PREFERENCES Phone verified Complete all preference pages
PENDING_PROFILE_COMPLETION Preferences done Fill profile (name, username, bio)
COMPLETED All steps done Full platform access

Step Weight Distribution

Step Weight Description
Registration 15% Auto-completed when user exists
Email Verification 15% From Firebase (may already be done)
Phone Verification 15% Required - via OTP
Preference Pages 40% Split equally among active pages
Profile Completion 15% Final step

Standard Response Format

All API responses follow a consistent structure using our Globe Response Builder pattern:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    // Actual response data goes here
  }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-01-02T10:30:45",
  "data": "Error description"
}

Phone Verification Endpoints

1. Request OTP

Purpose: Request a one-time password to be sent via SMS for phone number verification.

Endpoint: POST {base_url}/onboarding/auth-phone/request-otp

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "phoneNumber": "+255712345678"
}

Request Body Parameters:

Parameter Type Required Description Validation
phoneNumber string Yes Phone number in E.164 format Pattern: ^\+[0-9]{10,15}$

Supported Country Codes:

Code Country
+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-02T10:30:45",
  "data": {
    "phoneNumber": "+255****678",
    "expiresInSeconds": 600,
    "resendAvailableIn": 120
  }
}

Success Response Fields:

Field Description
phoneNumber Masked phone number for security
expiresInSeconds OTP validity period (default: 600 = 10 minutes)
resendAvailableIn Seconds until resend is allowed (default: 120 = 2 minutes)

Error Response JSON Samples:

Phone Already Registered (409):

{
  "success": false,
  "httpStatus": "CONFLICT",
  "message": "Phone number already registered",
  "action_time": "2025-01-02T10:30:45",
  "data": "Phone number already registered to another account"
}

Rate Limit Exceeded (429):

{
  "success": false,
  "httpStatus": "TOO_MANY_REQUESTS",
  "message": "Too many OTP requests",
  "action_time": "2025-01-02T10:30:45",
  "data": "Too many OTP requests. Try again in 10 minutes."
}

Resend Cooldown Active (429):

{
  "success": false,
  "httpStatus": "TOO_MANY_REQUESTS",
  "message": "Please wait before requesting another OTP",
  "action_time": "2025-01-02T10:30:45",
  "data": "Please wait 85 seconds before requesting another OTP"
}

Unsupported Country (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Unsupported country code",
  "action_time": "2025-01-02T10:30:45",
  "data": "Supported country codes: +255, +254, +256, +250, +257"
}

2. Verify OTP

Purpose: Verify the OTP code sent to the user's phone number.

Endpoint: POST {base_url}/onboarding/auth-phone/verify

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "phoneNumber": "+255712345678",
  "otp": "123456"
}

Request Body Parameters:

Parameter Type Required Description Validation
phoneNumber string Yes Phone number that received OTP E.164 format
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-02T10:30:45",
  "data": {
    "verified": true,
    "phoneNumber": "+255712345678",
    "onboardingStatus": "PENDING_PREFERENCES",
    "nextStep": "/api/v1/onboarding/preferences"
  }
}

Success Response Fields:

Field Description
verified Always true on success
phoneNumber The verified phone number
onboardingStatus Updated status (moves to PENDING_PREFERENCES)
nextStep API endpoint for next onboarding step

Error Response JSON Samples:

Invalid OTP (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Invalid OTP",
  "action_time": "2025-01-02T10:30:45",
  "data": "Invalid OTP. 2 attempt(s) remaining."
}

OTP Expired (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "OTP has expired",
  "action_time": "2025-01-02T10:30:45",
  "data": "OTP has expired. Please request a new one."
}

Max Attempts Reached (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Maximum attempts reached",
  "action_time": "2025-01-02T10:30:45",
  "data": "Maximum attempts reached. Please request a new OTP."
}

No Active OTP (403):

{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "No active OTP found",
  "action_time": "2025-01-02T10:30:45",
  "data": "No active OTP found. Please request a new one."
}

3. Resend OTP

Purpose: Resend OTP to the same phone number (subject to cooldown).

Endpoint: POST {base_url}/onboarding/auth-phone/resend-otp

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "phoneNumber": "+255712345678"
}

Request Body Parameters:

Parameter Type Required Description Validation
phoneNumber string Yes Phone number to resend OTP to E.164 format

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "OTP resent successfully",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "phoneNumber": "+255****678",
    "expiresInSeconds": 600,
    "resendAvailableIn": 120
  }
}

Same response format as Request OTP


Language Preference Endpoint

4. Set Language Preference

Purpose: Set user's preferred language for the platform.

Endpoint: POST {base_url}/onboarding/language-preference

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Content-Type string Yes Must be application/json

Request JSON Sample:

{
  "code": "sw"
}

Request Body Parameters:

Parameter Type Required Description Validation
code string Yes Language code 2-5 characters, must be active language

Supported Languages:

Code Name Native Name
en English English
sw Swahili Kiswahili
fr French Français
zh Chinese 中文

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Language preference updated",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "code": "sw",
    "name": "Swahili",
    "nativeName": "Kiswahili"
  }
}

Success Response Fields:

Field Description
code Language code that was set
name English name of the language
nativeName Native name of the language

Onboarding Progress Endpoint

5. Get Onboarding Progress

Purpose: Get detailed progress information across all onboarding steps with weighted percentages.

Endpoint: GET {base_url}/onboarding/progress

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Accept-Language string No Preferred language (default: en)

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Progress retrieved",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "percentage": 55.0,
    "currentStage": "PENDING_PREFERENCES",
    "currentStageLabel": "Complete your preferences",
    "steps": [
      {
        "key": "registration",
        "label": "Registration",
        "completed": true,
        "weight": 15.0
      },
      {
        "key": "email_verification",
        "label": "Email Verification",
        "completed": true,
        "weight": 15.0
      },
      {
        "key": "phone_verification",
        "label": "Phone Verification",
        "completed": true,
        "weight": 15.0
      },
      {
        "key": "page_interests",
        "label": "What are your interests?",
        "completed": true,
        "weight": 13.33
      },
      {
        "key": "page_industries",
        "label": "Select your industries",
        "completed": false,
        "weight": 13.33
      },
      {
        "key": "page_goals",
        "label": "What are your goals?",
        "completed": false,
        "weight": 13.34
      },
      {
        "key": "profile_completion",
        "label": "Complete Profile",
        "completed": false,
        "weight": 15.0
      }
    ],
    "nextStep": {
      "key": "page_industries",
      "label": "Select your industries",
      "endpoint": "/api/v1/onboarding/pages?page=2"
    }
  }
}

Success Response Fields:

Field Description
percentage Overall completion percentage (0-100)
currentStage Current onboarding status enum value
currentStageLabel Human-readable description of current stage
steps Array of all onboarding steps with completion status
steps[].key Unique identifier for the step
steps[].label Display label (translated)
steps[].completed Whether step is completed
steps[].weight Percentage weight of this step
nextStep Next incomplete step details
nextStep.key Step identifier
nextStep.label Step display label
nextStep.endpoint API endpoint to complete this step

Onboarding Pages Endpoints

6. Get All Pages

Purpose: Retrieve all active onboarding preference pages with completion status.

Endpoint: GET {base_url}/onboarding/pages

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Accept-Language string No Preferred language (default: en)

Query Parameters:

Parameter Type Required Description
all boolean No If true, returns all pages
page integer No Get specific page by order number
category string No Get page by category key
current boolean No If true, returns current incomplete page

Success Response JSON Sample (all=true):

{
  "success": true,
  "httpStatus": "OK",
  "message": "All pages retrieved",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "totalPages": 3,
    "completedPages": 1,
    "isOnboardingComplete": false,
    "pages": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "pageOrder": 1,
        "categoryKey": "interests",
        "title": "What are your interests?",
        "description": "Select the areas you're most interested in exploring on Fursa Hub",
        "bannerImages": [
          "https://cdn.fursahub.com/onboarding/interests-banner.jpg"
        ],
        "isSkippable": false,
        "minSelections": 1,
        "maxSelections": 5,
        "options": [
          {
            "key": "technology",
            "label": "Technology",
            "icon": "💻"
          },
          {
            "key": "agriculture",
            "label": "Agriculture",
            "icon": "🌾"
          },
          {
            "key": "finance",
            "label": "Finance & Banking",
            "icon": "💰"
          },
          {
            "key": "health",
            "label": "Healthcare",
            "icon": "🏥"
          },
          {
            "key": "education",
            "label": "Education",
            "icon": "📚"
          }
        ],
        "isCompleted": true
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440002",
        "pageOrder": 2,
        "categoryKey": "industries",
        "title": "Select your industries",
        "description": "Choose the industries you work in or want to explore",
        "bannerImages": [],
        "isSkippable": true,
        "minSelections": 0,
        "maxSelections": 10,
        "options": [
          {
            "key": "tech_startup",
            "label": "Tech Startups",
            "icon": "🚀"
          },
          {
            "key": "manufacturing",
            "label": "Manufacturing",
            "icon": "🏭"
          },
          {
            "key": "retail",
            "label": "Retail & E-commerce",
            "icon": "🛒"
          }
        ],
        "isCompleted": false
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440003",
        "pageOrder": 3,
        "categoryKey": "goals",
        "title": "What are your goals?",
        "description": "Tell us what you want to achieve on Fursa Hub",
        "bannerImages": [],
        "isSkippable": false,
        "minSelections": 1,
        "maxSelections": 3,
        "options": [
          {
            "key": "find_job",
            "label": "Find a Job",
            "icon": "💼"
          },
          {
            "key": "find_funding",
            "label": "Find Funding",
            "icon": "💵"
          },
          {
            "key": "network",
            "label": "Networking",
            "icon": "🤝"
          },
          {
            "key": "learn_skills",
            "label": "Learn New Skills",
            "icon": "📖"
          }
        ],
        "isCompleted": false
      }
    ]
  }
}

Success Response Fields:

Field Description
totalPages Total number of active onboarding pages
completedPages Number of pages user has completed
isOnboardingComplete Whether all pages are completed
pages Array of page objects
pages[].id Unique page identifier (UUID)
pages[].pageOrder Display order (1, 2, 3...)
pages[].categoryKey Unique category identifier
pages[].title Page title (translated)
pages[].description Page description (translated)
pages[].bannerImages Array of banner image URLs
pages[].isSkippable Whether page can be skipped
pages[].minSelections Minimum options user must select
pages[].maxSelections Maximum options user can select
pages[].options Array of selectable options
pages[].options[].key Option identifier
pages[].options[].label Option label (translated)
pages[].options[].icon Option icon (emoji or URL)
pages[].isCompleted Whether user has completed this page

7. Get Single Page

Purpose: Retrieve a specific onboarding page with progress context.

Endpoint: GET {base_url}/onboarding/pages?page={pageOrder}

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Query Parameters:

Parameter Type Required Description
page integer Yes Page order number (1, 2, 3...)

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page retrieved",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "page": {
      "id": "550e8400-e29b-41d4-a716-446655440002",
      "pageOrder": 2,
      "categoryKey": "industries",
      "title": "Select your industries",
      "description": "Choose the industries you work in or want to explore",
      "bannerImages": [],
      "isSkippable": true,
      "minSelections": 0,
      "maxSelections": 10,
      "options": [
        {
          "key": "tech_startup",
          "label": "Tech Startups",
          "icon": "🚀"
        },
        {
          "key": "manufacturing",
          "label": "Manufacturing",
          "icon": "🏭"
        }
      ],
      "isCompleted": false
    },
    "progress": {
      "current": 2,
      "total": 3,
      "nextPage": 3,
      "isLast": false,
      "isCompleted": false
    }
  }
}

Success Response Fields:

Field Description
page Page object (same as in all pages response)
progress.current Current page number
progress.total Total number of pages
progress.nextPage Next page number (null if last)
progress.isLast Whether this is the last page
progress.isCompleted Whether all pages are completed

8. Get Current Page

Purpose: Get the next incomplete onboarding page.

Endpoint: GET {base_url}/onboarding/pages?current=true

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Success Response JSON Sample (when pages remain):

{
  "success": true,
  "httpStatus": "OK",
  "message": "Current page retrieved",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "page": {
      "id": "550e8400-e29b-41d4-a716-446655440002",
      "pageOrder": 2,
      "categoryKey": "industries",
      "title": "Select your industries",
      "description": "Choose the industries you work in",
      "bannerImages": [],
      "isSkippable": true,
      "minSelections": 0,
      "maxSelections": 10,
      "options": [...],
      "isCompleted": false
    },
    "progress": {
      "current": 2,
      "total": 3,
      "nextPage": 3,
      "isLast": false,
      "isCompleted": false
    }
  }
}

Success Response JSON Sample (all pages completed):

{
  "success": true,
  "httpStatus": "OK",
  "message": "Current page retrieved",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "page": null,
    "progress": {
      "current": 3,
      "total": 3,
      "nextPage": null,
      "isLast": true,
      "isCompleted": true
    }
  }
}

9. Submit Page Response

Purpose: Submit user's selected options for an onboarding page.

Endpoint: POST {base_url}/onboarding/pages/{pageId}/response

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}
Content-Type string Yes Must be application/json

Path Parameters:

Parameter Type Required Description
pageId UUID Yes Page identifier

Request JSON Sample:

{
  "selectedOptions": ["technology", "finance", "health"]
}

Request Body Parameters:

Parameter Type Required Description Validation
selectedOptions string[] Yes Array of selected option keys Must meet min/max selection rules

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Response saved",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "saved": true,
    "progress": {
      "current": 1,
      "total": 3,
      "nextPage": 2,
      "isLast": false,
      "isCompleted": false
    }
  }
}

Success Response Fields:

Field Description
saved Always true on success
progress.current Current page number
progress.total Total pages
progress.nextPage Next page to complete (null if done)
progress.isLast Whether current was last page
progress.isCompleted Whether all pages are now completed

Error Response JSON Samples:

Too Few Selections (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Minimum selections not met",
  "action_time": "2025-01-02T10:30:45",
  "data": "Minimum 1 selection(s) required"
}

Too Many Selections (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Maximum selections exceeded",
  "action_time": "2025-01-02T10:30:45",
  "data": "Maximum 5 selection(s) allowed"
}

Invalid Option (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Invalid option",
  "action_time": "2025-01-02T10:30:45",
  "data": "Invalid option: unknown_key"
}

Page Not Found (404):

{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Page not found",
  "action_time": "2025-01-02T10:30:45",
  "data": "Page not found"
}

10. Skip Page

Purpose: Skip an optional onboarding page.

Endpoint: POST {base_url}/onboarding/pages/{pageId}/skip

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
pageId UUID Yes Page identifier

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page skipped",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "saved": true,
    "progress": {
      "current": 2,
      "total": 3,
      "nextPage": 3,
      "isLast": false,
      "isCompleted": false
    }
  }
}

Error Response JSON Sample:

Page Not Skippable (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Page cannot be skipped",
  "action_time": "2025-01-02T10:30:45",
  "data": "This page cannot be skipped"
}

Admin Endpoints

11. Get All Pages (Admin)

Purpose: Retrieve all onboarding pages including inactive ones for admin management.

Endpoint: GET {base_url}/onboarding/pages/manage

Access Level: 🔒 Protected (Requires ROLE_MODERATOR, ROLE_ADMIN, or ROLE_SUPER_ADMIN)

Authentication: Bearer Token

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Pages retrieved",
  "action_time": "2025-01-02T10:30:45",
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "pageOrder": 1,
      "categoryKey": "interests",
      "title": "What are your interests?",
      "description": "Select the areas you're most interested in",
      "bannerImages": [],
      "isSkippable": false,
      "minSelections": 1,
      "maxSelections": 5,
      "options": [...],
      "isCompleted": false
    }
  ]
}

12. Create Page (Admin)

Purpose: Create a new onboarding page with multilingual support.

Endpoint: POST {base_url}/onboarding/pages/manage

Access Level: 🔒 Protected (Requires ROLE_MODERATOR, ROLE_ADMIN, or ROLE_SUPER_ADMIN)

Authentication: Bearer Token

Request JSON Sample:

{
  "pageOrder": 4,
  "categoryKey": "experience_level",
  "isSkippable": true,
  "minSelections": 1,
  "maxSelections": 1,
  "translations": {
    "en": {
      "title": "What's your experience level?",
      "description": "Help us understand your professional background"
    },
    "sw": {
      "title": "Kiwango chako cha uzoefu ni kipi?",
      "description": "Tusaidie kuelewa historia yako ya kitaaluma"
    }
  },
  "bannerImages": [
    "https://cdn.fursahub.com/onboarding/experience-banner.jpg"
  ],
  "options": [
    {
      "key": "student",
      "icon": "🎓",
      "translations": {
        "en": "Student",
        "sw": "Mwanafunzi"
      }
    },
    {
      "key": "entry_level",
      "icon": "🌱",
      "translations": {
        "en": "Entry Level (0-2 years)",
        "sw": "Kiwango cha Mwanzo (miaka 0-2)"
      }
    },
    {
      "key": "mid_level",
      "icon": "📈",
      "translations": {
        "en": "Mid Level (3-5 years)",
        "sw": "Kiwango cha Kati (miaka 3-5)"
      }
    },
    {
      "key": "senior",
      "icon": "⭐",
      "translations": {
        "en": "Senior (6+ years)",
        "sw": "Mkuu (miaka 6+)"
      }
    }
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
pageOrder integer Yes Display order Min: 1
categoryKey string Yes Unique category identifier Not blank
isSkippable boolean No Whether page can be skipped Default: false
minSelections integer No Minimum required selections Min: 0
maxSelections integer No Maximum allowed selections Min: 1
translations object Yes Language translations map At least en required
translations.{lang}.title string Yes Page title in language Not blank
translations.{lang}.description string No Page description -
bannerImages string[] No Array of banner image URLs -
options array Yes Array of option objects At least 1 option
options[].key string Yes Unique option identifier Not blank
options[].icon string No Icon (emoji or URL) -
options[].translations object Yes Option label translations At least en required

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Page created",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440004",
    "pageOrder": 4,
    "categoryKey": "experience_level",
    "title": "What's your experience level?",
    "description": "Help us understand your professional background",
    "bannerImages": ["https://cdn.fursahub.com/onboarding/experience-banner.jpg"],
    "isSkippable": true,
    "minSelections": 1,
    "maxSelections": 1,
    "options": [
      {"key": "student", "label": "Student", "icon": "🎓"},
      {"key": "entry_level", "label": "Entry Level (0-2 years)", "icon": "🌱"},
      {"key": "mid_level", "label": "Mid Level (3-5 years)", "icon": "📈"},
      {"key": "senior", "label": "Senior (6+ years)", "icon": "⭐"}
    ],
    "isCompleted": false
  }
}

13. Update Page (Admin)

Purpose: Update an existing onboarding page.

Endpoint: PUT {base_url}/onboarding/pages/manage/{pageId}

Access Level: 🔒 Protected (Requires ROLE_MODERATOR, ROLE_ADMIN, or ROLE_SUPER_ADMIN)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
pageId UUID Yes Page identifier

Request body same as Create Page


14. Delete Page (Admin)

Purpose: Delete an onboarding page.

Endpoint: DELETE {base_url}/onboarding/pages/manage/{pageId}

Access Level: 🔒 Protected (Requires ROLE_MODERATOR, ROLE_ADMIN, or ROLE_SUPER_ADMIN)

Authentication: Bearer Token

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-02T10:30:45",
  "data": null
}

15. Activate/Deactivate Page (Admin)

Purpose: Toggle page active status.

Endpoint (Activate): PATCH {base_url}/onboarding/pages/manage/{pageId}/activate

Endpoint (Deactivate): PATCH {base_url}/onboarding/pages/manage/{pageId}/deactivate

Access Level: 🔒 Protected (Requires ROLE_MODERATOR, ROLE_ADMIN, or ROLE_SUPER_ADMIN)

Authentication: Bearer Token

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Page activated",
  "action_time": "2025-01-02T10:30:45",
  "data": null
}

16. Reorder Pages (Admin)

Purpose: Change the display order of onboarding pages.

Endpoint: PATCH {base_url}/onboarding/pages/manage/reorder

Access Level: 🔒 Protected (Requires ROLE_MODERATOR, ROLE_ADMIN, or ROLE_SUPER_ADMIN)

Authentication: Bearer Token

Request JSON Sample:

{
  "pageIds": [
    "550e8400-e29b-41d4-a716-446655440003",
    "550e8400-e29b-41d4-a716-446655440001",
    "550e8400-e29b-41d4-a716-446655440002"
  ]
}

Request Body Parameters:

Parameter Type Required Description Validation
pageIds UUID[] Yes Ordered array of page IDs Not empty

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Pages reordered",
  "action_time": "2025-01-02T10:30:45",
  "data": null
}

Standard Error Types

Application-Level Exceptions (400-499)

Status Code HTTP Status When It Occurs
400 BAD_REQUEST Invalid input, selection rules violated, page not skippable
401 UNAUTHORIZED Token empty, invalid, or expired
403 FORBIDDEN OTP invalid/expired, insufficient permissions
404 NOT_FOUND Page or resource not found
409 CONFLICT Phone already registered
422 UNPROCESSABLE_ENTITY Validation errors
429 TOO_MANY_REQUESTS Rate limit or cooldown active

Server-Level Exceptions (500+)

Status Code HTTP Status When It Occurs
500 INTERNAL_SERVER_ERROR Database errors, SMS gateway failures

Frontend Implementation Guide

Complete Onboarding Flow

const completeOnboarding = async () => {
  // Step 1: Check current progress
  const progress = await getProgress();
  
  switch (progress.currentStage) {
    case 'PENDING_PHONE_VERIFICATION':
      navigation.navigate('PhoneVerification');
      break;
    case 'PENDING_PREFERENCES':
      navigation.navigate('OnboardingPages');
      break;
    case 'PENDING_PROFILE_COMPLETION':
      navigation.navigate('ProfileCompletion');
      break;
    case 'COMPLETED':
      navigation.navigate('Home');
      break;
  }
};

Quick Reference

Onboarding Status Flow

PENDING_PHONE_VERIFICATION → PENDING_PREFERENCES → PENDING_PROFILE_COMPLETION → COMPLETED

OTP Configuration

Setting Value
OTP Length 6 digits
Expiry 10 minutes
Max Attempts 3
Resend Cooldown 2 minutes
Rate Limit 3 requests per 10 minutes

Supported Languages

Code Name Native Name
en English English
sw Swahili Kiswahili
fr French Français
zh Chinese 中文

Admin Roles for Page Management

Role Can Manage Pages
ROLE_USER ❌ No
ROLE_MODERATOR ✅ Yes
ROLE_ADMIN ✅ Yes
ROLE_SUPER_ADMIN ✅ Yes