Skip to main content

Profile

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

Base URL: https://api.fursahub.com/api/v1

Short Description: The Profile API manages user profile information for Fursa Hub. It handles profile retrieval, updates, username validation, and profile photo management. The API features automatic onboarding completion detection when required profile fields are populated.

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, FEMALE, OTHER, PREFER_NOT_TO_SAY
  • All profile endpoints require authentication

Profile Management Flow

Profile Completion (During Onboarding)

  1. User reaches profile step after completing phone verification and preferences
  2. User updates profile via PUT /api/v1/profile with required fields
  3. Backend auto-detects completion when fullName, username, and bio are all set
  4. Onboarding status updated to COMPLETED automatically
  5. User redirected to main app/dashboard

Profile Photo Flow

  1. Upload photo via File Management API (POST /api/v1/files/upload-single with directory PROFILE)
  2. Add photo URL to profile via POST /api/v1/profile/photo
  3. Set primary photo via PATCH /api/v1/profile/photo/primary (optional)
  4. Remove photo via DELETE /api/v1/profile/photo if needed

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 authenticated 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",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "john.doe@gmail.com",
    "username": "johndoe",
    "phoneNumber": "+255712345678",
    "fullName": "John Doe",
    "bio": "Passionate software developer with 5+ years of experience in mobile and web development. Looking for exciting opportunities in East Africa's tech ecosystem.",
    "gender": "MALE",
    "link": "https://linkedin.com/in/johndoe",
    "profilePhotoUrls": [
      "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo1.jpg",
      "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg"
    ],
    "primaryPhotoUrl": "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo1.jpg",
    "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"
  }
}

Success Response Fields:

Field Description
id Unique user identifier (UUID)
email User's email address
username Unique username (3-30 chars, alphanumeric + underscore)
phoneNumber Verified phone number in E.164 format
fullName User's display name
bio User's biography/professional summary (max 500 chars)
gender Gender: MALE, FEMALE, OTHER, PREFER_NOT_TO_SAY, or null
link Portfolio/social media URL
profilePhotoUrls Array of profile photo URLs (JSONB list)
primaryPhotoUrl Currently selected primary photo URL
isPhoneVerified Phone verification status
isEmailVerified Email verification status
preferredLanguage Language preference code
authProvider Original auth provider: GOOGLE, APPLE, EMAIL
role User role in the system
onboardingStatus Current onboarding step
isOnboardingComplete Boolean flag for onboarding completion
createdAt Account creation timestamp
updatedAt Last profile update timestamp

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. Automatically completes onboarding when required fields (fullName, username, bio) are all set.

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",
  "username": "johndoe",
  "bio": "Passionate software developer with 5+ years of experience in mobile and web development.",
  "gender": "MALE",
  "link": "https://linkedin.com/in/johndoe"
}

Request Body Parameters:

Parameter Type Required Description Validation
fullName string No* User's display name 2-100 characters
username string No* Unique username 3-30 chars, alphanumeric + underscore, must be unique
bio string No* Biography/professional summary Max 500 characters
gender string No User's gender Enum: MALE, FEMALE, OTHER, PREFER_NOT_TO_SAY
link string No Portfolio/social URL Valid URL format, max 500 characters

*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",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "john.doe@gmail.com",
    "username": "johndoe",
    "phoneNumber": "+255712345678",
    "fullName": "John Doe",
    "bio": "Passionate software developer with 5+ years of experience in mobile and web development.",
    "gender": "MALE",
    "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"
  }
}

Success Response Fields:

Field Description
onboardingStatus Will change to COMPLETED if auto-completion triggered
isOnboardingComplete Will be true if auto-completion triggered
updatedAt Timestamp of this update
(other fields) Same as Get Profile response

Error Response JSON Samples:

Username Already Taken (409):

{
  "success": false,
  "httpStatus": "CONFLICT",
  "message": "Username already taken",
  "action_time": "2025-01-02T10:30:45",
  "data": "Username 'johndoe' is already in use"
}

Validation Error (422):

{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "username": "Username must be 3-30 characters, alphanumeric and underscores only",
    "bio": "Bio must not exceed 500 characters",
    "link": "Must be a valid URL"
  }
}

Invalid Gender Value (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Invalid gender value",
  "action_time": "2025-01-02T10:30:45",
  "data": "Invalid gender: UNKNOWN. Valid values: MALE, FEMALE, OTHER, PREFER_NOT_TO_SAY"
}

3. 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:

Header Type Required Description
Authorization string Yes Bearer token: Bearer {accessToken}

Query Parameters:

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

Success Response JSON Sample (Available):

{
  "success": true,
  "httpStatus": "OK",
  "message": "Username is available",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "username": "johndoe_new",
    "available": true
  }
}

Success Response JSON Sample (Not Available):

{
  "success": true,
  "httpStatus": "OK",
  "message": "Username is not available",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "username": "johndoe",
    "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. Add Profile Photo

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

Endpoint: POST {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 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 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:

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

{
  "success": true,
  "httpStatus": "OK",
  "message": "Photo removed successfully",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "profilePhotoUrls": [
      "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg"
    ],
    "primaryPhotoUrl": "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg",
    "totalPhotos": 1,
    "removedUrl": "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_photo.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. Set Primary Photo

Purpose: Set a specific photo as the primary/main profile photo.

Endpoint: PATCH {base_url}/profile/photo/primary

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:

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

Request Body Parameters:

Parameter Type Required Description Validation
photoUrl string Yes URL of photo to set as primary Must exist in profilePhotoUrls

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Primary photo updated",
  "action_time": "2025-01-02T10:30:45",
  "data": {
    "primaryPhotoUrl": "http://localhost:9000/fursa-550e8400.../profile/b2c3d4e5_photo2.jpg",
    "profilePhotoUrls": [
      "http://localhost:9000/fursa-550e8400.../profile/a1b2c3d4_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": "BAD_REQUEST",
  "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"
}

Standard Error Types

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 Trigger

Onboarding completes automatically when:

fullName != null AND
username != null AND
bio != null AND
onboardingStatus == PENDING_PROFILE_COMPLETION