# 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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{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**:
```json
{
  "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**:
```json
{
  "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):*
```json
{
  "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):*
```json
{
  "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):*
```json
{
  "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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/onboarding/auth-phone/verify`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Request JSON Sample**:
```json
{
  "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**:
```json
{
  "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):*
```json
{
  "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):*
```json
{
  "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):*
```json
{
  "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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/onboarding/auth-phone/resend-otp`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Request JSON Sample**:
```json
{
  "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**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/onboarding/email-verification/status`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Success Response JSON Sample**:
```json
{
  "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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/onboarding/email-verification/skip`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Success Response JSON Sample**:
```json
{
  "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**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/onboarding/progress`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Success Response JSON Sample**:
```json
{
  "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**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{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**:
```json
{
  "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**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/onboarding/pages?current=true`

**Success Response JSON Sample**:
```json
{
  "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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{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**:
```json
{
  "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**:
```json
{
  "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):*
```json
{
  "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):*
```json
{
  "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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/onboarding/pages/{pageId}/skip`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Success Response JSON Sample**:
```json
{
  "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)**:
```json
{
  "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**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/onboarding/language-preference`

**Access Level**: 🔒 Protected

**Authentication**: Bearer Token

**Request JSON Sample**:
```json
{
  "code": "sw"
}
```

**Success Response JSON Sample**:
```json
{
  "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
```