# Onboarding Analytics Admin

**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"
  }
}
```

---

## Admin: Manage Onboarding Pages

These endpoints allow admins to create, edit, reorder, and manage onboarding preference pages without code changes.

---

## 12. Get All Pages (Admin)
**Purpose**: Get all onboarding pages with full details for admin management.

**Endpoint**: <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/manage`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Authentication**: Bearer Token (ROLE_ADMIN, ROLE_SUPER_ADMIN, ROLE_MODERATOR)

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Pages retrieved",
  "action_time": "2025-01-05T12:00:00",
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "categoryKey": "interests",
      "pageOrder": 1,
      "isActive": true,
      "isSkippable": false,
      "minSelections": 1,
      "maxSelections": 5,
      "bannerImages": ["https://cdn.fursahub.com/onboarding/interests.jpg"],
      "translations": {
        "en": {
          "title": "Your Interests",
          "description": "Select what interests you"
        },
        "sw": {
          "title": "Maslahi Yako",
          "description": "Chagua mambo yanayokuvutia"
        }
      },
      "options": [
        {
          "key": "jobs",
          "icon": "briefcase",
          "translations": {
            "en": "Jobs",
            "sw": "Kazi"
          }
        },
        {
          "key": "funding",
          "icon": "dollar",
          "translations": {
            "en": "Funding",
            "sw": "Ufadhili"
          }
        }
      ],
      "createdAt": "2025-01-01T00:00:00",
      "updatedAt": "2025-01-05T10:00:00"
    }
  ]
}
```

---

## 13. Get Page by ID (Admin)
**Purpose**: Get single page details for editing.

**Endpoint**: <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/manage/{pageId}`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Path Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| pageId | UUID | Yes | Page identifier |

**Success Response**: Same structure as single item in Get All Pages

---

## 14. Create Page
**Purpose**: Create a new onboarding preference page.

**Endpoint**: <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/manage`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Authentication**: Bearer Token

**Request JSON Sample**:
```json
{
  "categoryKey": "location",
  "pageOrder": 4,
  "isActive": true,
  "isSkippable": true,
  "minSelections": 1,
  "maxSelections": 1,
  "bannerImages": ["https://cdn.fursahub.com/onboarding/location.jpg"],
  "translations": {
    "en": {
      "title": "Your Location",
      "description": "Where are you based?"
    },
    "sw": {
      "title": "Mahali Ulipo",
      "description": "Unaishi wapi?"
    }
  },
  "options": [
    {
      "key": "dar_es_salaam",
      "icon": "map-pin",
      "translations": {
        "en": "Dar es Salaam",
        "sw": "Dar es Salaam"
      }
    },
    {
      "key": "arusha",
      "icon": "map-pin",
      "translations": {
        "en": "Arusha",
        "sw": "Arusha"
      }
    },
    {
      "key": "mwanza",
      "icon": "map-pin",
      "translations": {
        "en": "Mwanza",
        "sw": "Mwanza"
      }
    },
    {
      "key": "other",
      "icon": "map",
      "translations": {
        "en": "Other",
        "sw": "Nyingine"
      }
    }
  ]
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| categoryKey | string | Yes | Unique category identifier | Lowercase, no spaces |
| pageOrder | integer | Yes | Display order (1, 2, 3...) | Min: 1 |
| isActive | boolean | No | Page is active | Default: true |
| isSkippable | boolean | No | User can skip this page | Default: false |
| minSelections | integer | No | Minimum options to select | Default: 1 |
| maxSelections | integer | No | Maximum options to select | Default: 10 |
| bannerImages | array | No | Banner image URLs | Array of URLs |
| translations | object | Yes | Title/description per language | Must include "en" |
| translations.{lang}.title | string | Yes | Page title | Max 100 chars |
| translations.{lang}.description | string | No | Page description | Max 500 chars |
| options | array | Yes | Selectable options | Min 2 options |
| options[].key | string | Yes | Unique option key | Lowercase, no spaces |
| options[].icon | string | No | Icon name | From icon library |
| options[].translations | object | Yes | Label per language | Must include "en" |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Page created",
  "action_time": "2025-01-05T12:05:00",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440004",
    "categoryKey": "location",
    "pageOrder": 4,
    "isActive": true,
    ...
  }
}
```

**Error Responses**:

*Duplicate Category Key (400):*
```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Category key already exists: interests",
  "action_time": "2025-01-05T12:05:00",
  "data": "Category key already exists: interests"
}
```

---

## 15. Update Page
**Purpose**: Update an existing onboarding page.

**Endpoint**: <span style="background-color: #ffc107; color: black; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PUT</span> `{base_url}/onboarding/pages/manage/{pageId}`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Path Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| pageId | UUID | Yes | Page identifier |

**Request JSON Sample**: Same as Create Page

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Page updated",
  "action_time": "2025-01-05T12:10:00",
  "data": { ... }
}
```

---

## 16. Delete Page
**Purpose**: Delete an onboarding page (soft delete recommended - use deactivate instead).

**Endpoint**: <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> `{base_url}/onboarding/pages/manage/{pageId}`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Path Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| pageId | UUID | Yes | Page identifier |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Page deleted",
  "action_time": "2025-01-05T12:15:00",
  "data": null
}
```

---

## 17. Activate Page
**Purpose**: Activate a deactivated page.

**Endpoint**: <span style="background-color: #fd7e14; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `{base_url}/onboarding/pages/manage/{pageId}/activate`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Page activated",
  "action_time": "2025-01-05T12:20:00",
  "data": null
}
```

---

## 18. Deactivate Page
**Purpose**: Deactivate a page (hides from users without deleting).

**Endpoint**: <span style="background-color: #fd7e14; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `{base_url}/onboarding/pages/manage/{pageId}/deactivate`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Page deactivated",
  "action_time": "2025-01-05T12:25:00",
  "data": null
}
```

---

## 19. Reorder Pages
**Purpose**: Change the display order of all pages at once.

**Endpoint**: <span style="background-color: #fd7e14; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `{base_url}/onboarding/pages/manage/reorder`

**Access Level**: 🔒 Protected (Admin/Moderator)

**Request JSON Sample**:
```json
{
  "pageIds": [
    "550e8400-e29b-41d4-a716-446655440002",
    "550e8400-e29b-41d4-a716-446655440001",
    "550e8400-e29b-41d4-a716-446655440003",
    "550e8400-e29b-41d4-a716-446655440004"
  ]
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| pageIds | array | Yes | Page IDs in new order | Must include all active page IDs |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Pages reordered",
  "action_time": "2025-01-05T12:30:00",
  "data": null
}
```

---

## Admin Panel Implementation Guide

### Page List View
```
GET /onboarding/pages/manage
├── Display table with: Order, Category, Title (en), Status, Actions
├── Actions: Edit, Activate/Deactivate, Delete
├── Drag-and-drop for reorder → PATCH /onboarding/pages/manage/reorder
└── "Add New Page" button
```

### Create/Edit Page Form
```
Fields:
├── Category Key (text, required, unique)
├── Page Order (number)
├── Is Skippable (checkbox)
├── Min/Max Selections (numbers)
├── Banner Images (file upload → Files API)
├── Translations (tabs for each language)
│   ├── Title (text)
│   └── Description (textarea)
└── Options (repeater)
    ├── Key (text, unique within page)
    ├── Icon (icon picker)
    └── Translations (text per language)

On Save:
├── New: POST /onboarding/pages/manage
└── Edit: PUT /onboarding/pages/manage/{pageId}
```

### Quick Actions
```
Activate: PATCH /onboarding/pages/manage/{pageId}/activate
Deactivate: PATCH /onboarding/pages/manage/{pageId}/deactivate
Delete: DELETE /onboarding/pages/manage/{pageId} (with confirmation)
```

---

## Frontend Implementation Guide

### Phone Verification Screen
```
1. Show phone input with country code selector
2. On submit: POST /onboarding/auth-phone/request-otp
3. Save token from response
4. Navigate to OTP input screen
5. Show countdown for resendAvailableIn
6. On OTP submit: POST /onboarding/auth-phone/verify
7. On success: Navigate based on nextStep
```

### Preference Pages Loop
```
1. GET /onboarding/pages?current=true
2. If page is null → All done, navigate to profile
3. Display page with:
   ├── Title, description (translated)
   ├── Banner image
   ├── Options as selectable chips/cards
   └── Skip button (if isSkippable)
4. On submit: POST /onboarding/pages/{pageId}/response
5. Check progress.isCompleted
   ├── true → Navigate to profile completion
   └── false → GET /onboarding/pages?current=true (loop)
```

### Progress Indicator
```
GET /onboarding/progress
├── Use percentage for progress bar
├── Show steps as dots/icons
├── Highlight current step
└── Show completed steps with checkmarks
```