Skip to main content

Profile

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

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 when fullName, username, and bio are all set
  • Usernames must be unique, 3-30 characters, alphanumeric with underscores only
  • Profile photos are stored as a list (JSONB) - users can have multiple photos
  • One photo can be set as "primary" for display purposes
  • Gender 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
    registration)
  1. User reaches profile└── stepfullName after(from completingGoogle/Apple phoneaccount) verification and preferences
  2. └──
  3. UserprofilePhotoUrl updates(from profilesocial account) │ │ └── email (from auth provider) │ │ │ │ 2. REGISTRATION (user choice) │ │ └── preferredLanguage │ │ └── theme │ │ │ │ 3. ONBOARDING (user completes) │ │ └── phoneNumber (verified via PUTOTP) /api/v1/profile with required└── fields
  4. Backend auto-detects completion when fullName, username, and bio are all set
  5. Onboarding status updated to COMPLETED automatically
  6. User redirected to main app/dashboard

Profile Photo Flow

  1. Upload photo via File Management APIpreferences (POSTfrom /api/v1/files/upload-singleonboarding withpages) directory │ │ │ 4. PROFILE)
  2. AddCOMPLETION photo(final URLstep) to profile via└── POSTusername /api/v1/profile/photo
  3. (required,
  4. Setunique) primary photo via└── PATCHbio /api/v1/profile/photo/primary(required) │ │ └── fullName (can edit Firebase default) │ │ └── gender, link (optional)
  5. Remove photo via DELETE /api/v1/profile/photoWhen user completes: fullName + username + bio │ │ └── Onboarding auto-completes if needed
  6. at
PENDING_PROFILE_COMPLETION

Username Selection Flow

  1. Check availability via GET /api/v1/profile/username/check?username=desired_name
  2. If available: Proceed with profile update
  3. If 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 token: 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:

auth provider:
Field Description
id Unique user identifier (UUID)
email User's email address
username Unique username (3-30 chars, alphanumeric + underscore)lowercase)
phoneNumber Verified phone number in E.164 format
fullName User's displayDisplay name
bio User'sUser biography/professional summary (max 500 chars)description
gender Gender: MALE, FEMALE, OTHER, PREFER_NOT_TO_SAY, or nullFEMALE
link Portfolio/Personal website or social media URLlink
profilePhotoUrls Array of all profile photo URLs (JSONB list)
primaryPhotoUrl Currently selected primaryFirst photo URL (main profile picture)
isPhoneVerified Phone verification status
isEmailVerified Email verification status
preferredLanguage Language preferencecode code(en, sw, fr, zh)
themeauthProviderLIGHT, DARK, or SYSTEM
OriginalauthProvider GOOGLE, APPLE, or EMAIL
role User role in(ROLE_USER, theROLE_ADMIN, systemetc.)
onboardingStatus Current onboarding step
isOnboardingComplete Booleantrue flag forif onboarding completion
createdAtAccount creation timestamp
updatedAtLast profile update timestampfinished

Error Response JSON Sample:

Unauthorized (401):

{
  "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 token: Bearer {accessToken}
Content-Type string Yes Must be 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:

max500characters
Parameter Type Required Description Validation
fullNamestringNo*User's display name2-100 characters
usernamestringNo*Unique username3-30 chars, alphanumeric + underscore, must be unique
biostringNo*Biography/professional summaryMax 500 characters
gender string No User'sDisplay gendername Enum:Min: MALE,2, FEMALE,Max: OTHER,100 PREFER_NOT_TO_SAYchars
linkusername string No Portfolio/socialUnique URLusername ValidMin: 3, Max: 30 chars, alphanumeric + underscore only
biostringNoUser biographyMax: 500 chars
genderstringNoUser genderenum: MALE, FEMALE
linkstringNoPersonal/social linkMust be valid URL format,(https://...)
profilePhotoUrlsarrayNoList of photo URLsArray of strings
themestringNoUI theme preferenceenum: LIGHT, DARK, SYSTEM
preferredLanguagestringNoLanguage preferenceMust be active language code

*Required for onboarding completion

Auto-Completion Logic: When the following conditions are met, onboarding automatically completes:

  • fullName is not null/empty
  • username is not null/empty
  • bio is not null/empty
  • Current onboardingStatus is PENDING_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:

FieldDescription
onboardingStatusWill change to COMPLETED if auto-completion triggered
isOnboardingCompleteWill be true if auto-completion triggered
updatedAtTimestamp of this update
(other fields)Same as Get Profile response

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

Invalid


Gender

3. ValueUpdate Theme (400)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:

ParameterTypeRequiredDescriptionValidation
themestringYesTheme preferenceenum: LIGHT, DARK, SYSTEM

Success Response JSON Sample:

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

Purpose: Check if a username is available before updating profile.

Endpoint: GET {base_url}/profile/username/check

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token: Bearer {accessToken}

Query Parameters:

Parameter Type Required Description Validation
username string Yes Username to check 3-30Min: chars,3 alphanumeric + underscorechars

Success Response JSON Sample (Available):

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

Success Response JSON Sample (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:

FieldDescription
usernameThe username that was checked
availabletrue 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 Photo

Purpose: Add a new photo URL to the user's profile photophotos list. Photo must be uploaded first via File Management API.array.

Endpoint: POST {base_url}/profile/photo

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token: Bearer {accessToken}
Content-TypestringYesMust be application/json

Request JSON Sample:

{
  "photoUrl": "http:https://localhost:9000/fursa-550e8400.../files.fursahub.com/bucket/profile/a1b2c3d4_photo.new-photo.jpg"
}

Request Body Parameters:

Parameter Type Required Description Validation
photoUrl string Yes URL of uploaded photo Valid URL format

Photo Upload Flow:

  1. First upload photo via POST /api/v1/files/upload-single with directory=PROFILE
  2. Get permanentUrl from upload response
  3. 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:

FieldDescription
profilePhotoUrlsUpdated list of all profile photo URLs
primaryPhotoUrlCurrent primary photo (auto-set to first if none selected)
totalPhotosTotal 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:

valid
HeaderTypeRequiredDescription
AuthorizationstringYesBearer token: Bearer {accessToken}
Content-TypestringYesMust be application/json

Request JSON Sample:

{
  "photoUrl": "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo.jpg"
}

Request Body Parameters:

ParameterTypeRequiredDescriptionValidation
photoUrlstringYesURL of photo to removeMust exist in profile

Success Response JSON Sample:

{
  "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:

FieldDescription
profilePhotoUrlsUpdated list after removal
primaryPhotoUrlUpdated primary (auto-selects next if removed was primary)
totalPhotosUpdated total count
removedUrlThe 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 Photo

Purpose: SetRemove a specific photo asfrom the primary/mainuser's profile photo.photos array.

Endpoint: PATCHDELETE {base_url}/profile/photo/primaryphoto

Access Level: 🔒 Protected (Requires valid access token)

Authentication: Bearer Token

Request Headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token: Bearer {accessToken}
Content-TypestringYesMust be application/json

Request JSON Sample:

{
  "photoUrl": "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg"
}

Request BodyQuery Parameters:

Parameter Type Required Description Validation
photoUrl string Yes URL of photo to set as primaryMust exist in profilePhotoUrlsremove

Success Response JSON Sample:

{
  "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:

FieldDescription
primaryPhotoUrlThe newly set primary photo URL
profilePhotoUrlsComplete 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 CodeHTTP StatusWhen It Occurs
400BAD_REQUESTInvalid input data, invalid gender/username format
401UNAUTHORIZEDToken empty, invalid, or expired
404NOT_FOUNDPhoto URL not found in profile
409CONFLICTUsername already taken, photo already exists
422UNPROCESSABLE_ENTITYValidation errors with field details

Server-Level Exceptions (500+)

Status CodeHTTP StatusWhen It Occurs
500INTERNAL_SERVER_ERRORDatabase errors, unexpected failures

Quick Reference

Username Rules

RuleRequirement
Length3-30 characters
CharactersLetters (a-z, A-Z), numbers (0-9), underscore (_)
UniquenessMust be unique across all users
CaseCase-insensitive for uniqueness check

Profile Field Limits

FieldMax LengthRequired for Onboarding
fullName100 chars✅ Yes
username30 chars✅ Yes
bio500 chars✅ Yes
gender-❌ No
link500 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