Profile
Base URL: https://api.fursahub.com/api/v1
Short Description: Profile management endpoints for Fursa Hub. Allows users to view and update their profile information, manage photos, change theme and language preferences. Profile completion is the final step of onboarding.
Hints:
- All profile endpoints require authentication (Bearer token)
- Username must be unique and can only contain letters, numbers, and underscores
- Profile photos should be uploaded via Files API first, then URLs added here
- Completing profile (fullName + username + bio) auto-completes onboarding if at final step
- Theme changes take effect immediately on client side
Profile in Onboarding Context
┌─────────────────────────────────────────────────────────────────────────┐
│ PROFILE & ONBOARDING RELATIONSHIP │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Profile data comes from multiple sources: │
│ │
│ 1. FIREBASE (auto-filled at registration) │
│ └── fullName (from Google/Apple account) │
│ └── profilePhotoUrl (from social account) │
│ └── email (from auth provider) │
│ │
│ 2. REGISTRATION (user choice) │
│ └── preferredLanguage │
│ └── theme │
│ │
│ 3. ONBOARDING (user completes) │
│ └── phoneNumber (verified via OTP) │
│ └── preferences (from onboarding pages) │
│ │
│ 4. PROFILE COMPLETION (final step) │
│ └── username (required, unique) │
│ └── bio (required) │
│ └── fullName (can edit Firebase default) │
│ └── gender, link (optional) │
│ │
│ When user completes: fullName + username + bio │
│ └── Onboarding auto-completes if at PENDING_PROFILE_COMPLETION │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Endpoints
1. Get Profile
Purpose: Retrieve current user's complete profile information.
Endpoint: GET {base_url}/profile
Access Level: 🔒 Protected
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer {accessToken} |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Profile retrieved",
"action_time": "2025-01-05T10:30:45",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"username": "johndoe",
"phoneNumber": "+255712345678",
"fullName": "John Doe",
"bio": "Software developer passionate about tech",
"gender": "MALE",
"link": "https://johndoe.com",
"profilePhotoUrls": [
"https://files.fursahub.com/bucket/profile/photo1.jpg",
"https://files.fursahub.com/bucket/profile/photo2.jpg"
],
"primaryPhotoUrl": "https://files.fursahub.com/bucket/profile/photo1.jpg",
"isPhoneVerified": true,
"isEmailVerified": true,
"preferredLanguage": "sw",
"theme": "DARK",
"authProvider": "GOOGLE",
"role": "ROLE_USER",
"onboardingStatus": "COMPLETED",
"isOnboardingComplete": true,
"createdAt": "2025-01-01T08:00:00",
"updatedAt": "2025-01-05T10:30:00"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| id | Unique user identifier (UUID) |
| User's email address | |
| username | Unique username (lowercase) |
| phoneNumber | Verified phone number in E.164 format |
| fullName | Display name |
| bio | User biography/description |
| gender | MALE or FEMALE |
| link | Personal website or social link |
| profilePhotoUrls | Array of all profile photo URLs |
| primaryPhotoUrl | First photo URL (main profile picture) |
| isPhoneVerified | Phone verification status |
| isEmailVerified | Email verification status |
| preferredLanguage | Language code (en, sw, fr, zh) |
| theme | LIGHT, DARK, or SYSTEM |
| authProvider | GOOGLE, APPLE, or EMAIL |
| role | User role (ROLE_USER, ROLE_ADMIN, etc.) |
| onboardingStatus | Current onboarding step |
| isOnboardingComplete | true if onboarding finished |
2. Update Profile
Purpose: Update user profile information. Only provided fields are updated.
Endpoint: PUT {base_url}/profile
Access Level: 🔒 Protected
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer {accessToken} |
| Content-Type | string | Yes | application/json |
Request JSON Sample:
{
"fullName": "John Doe Updated",
"username": "johndoe_new",
"bio": "Building the future of opportunity in East Africa",
"gender": "MALE",
"link": "https://linkedin.com/in/johndoe",
"profilePhotoUrls": [
"https://files.fursahub.com/bucket/profile/new-photo.jpg"
],
"theme": "LIGHT",
"preferredLanguage": "en"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| fullName | string | No | Display name | Min: 2, Max: 100 chars |
| username | string | No | Unique username | Min: 3, Max: 30 chars, alphanumeric + underscore only |
| bio | string | No | User biography | Max: 500 chars |
| gender | string | No | User gender | enum: MALE, FEMALE |
| link | string | No | Personal/social link | Must be valid URL (https://...) |
| profilePhotoUrls | array | No | List of photo URLs | Array of strings |
| theme | string | No | UI theme preference | enum: LIGHT, DARK, SYSTEM |
| preferredLanguage | string | No | Language preference | Must be active language code |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Profile updated",
"action_time": "2025-01-05T10:35:00",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"username": "johndoe_new",
"fullName": "John Doe Updated",
"bio": "Building the future of opportunity in East Africa",
"theme": "LIGHT",
"preferredLanguage": "en",
"onboardingStatus": "COMPLETED",
"isOnboardingComplete": true
}
}
Error Responses:
Username Already Taken (409):
{
"success": false,
"httpStatus": "CONFLICT",
"message": "Username already taken",
"action_time": "2025-01-05T10:35:00",
"data": "Username already taken"
}
Invalid Language Code (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Invalid or inactive language code: xx",
"action_time": "2025-01-05T10:35:00",
"data": "Invalid or inactive language code: xx"
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-01-05T10:35:00",
"data": {
"username": "Username can only contain letters, numbers, and underscores",
"fullName": "Name must be 2-100 characters"
}
}
3. Update Theme (Quick Toggle)
Purpose: Quick endpoint to change theme without full profile update.
Endpoint: PATCH {base_url}/profile/theme
Access Level: 🔒 Protected
Authentication: Bearer Token
Request JSON Sample:
{
"theme": "DARK"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| theme | string | Yes | Theme preference | enum: LIGHT, DARK, SYSTEM |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Theme updated",
"action_time": "2025-01-05T10:40:00",
"data": {
"theme": "DARK"
}
}
4. Check Username Availability
Purpose: Check if a username is available before updating profile.
Endpoint: GET {base_url}/profile/username/check
Access Level: 🔒 Protected
Authentication: Bearer Token
Query Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| username | string | Yes | Username to check | Min: 3 chars |
Success Response JSON Sample (Available):
{
"success": true,
"httpStatus": "OK",
"message": "Username available",
"action_time": "2025-01-05T10:45:00",
"data": {
"username": "newusername",
"available": true
}
}
Success Response JSON Sample (Taken):
{
"success": true,
"httpStatus": "OK",
"message": "Username taken",
"action_time": "2025-01-05T10:45:00",
"data": {
"username": "existinguser",
"available": false
}
}
5. Add Profile Photo
Purpose: Add a new photo to user's profile photos array.
Endpoint: POST {base_url}/profile/photo
Access Level: 🔒 Protected
Authentication: Bearer Token
Request JSON Sample:
{
"photoUrl": "https://files.fursahub.com/bucket/profile/new-photo.jpg"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| photoUrl | string | Yes | URL of uploaded photo | Must be valid URL |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Photo added",
"action_time": "2025-01-05T10:50:00",
"data": {
"profilePhotoUrls": [
"https://files.fursahub.com/bucket/profile/photo1.jpg",
"https://files.fursahub.com/bucket/profile/new-photo.jpg"
],
"primaryPhotoUrl": "https://files.fursahub.com/bucket/profile/photo1.jpg"
}
}
6. Remove Profile Photo
Purpose: Remove a photo from user's profile photos array.
Endpoint: DELETE {base_url}/profile/photo
Access Level: 🔒 Protected
Authentication: Bearer Token
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| photoUrl | string | Yes | URL of photo to remove |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Photo removed",
"action_time": "2025-01-05T10:55:00",
"data": {
"profilePhotoUrls": [
"https://files.fursahub.com/bucket/profile/photo1.jpg"
],
"primaryPhotoUrl": "https://files.fursahub.com/bucket/profile/photo1.jpg"
}
}
Frontend Implementation Guide
Profile Completion Screen
When onboardingStatus = "PENDING_PROFILE_COMPLETION":
1. GET /profile to fetch current data
2. Show form with:
├── fullName (pre-filled from Firebase)
├── username (auto-generated, editable)
│ └── On change: GET /profile/username/check
├── bio (required)
├── gender (optional)
└── link (optional)
3. PUT /profile with form data
4. If isOnboardingComplete = true → Navigate to home
Settings Screen
Theme Toggle:
└── PATCH /profile/theme with selected theme
└── Apply theme immediately on client
Language Change:
└── PUT /profile with preferredLanguage
└── Reload UI with new language strings
Profile Edit:
└── PUT /profile with changed fields only
Photo Management
Adding Photo:
1. Upload via Files API (POST /files/upload-single)
2. Get permanentUrl from response
3. POST /profile/photo with photoUrl
Removing Photo:
1. DELETE /profile/photo?photoUrl=...
2. Optionally delete from Files API
No comments to display
No comments to display