Skip to main content

Onboarding EndUser

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

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"
  }
}

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