Profile
Base URL: https://api.fursahub.com/api/v1
Short Description: The Profile APImanagement manages user profile informationendpoints for Fursa Hub. ItAllows handlesusers to view and update their profile retrieval,information, updates,manage usernamephotos, validation,change theme and profilelanguage photopreferences. management. The API features automatic onboardingProfile completion detectionis whenthe requiredfinal profilestep fieldsof are populated.onboarding.
Hints:
Profile updates automatically complete onboarding whenfullName,username, andbioare all setUsernames must be unique, 3-30 characters, alphanumeric with underscores onlyProfile photos are stored as a list (JSONB) - users can have multiple photosOne photo can be set as "primary" for display purposesGender field is optional with values:MALE,- 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 Managementin FlowOnboarding Context
┌─────────────────────────────────────────────────────────────────────────┐
│ PROFILE & ONBOARDING RELATIONSHIP │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Profile Completiondata comes from multiple sources: │
│ │
│ 1. FIREBASE (Duringauto-filled Onboarding)
at ┌─────────────────────────────────────────────────────────────────────────┐
│ PROFILE & ONBOARDING RELATIONSHIP │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Profile Completiondata comes from multiple sources: │
│ │
│ 1. FIREBASE (Duringauto-filled Onboarding)- registration)
User│reaches│profile└──stepfullNameafter(fromcompletingGoogle/Applephoneaccount)verification│and│preferences└── UserprofilePhotoUrlupdates(fromprofilesocial account) │ │ └── email (from auth provider) │ │ │ │ 2. REGISTRATION (user choice) │ │ └── preferredLanguage │ │ └── theme │ │ │ │ 3. ONBOARDING (user completes) │ │ └── phoneNumber (verified via│PUTOTP)/api/v1/profilewith│required└──fieldsBackend auto-detects completionwhenfullName,username, andbioare all setOnboarding status updatedtoCOMPLETEDautomaticallyUser redirectedto main app/dashboard
Profile Photo Flow
Upload photovia File Management APIpreferences (onboardingPOSTfrom/api/v1/files/upload-singlewithpages)directory││ │ │ 4. PROFILE)AddCOMPLETIONphoto(finalURLstep)to│profile│via└──POSTusername/api/v1/profile/photo(required, Setunique)primary│photo│via└──(required) │ │ └── fullName (can edit Firebase default) │ │ └── gender, link (optional)PATCHbio/api/v1/profile/photo/primaryRemove│photo│via│When user completes: fullName + username + bio │ │ └── Onboarding auto-completes ifDELETE│/api/v1/profile/photoneededat
Username│
Selection│ Flow
│
Check availabilityviaGET /api/v1/profile/username/check?username=desired_nameIf available: Proceed with profile updateIf taken: Prompt user to choose different username
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"
}└─────────────────────────────────────────────────────────────────────────┘
Endpoints
1. Get Current Profile
Purpose: Retrieve the authenticatedcurrent user's complete profile information.
Endpoint: GET {base_url}/profile
Access Level: 🔒 Protected (Requires valid access token)
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 successfully"retrieved",
"action_time": "2025-01-02T10:05T10:30:45",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@gmail.user@example.com",
"username": "johndoe",
"phoneNumber": "+255712345678",
"fullName": "John Doe",
"bio": "Passionate softwareSoftware developer withpassionate 5+about years of experience in mobile and web development. Looking for exciting opportunities in East Africa's tech ecosystem."tech",
"gender": "MALE",
"link": "https://linkedin.com/in/johndoe"johndoe.com",
"profilePhotoUrls": [
"http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/a1b2c3d4_photo1.photo1.jpg",
"http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/b2c3d4e5_photo2.photo2.jpg"
],
"primaryPhotoUrl": "http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/a1b2c3d4_photo1.photo1.jpg",
"isPhoneVerified": true,
"isEmailVerified": true,
"preferredLanguage": "en"sw",
"theme": "DARK",
"authProvider": "GOOGLE",
"role": "ROLE_USER",
"onboardingStatus": "COMPLETED",
"isOnboardingComplete": true,
"createdAt": "2025-01-01T08:00:00",
"updatedAt": "2025-01-02T10:05T10:30:45"00"
}
}
Success Response Fields:
| Field | Description |
|---|---|
id |
Unique user identifier (UUID) |
email |
User's email address |
username |
Unique username ( |
phoneNumber |
Verified phone number in E.164 format |
fullName |
|
bio |
|
gender |
MALE |
link |
|
profilePhotoUrls |
Array of all profile photo URLs |
primaryPhotoUrl |
|
isPhoneVerified |
Phone verification status |
isEmailVerified |
Email verification status |
preferredLanguage |
Language |
| theme | , DARK, or SYSTEM |
GOOGLE, APPLE, or EMAIL |
|
role |
User role |
onboardingStatus |
Current onboarding step |
isOnboardingComplete |
true |
| |
|
Error Response JSON Sample:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token is missing or invalid",
"action_time": "2025-01-02T10:30:45",
"data": "Token is missing or invalid"
}
2. Update Profile
Purpose: Update user profile fields.information. AutomaticallyOnly completes onboarding when requiredprovided fields (fullName, username, bio) are all set.updated.
Endpoint: PUT {base_url}/profile
Access Level: 🔒 Protected (Requires valid access token)
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"Doe Updated",
"username": "johndoe"johndoe_new",
"bio": "PassionateBuilding softwarethe developer with 5+ yearsfuture of experienceopportunity in mobileEast and web development."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 | chars |
|
username |
string | No | ||
| 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 |
| 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 |
*Required for onboarding completion
Auto-Completion Logic:
When the following conditions are met, onboarding automatically completes:
fullNameis not null/emptyusernameis not null/emptybiois not null/emptyCurrentonboardingStatusisPENDING_PROFILE_COMPLETION
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Profile updated successfully"updated",
"action_time": "2025-01-02T10:30:45"05T10:35:00",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@gmail.user@example.com",
"username": "johndoe",
"phoneNumber": "+255712345678"johndoe_new",
"fullName": "John Doe"Doe Updated",
"bio": "PassionateBuilding softwarethe developer with 5+ yearsfuture of experienceopportunity in mobileEast and web development."Africa",
"gender"theme": "MALE"LIGHT",
"link": "https://linkedin.com/in/johndoe",
"profilePhotoUrls": [],
"primaryPhotoUrl": null,
"isPhoneVerified": true,
"isEmailVerified": true,
"preferredLanguage": "en",
"authProvider": "GOOGLE",
"role": "ROLE_USER",
"onboardingStatus": "COMPLETED",
"isOnboardingComplete": true,
"createdAt": "2025-01-01T08:00:00",
"updatedAt": "2025-01-02T10:30:45"true
}
}
Success Response Fields:
| |
| |
| |
Error Response JSON SamplesResponses:
Username Already Taken (409):
{
"success": false,
"httpStatus": "CONFLICT",
"message": "Username already taken",
"action_time": "2025-01-02T10:30:45"05T10:35:00",
"data": "Username 'johndoe' is already intaken"
use"}
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-02T10:30:45"05T10:35:00",
"data": {
"username": "Username can only contain letters, numbers, and underscores",
"fullName": "Name must be 3-30 characters, alphanumeric and underscores only",
"bio": "Bio must not exceed 5002-100 characters",
"link": "Must be a valid URL"
}
}
Purpose: Quick endpoint to change theme without full profile update. Endpoint: PATCH Access Level: 🔒 Protected Authentication: Bearer Token Request JSON Sample: Request Body Parameters: Success Response JSON Sample: Purpose: Check if a username is available before updating profile. Endpoint: GET Access Level: 🔒 Protected Authentication: Bearer Token Query Parameters: Success Response JSON Sample (Available): Success Response JSON Sample ( Purpose: Add a new photo Endpoint: POST Access Level: 🔒 Protected Authentication: Bearer Token Request JSON Sample: Request Body Parameters: Success Response JSON Sample: Purpose: Endpoint: Access Level: 🔒 Protected Authentication: Bearer Token Success Response JSON Sample:Invalid
Gender3.
ValueUpdate Theme (400)Quick Toggle){base_url}/profile/theme{
"theme": "DARK"
}
Parameter
Type
Required
Description
Validation
theme
string
Yes
Theme preference
enum:
LIGHT, DARK, SYSTEM{
"success": false,true,
"httpStatus": "BAD_REQUEST"OK",
"message": "InvalidTheme gender value"updated",
"action_time": "2025-01-02T10:30:45"05T10:40:00",
"data": {
"Invalidtheme": gender:"DARK"
UNKNOWN. Valid values: MALE, FEMALE, OTHER, PREFER_NOT_TO_SAY"}
}
3.4. Check Username Availability{base_url}/profile/username/check (Requires valid access token)
Request Headers:
Header
Type
Required
Description
Authorization
string
Yes
Bearer token: Bearer {accessToken}
Parameter
Type
Required
Description
Validation
usernamestring
Yes
Username to check
3-30Min: chars,3 alphanumeric + underscorechars{
"success": true,
"httpStatus": "OK",
"message": "Username is available",
"action_time": "2025-01-02T10:30:45"05T10:45:00",
"data": {
"username": "johndoe_new"newusername",
"available": true
}
}
Not Available)Taken):{
"success": true,
"httpStatus": "OK",
"message": "Username is not available"taken",
"action_time": "2025-01-02T10:30:45"05T10:45:00",
"data": {
"username": "johndoe"existinguser",
"available": false
}
}
Success Response Fields:
Field
Description
username
The username that was checked
available
true if available, false if taken
Error Response JSON Sample:
Invalid Username Format (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Invalid username format",
"action_time": "2025-01-02T10:30:45",
"data": "Username must be 3-30 characters, containing only letters, numbers, and underscores"
}
4.5. Add Profile PhotoURL to the user's profile photophotos list. Photo must be uploaded first via File Management API.array.{base_url}/profile/photo (Requires valid access token)
Request Headers:
Header
Type
Required
Description
Authorization
string
Yes
Bearer token: Bearer {accessToken}
Content-Type
string
Yes
Must be application/json
{
"photoUrl": "http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/a1b2c3d4_photo.new-photo.jpg"
}
Parameter
Type
Required
Description
Validation
photoUrlstring
Yes
URL of uploaded photo
Valid URL format
Photo Upload Flow:
First upload photo via POST /api/v1/files/upload-single with directory=PROFILE
Get permanentUrl from upload response
Add URL to profile via this endpoint
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Photo added successfully",
"action_time": "2025-01-02T10:30:45",
"data": {
"profilePhotoUrls": [
"http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo.jpg"
],
"primaryPhotoUrl": "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo.jpg",
"totalPhotos": 1
}
}
Success Response Fields:
Field
Description
profilePhotoUrls
Updated list of all profile photo URLs
primaryPhotoUrl
Current primary photo (auto-set to first if none selected)
totalPhotos
Total number of photos in profile
Error Response JSON Samples:
Invalid URL (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Invalid photo URL",
"action_time": "2025-01-02T10:30:45",
"data": "Photo URL must be a valid URL"
}
Photo Already Exists (409):
{
"success": false,
"httpStatus": "CONFLICT",
"message": "Photo already exists",
"action_time": "2025-01-02T10:30:45",
"data": "This photo URL is already in your profile"
}
5. Remove Profile Photo
Purpose: Remove a photo URL from the user's profile photo list.
Endpoint: DELETE {base_url}/profile/photo
Access Level: 🔒 Protected (Requires valid access token)
Authentication: Bearer Token
Request Headers:
Header
Type
Required
Description
Authorization
string
Yes
Bearer token: Bearer {accessToken}
Content-Type
string
Yes
Must be valid application/json
Request JSON Sample:
{
"photoUrl": "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo.jpg"
}
Request Body Parameters:
Parameter
Type
Required
Description
Validation
photoUrl
string
Yes
URL of photo to remove
Must exist in profile{
"success": true,
"httpStatus": "OK",
"message": "Photo removed successfully"added",
"action_time": "2025-01-02T10:30:45"05T10:50:00",
"data": {
"profilePhotoUrls": [
"http:https://localhost:9000/fursa-550e8400...files.fursahub.com/bucket/profile/photo1.jpg",
"https://files.fursahub.com/bucket/profile/b2c3d4e5_photo2.new-photo.jpg"
],
"primaryPhotoUrl": "http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/b2c3d4e5_photo2.jpg",
"totalPhotos": 1,
"removedUrl": "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo.photo1.jpg"
}
}
Success Response Fields:
Field
Description
profilePhotoUrls
Updated list after removal
primaryPhotoUrl
Updated primary (auto-selects next if removed was primary)
totalPhotos
Updated total count
removedUrl
The URL that was removed
Error Response JSON Sample:
Photo Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Photo not found",
"action_time": "2025-01-02T10:30:45",
"data": "This photo URL is not in your profile"
}
6.
SetRemove PrimaryProfile PhotoSetRemove a specific photo asfrom the primary/mainuser's profile photo.photos array.PATCHDELETE {base_url}/profile/photo/primaryphoto (Requires valid access token)Request Headers:
Header
Type
Required
Description
Authorization
string
Yes
Bearer token: Bearer {accessToken}
Content-Type
string
Yes
Must be application/json
Request JSON Sample:
{
"photoUrl": "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg"
}
Request BodyQuery Parameters:
Parameter
Type
Required
Description
Validation
photoUrlstring
Yes
URL of photo to set as primary
Must exist in profilePhotoUrlsremove{
"success": true,
"httpStatus": "OK",
"message": "PrimaryPhoto photo updated"removed",
"action_time": "2025-01-02T10:30:45"05T10:55:00",
"data": {
"primaryPhotoUrl": "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg",
"profilePhotoUrls": [
"http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/a1b2c3d4_photo1.photo1.jpg"
],
"http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg"
]
}
}
Success Response Fields:
Field
Description
primaryPhotoUrl
The newly set primary photo URL
profilePhotoUrls
Complete list of all profile photos
Error Response JSON Sample:
Photo Not In Profile (400):
{
"success": false,
"httpStatus"primaryPhotoUrl": "BAD_REQUEST",https://files.fursahub.com/bucket/profile/photo1.jpg"
"message": "Photo not in profile",
"action_time": "2025-01-02T10:30:45",
"data": "Cannot set primary: This photo URL is not in your profile photo list"}
}
StandardFrontend ErrorImplementation TypesGuide
Application-Level Exceptions (400-499)
Status Code
HTTP Status
When It Occurs
400
BAD_REQUEST
Invalid input data, invalid gender/username format
401
UNAUTHORIZED
Token empty, invalid, or expired
404
NOT_FOUND
Photo URL not found in profile
409
CONFLICT
Username already taken, photo already exists
422
UNPROCESSABLE_ENTITY
Validation errors with field details
Server-Level Exceptions (500+)
Status Code
HTTP Status
When It Occurs
500
INTERNAL_SERVER_ERROR
Database errors, unexpected failures
Quick Reference
Username Rules
Rule
Requirement
Length
3-30 characters
Characters
Letters (a-z, A-Z), numbers (0-9), underscore (_)
Uniqueness
Must be unique across all users
Case
Case-insensitive for uniqueness check
Profile
Field Limits
Field
Max Length
Required for Onboarding
fullName
100 chars
✅ Yes
username
30 chars
✅ Yes
bio
500 chars
✅ Yes
gender
-
❌ No
link
500 chars
❌ No
Auto-Completion TriggerScreen
Onboarding completes automatically when:fullName != null AND
username != null AND
bio != null ANDWhen 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 = PENDING_PROFILE_COMPLETIONtrue → 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