Skip to main content

Onboarding EndUser

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

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

Short Description: The Onboarding APIflow manages the complete user onboarding flowendpoints for Fursa Hub. ItGuides handlesnew users through phone verification via OTP, languageverification, preference selection, dynamic preference pages with multilingual support, progress tracking, and administrativeprofile pagecompletion. management.All Thecontent systemis usestranslated abased stateon machineuser's approachpreferredLanguage to ensure users complete all required steps before accessing the main platform.setting.

Hints:

  • Onboarding followssteps amust strictbe sequence:completed Phonein Verificationorder - Languageskipping Preferenceahead returns Preference412 Pages → Profile Completion
  • All preference pages are admin-configurable with multilingual support (EN, SW, FR, ZH)PRECONDITION_FAILED
  • Phone verification uses internal OTP systemsent via Textify Africa SMS gateway(supports TZ, KE, UG, RW, BI country codes)
  • Progress endpoint provides weighted percentage completion across all steps
  • Skip is only allowed onPreference pages markedare asdynamic isSkippable:- trueadmin can add/remove/reorder without code changes
  • User's Accept-Language header or preferredLanguage determines responsetranslation languageof all onboarding content
  • Email verification is optional/skippable by default (Firebase handles actual verification)

Onboarding Flow Overview

Complete Onboarding Journey

Flow
┌─────────────────────────────────────────────────────────────────────────┐
│                        COMPLETE ONBOARDING STATEFLOW                          MACHINE│
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  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                          │    │
│  └─────────────────────────────────────────────────────────────────┘    │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
  │   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

StatusDescriptionUser Action Required
PENDING_PHONE_VERIFICATIONInitial state after Firebase authVerify phone number via OTP
PENDING_PREFERENCESPhone verifiedComplete all preference pages
PENDING_PROFILE_COMPLETIONPreferences doneFill profile (name, username, bio)
COMPLETEDAll steps doneFull platform access

Step Weight Distribution

StepWeightDescription
Registration15%Auto-completed when user exists
Email Verification15%From Firebase (may already be done)
Phone Verification15%Required - via OTP
Preference Pages40%Split equally among active pages
Profile Completion15%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: RequestSend aOTP one-time passwordcode to be sent via SMS foruser's phone number for verification.

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

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request HeadersPrerequisite:

User
HeaderTypeRequiredDescription
AuthorizationstringYesBearer token: Bearer {accessToken}
Content-TypestringYesMustmust be at application/jsonPENDING_PHONE_VERIFICATION
step

Request JSON Sample:

{
  "phoneNumber": "+255712345678"
}

Request Body Parameters:

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

Supported Country Codes:

CodeCountry
  • +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:05T10:30:45",
      "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "phoneNumber": "+255****678",
        "expiresInSeconds": 600,
        "resendAvailableIn": 120
      }
    }
    

    Success Response Fields:

    Field Description
    tokenTemporary token for verify/resend endpoints
    phoneNumber Masked phone number for securitydisplay
    expiresInSeconds OTP validity period (default: 600 = 10 minutes)
    resendAvailableIn Seconds until resend is allowed (default: 120 = 2 minutes)

    Error Response JSON SamplesResponses:

    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-02T10:05T10: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"registered"
    }
    

    2. Verify OTP

    Purpose: Verify the OTP code sentand to the user'scomplete phone number.verification.

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

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Request Headers:

    HeaderTypeRequiredDescription
    AuthorizationstringYesBearer token: Bearer {accessToken}
    Content-TypestringYesMust be application/json

    Request JSON Sample:

    {
      "phoneNumber"token": "+255712345678"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "otp": "123456"
    }
    

    Request Body Parameters:

    Parameter Type Required Description Validation
    phoneNumbertoken string Yes PhoneToken numberfrom thatrequest-otp received OTPresponse E.164Must formatbe 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-02T10:30:45"05T10:35:00",
      "data": {
        "verified": true,
        "phoneNumber": "+255712345678"255****678",
        "onboardingStatus": "PENDING_PREFERENCES",
        "nextStep": "/api/v1/onboarding/preferences"pages"
      }
    }
    

    Success Response Fields:

    FieldDescription
    verifiedAlways true on success
    phoneNumberThe verified phone number
    onboardingStatusUpdated status (moves to PENDING_PREFERENCES)
    nextStepAPI endpoint for next onboarding step

    Error Response JSON SamplesResponses:

    Invalid OTP (403):

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

    Expired OTP Expired (403):

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

    Max Attempts Reached (403):

    {
      "success": false,
      "httpStatus": "FORBIDDEN",
      "message": "Maximum attempts reached"reached. Please request a new OTP.",
      "action_time": "2025-01-02T10:30:45"05T10:35:00",
      "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: ResendRequest a new OTP tocode using the sameexisting phone number (subject to cooldown).token.

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

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Request Headers:

    HeaderTypeRequiredDescription
    AuthorizationstringYesBearer token: Bearer {accessToken}
    Content-TypestringYesMust be application/json

    Request JSON Sample:

    {
      "phoneNumber"token": "+255712345678"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
    

    RequestSuccess Body ParametersResponse:

    Sameas endpoint

    Email

    VerificationEndpoints
    ParameterTypeRequiredDescriptionValidation
    phoneNumberstringYesPhone number to resendRequest OTP to E.164
    format

    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": "OTPEmail resentverification successfully"status",
      "action_time": "2025-01-02T10:30:45"05T10:00:00",
      "data": {
        "phoneNumber"verified": false,
        "email": "+255*jo****678"@example.com",
        "expiresInSeconds"required": 600,false,
        "resendAvailableIn"canSkip": 120true,
        "currentStep": "PENDING_EMAIL_VERIFICATION"
      }
    }
    

    Same response format as Request OTP


    Language5. PreferenceSkip Endpoint

    Email

    4. Set Language PreferenceVerification

    Purpose: SetSkip user'semail preferredverification languagestep for(if theallowed platform.by config).

    Endpoint: POST {base_url}/onboarding/language-preferenceemail-verification/skip

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Request Headers:

    HeaderTypeRequiredDescription
    AuthorizationstringYesBearer token: Bearer {accessToken}
    Content-TypestringYesMust be application/json

    Request JSON Sample:

    {
      "code": "sw"
    }
    

    Request Body Parameters:

    ParameterTypeRequiredDescriptionValidation
    codestringYesLanguage code2-5 characters, must be active language

    Supported Languages:

    CodeNameNative Name
    enEnglishEnglish
    swSwahiliKiswahili
    frFrenchFrançais
    zhChinese中文

    Success Response JSON Sample:

    {
      "success": true,
      "httpStatus": "OK",
      "message": "LanguageEmail preferenceverification updated"skipped",
      "action_time": "2025-01-02T10:30:45"05T10:05:00",
      "data": {
        "code"verified": false,
        "skipped": true,
        "nextStep": "sw",
        "name": "Swahili",
        "nativeName": "Kiswahili"PENDING_PHONE_VERIFICATION"
      }
    }
    

    Success


    Response

    Preference Fields:

    Pages
    FieldDescription
    codeLanguage code that was set
    nameEnglish name of the language
    nativeNameNative name of the language
    Endpoints


    Onboarding Progress Endpoint

    5.6. Get Onboarding Progress

    Purpose: Get detailedoverall onboarding progress information acrosswith all onboarding steps with weighted percentages.steps.

    Endpoint: GET {base_url}/onboarding/progress

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Request Headers:

    HeaderTypeRequiredDescription
    AuthorizationstringYesBearer token: Bearer {accessToken}
    Accept-LanguagestringNoPreferred language (default: en)

    Success Response JSON Sample:

    {
      "success": true,
      "httpStatus": "OK",
      "message": "Progress retrieved",
      "action_time": "2025-01-02T10:30:45"05T10:40:00",
      "data": {
        "percentage": 55.45.0,
        "currentStage": "PENDING_PREFERENCES",
        "currentStageLabel": "Complete your preferences",
        "steps": [
          {
            "key": "registration",
            "label": "Registration",
            "completed": true,
            "weight": 15.0
          },
          {0,
            "key"skippable": "email_verification",
            "label": "Email Verification",
            "completed": true,
            "weight": 15.0false
          },
          {
            "key": "phone_verification",
            "label": "Phone Verification",
            "completed": true,
            "weight": 15.00,
            "skippable": false
          },
          {
            "key": "page_interests",
            "label": "WhatYour are your interests?"Interests",
            "completed": true,
            "weight": 13.33
          },
          {33,
            "key"skippable": "page_industries",
            "label": "Select your industries",
            "completed": false,
            "weight": 13.33false
          },
          {
            "key": "page_goals",
            "label": "WhatYour are your goals?"Goals",
            "completed": false,
            "weight": 13.3433,
            "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.00,
            "skippable": false
          }
        ],
        "nextStep": {
          "key": "page_industries"page_goals",
          "label": "SelectYour your industries"Goals",
          "endpoint": "/api/v1/onboarding/pages?page=2",
          "skippable": true
        }
      }
    }
    

    Success Response Fields:

    FieldDescription
    percentageOverall completion percentage (0-100)
    currentStageCurrent onboarding status enum value
    currentStageLabelHuman-readable description of current stage
    stepsArray of all onboarding steps with completion status
    steps[].keyUnique identifier for the step
    steps[].labelDisplay label (translated)
    steps[].completedWhether step is completed
    steps[].weightPercentage weight of this step
    nextStepNext incomplete step details
    nextStep.keyStep identifier
    nextStep.labelStep display label
    nextStep.endpointAPI endpoint to complete this step

    Onboarding Pages Endpoints

    6.7. Get All Pages

    Purpose: RetrieveGet 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:

    HeaderTypeRequiredDescription
    AuthorizationstringYesBearer token: Bearer {accessToken}
    Accept-LanguagestringNoPreferred language (default: en)

    Query Parameters:

    Parameter Type Required Description
    all boolean No If true, returnsReturn all pages (default behavior)
    page integer No Get specific page by order number(1, 2, 3...)
    category string No Get page by category key
    current boolean No IfGet true, returns currentfirst incomplete page

    Success Response JSON Sample (all=true):

    {
      "success": true,
      "httpStatus": "OK",
      "message": "All pages retrieved",
      "action_time": "2025-01-02T10:30:45"05T10:45:00",
      "data": {
        "totalPages": 3,
        "completedPages": 1,
        "isOnboardingComplete": false,
        "pages": [
          {
            "id": "550e8400-e29b-41d4-a716-446655440001",
            "pageOrder": 1,
            "categoryKey": "interests",
            "title": "WhatMaslahi are your interests?"Yako",
            "description": "SelectChagua themambo areas you're most interested in exploring on Fursa Hub"yanayokuvutia",
            "bannerImages": [
              "https://cdn.fursahub.com/onboarding/interests-banner.interests.jpg"
            ],
            "isSkippable": false,
            "minSelections": 1,
            "maxSelections": 5,
            "options": [
              { "key": "technology"jobs", "label": "Technology"Kazi", "icon": "💻"briefcase" },
              { "key": "agriculture"funding", "label": "Agriculture"Ufadhili", "icon": "🌾"dollar" },
              { "key": "finance"events", "label": "Finance & Banking"Matukio", "icon": "💰"calendar" },
              { "key": "health"skills", "label": "Healthcare"Ujuzi", "icon": "🏥"book" },
              { "key": "education"networking", "label": "Education"Mitandao", "icon": "📚"users" }
            ],
            "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": "WhatMalengo are your goals?"Yako",
            "description": "TellUnataka uskufikia what you want to achieve on Fursa Hub",
            nini?"bannerImages": [],
            "isSkippable": false,true,
            "minSelections": 1,
            "maxSelections": 3,
            "options": [
              { "key": "find_job", "label": "FindKupata a Job"kazi", "icon": "💼"search" },
              { "key": "find_funding"start_business", "label": "FindKuanzisha Funding"biashara", "icon": "💵"
              },
              {
                "key": "network",
                "label": "Networking",
                "icon": "🤝"store" },
              { "key": "learn_skills", "label": "LearnKujifunza New Skills"ujuzi", "icon": "📖graduation" },
              { "key": "get_funding", "label": "Kupata ufadhili", "icon": "money" }
            ],
            "isCompleted": false
          }
        ]
      }
    }
    

    Success Response Fields:

    FieldDescription
    totalPagesTotal number of active onboarding pages
    completedPagesNumber of pages user has completed
    isOnboardingCompleteWhether all pages are completed
    pagesArray of page objects
    pages[].idUnique page identifier (UUID)
    pages[].pageOrderDisplay order (1, 2, 3...)
    pages[].categoryKeyUnique category identifier
    pages[].titlePage title (translated)
    pages[].descriptionPage description (translated)
    pages[].bannerImagesArray of banner image URLs
    pages[].isSkippableWhether page can be skipped
    pages[].minSelectionsMinimum options user must select
    pages[].maxSelectionsMaximum options user can select
    pages[].optionsArray of selectable options
    pages[].options[].keyOption identifier
    pages[].options[].labelOption label (translated)
    pages[].options[].iconOption icon (emoji or URL)
    pages[].isCompletedWhether user has completed this page

    7.8. Get SingleCurrent Page

    Purpose: RetrieveGet athe specificfirst onboardingincomplete pagepreference with progress context.page.

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

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Query Parameters:

    ParameterTypeRequiredDescription
    pageintegerYesPage order number (1, 2, 3...)

    Success Response JSON Sample:

    {
      "success": true,
      "httpStatus": "OK",
      "message": "PageCurrent page retrieved",
      "action_time": "2025-01-02T10:30:45"05T10:50:00",
      "data": {
        "page": {
          "id": "550e8400-e29b-41d4-a716-446655440002",
          "pageOrder": 2,
          "categoryKey": "industries"goals",
          "title": "SelectMalengo your industries"Yako",
          "description": "ChooseUnataka thekufikia industries you work in or want to explore",
          nini?"bannerImages": [],
          "isSkippable": true,
          "minSelections": 0,1,
          "maxSelections": 10,3,
          "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:

    FieldDescription
    pagePage object (same as in all pages response)
    progress.currentCurrent page number
    progress.totalTotal number of pages
    progress.nextPageNext page number (null if last)
    progress.isLastWhether this is the last page
    progress.isCompletedWhether 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: SubmitSave user's selected optionsselections for ana onboardingpreference page.

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

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Request Headers:

    HeaderTypeRequiredDescription
    AuthorizationstringYesBearer token: Bearer {accessToken}
    Content-TypestringYesMust be application/json

    Path Parameters:

    Parameter Type Required Description
    pageId UUID Yes Page identifier

    Request JSON Sample:

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

    Request Body Parameters:

    Parameter Type Required Description Validation
    selectedOptions string[]array Yes Array of selected option keys Must meetmatch page's min/max selection rulesselections

    Success Response JSON Sample:

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

    Success Response Fields:

    FieldDescription
    savedAlways true on success
    progress.currentCurrent page number
    progress.totalTotal pages
    progress.nextPageNext page to complete (null if done)
    progress.isLastWhether current was last page
    progress.isCompletedWhether all pages are now completed

    Error Response JSON SamplesResponses:

    Too Few Selections (400):

    {
      "success": false,
      "httpStatus": "BAD_REQUEST",
      "message": "Minimum selections1 notselection(s) met"required",
      "action_time": "2025-01-02T10:30:45"05T10:55:00",
      "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"option: unknown_key",
      "action_time": "2025-01-02T10:30:45"05T10:55:00",
      "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 ana optionalpreference onboardingpage page.(only if page is skippable).

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

    Access Level: 🔒 Protected (Requires valid access token)

    Authentication: Bearer Token

    Path Parameters:

    ParameterTypeRequiredDescription
    pageIdUUIDYesPage identifier

    Success Response JSON Sample:

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

    Error Response JSON Sample:

    (Page Not Skippable (400)Skippable):

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

    AdminLanguage EndpointsPreference Endpoint


    11. GetSet AllLanguage Pages (Admin)Preference

    Purpose: RetrieveUpdate alluser's onboardinglanguage pagespreference including(can inactivebe onescalled foranytime adminduring management.onboarding).

    Endpoint: GETPOST {base_url}/onboarding/pages/managelanguage-preference

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

    Authentication: Bearer Token

    Request JSON Sample:

    {
      "code": "sw"
    }
    

    Success Response JSON Sample:

    {
      "success": true,
      "httpStatus": "OK",
      "message": "PagesLanguage retrieved"preference updated",
      "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:

    ParameterTypeRequiredDescriptionValidation
    pageOrderintegerYesDisplay orderMin: 1
    categoryKeystringYesUnique category identifierNot blank
    isSkippablebooleanNoWhether page can be skippedDefault: false
    minSelectionsintegerNoMinimum required selectionsMin: 0
    maxSelectionsintegerNoMaximum allowed selectionsMin: 1
    translationsobjectYesLanguage translations mapAt least en required
    translations.{lang}.titlestringYesPage title in languageNot blank
    translations.{lang}.descriptionstringNoPage description-
    bannerImagesstring[]NoArray of banner image URLs-
    optionsarrayYesArray of option objectsAt least 1 option
    options[].keystringYesUnique option identifierNot blank
    options[].iconstringNoIcon (emoji or URL)-
    options[].translationsobjectYesOption label translationsAt least en required

    Success Response JSON Sample:

    {
      "success": true,
      "httpStatus": "CREATED",
      "message": "Page created",
      "action_time": "2025-01-02T10:30:45"05T11:05:00",
      "data": {
        "id"code": "550e8400-e29b-41d4-a716-446655440004"sw",
        "pageOrder": 4,
        "categoryKey"name": "experience_level"Swahili",
        "title"nativeName": "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": falseKiswahili"
      }
    }
    

    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:

    ParameterTypeRequiredDescription
    pageIdUUIDYesPage 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:

    ParameterTypeRequiredDescription
    pageIdUUIDYesPage 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:

    ParameterTypeRequiredDescriptionValidation
    pageIdsUUID[]YesOrdered array of page IDsNot 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 CodeHTTP StatusWhen It Occurs
    400BAD_REQUESTInvalid input, selection rules violated, page not skippable
    401UNAUTHORIZEDToken empty, invalid, or expired
    403FORBIDDENOTP invalid/expired, insufficient permissions
    404NOT_FOUNDPage or resource not found
    409CONFLICTPhone already registered
    422UNPROCESSABLE_ENTITYValidation errors
    429TOO_MANY_REQUESTSRate limit or cooldown active

    Server-Level Exceptions (500+)

    Status CodeHTTP StatusWhen It Occurs
    500INTERNAL_SERVER_ERRORDatabase errors, SMS gateway failures

    Frontend Implementation Guide

    CompletePhone OnboardingVerification FlowScreen

    const1. completeOnboardingShow =phone asyncinput ()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
    Step7. 1:On Checksuccess: currentNavigate progressbased conston 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 → COMPLETEDnextStep
    

    OTPPreference ConfigurationPages Loop

    1. 
    GET /onboarding/pages?current=true 2. If page is null done, to profile 3. Display with: ├── Title, (translated) ├── Banner image Options selectable chips/cards └── button submit: POST /onboarding/pages/{pageId}/response
    SettingValue
    OTPAll Length6navigate digits
    Expiry10page minutes
    Maxdescription Attempts3
    Resend├── Cooldown2as minutes
    RateSkip Limit3(if requestsisSkippable) per4. 10On minutes
    5. Check progress.isCompleted ├── true → Navigate to profile completion └── false → GET /onboarding/pages?current=true (loop)

    SupportedProgress LanguagesIndicator

    GET 
    /onboarding/progress ├── Use
    CodeNameNative Name
    enEnglishEnglish
    swSwahiliKiswahili
    frFrenchFrançais
    zhChinese中文

    Admin Rolespercentage for Pageprogress Management

    bar ├──ShowstepsasHighlightcurrentstep└──Show
    Role Candots/icons Manage├── Pages
    ROLE_USERcompleted steps with checkmarks ❌ No
    ROLE_MODERATOR✅ Yes
    ROLE_ADMIN✅ Yes
    ROLE_SUPER_ADMIN✅ Yes