# User-profile-nexgate-service(5)

# User Profile Management Service

**Author**: Josh S. Sakweli, Backend Lead Team  
**Last Updated**: 2026-05-19  
**Version**: v1.0

**Base URL**: `{base_url}/api/v1/profile`

**Short Description**: The Profile API manages a user's social presence — what others see when they visit your page. This includes viewing your own full profile, looking up other users by ID or username, and updating your display information such as name, bio, location, and profile pictures.

**Why Account and Profile Are Separate**:

> **Profile** = how you *appear* to others (display name, bio, location, picture, social stats). **Account** = who you *are* in the system (credentials, security, identity verification).
> 
> Profile operations are social in nature — some endpoints (`/id/{userId}`, `/u/{username}`) are intentionally viewable by any authenticated user, not just the owner. Account operations carry security implications and are always strictly private. By separating the two, we can apply the right access rules to each: profile data can be shared, account data cannot. Anything that touches credentials (password, 2FA, email, phone) belongs in the Account API, not here.

**Hints**:

- `GET /me` requires authentication; it returns private fields like `email`, `phoneNumber`, and `securityStrength` only visible to the account owner
- `GET /id/{userId}` and `GET /u/{username}` work for both authenticated and unauthenticated callers — authenticated callers additionally receive `relationship` info (isFollowing, isBlocked, etc.)
- `PUT /update` only accepts social/display fields — to change username use `POST /api/v1/account/username/change`; to change email or phone use the Account Linking API
- Profile picture URLs must be publicly accessible `https://` image URLs ending in `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`
- Maximum 5 profile picture URLs per account

---

## Standard Response Format

### Success Response Structure

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": {}
}

```

### Error Response Structure

```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-09-23T10:30:45",
  "data": "Error description"
}

```

### Standard Response Fields

<table id="bkmrk-field-type-descripti"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>`success`</td><td>boolean</td><td>`true` for successful operations, `false` for errors</td></tr><tr><td>`httpStatus`</td><td>string</td><td>HTTP status name (OK, BAD\_REQUEST, NOT\_FOUND, etc.)</td></tr><tr><td>`message`</td><td>string</td><td>Human-readable message describing the result</td></tr><tr><td>`action_time`</td><td>string</td><td>ISO 8601 timestamp of when the response was generated</td></tr><tr><td>`data`</td><td>object/string</td><td>Response payload on success, error details on failure</td></tr></tbody></table>

### Standard Error Types

- `400 BAD_REQUEST`: Invalid request data or business rule violation
- `401 UNAUTHORIZED`: Missing, expired, or invalid JWT token
- `404 NOT_FOUND`: User not found
- `422 UNPROCESSABLE_ENTITY`: Validation errors with field-level detail
- `500 INTERNAL_SERVER_ERROR`: Unexpected server error

---

## Endpoints

## 1. Get My Profile

**Purpose**: Returns the full profile of the currently authenticated user, including private fields (email, phone, verification status, security strength) and social stats (followers, posts, shops, events).

**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}/api/v1/profile/me`

**Access Level**: 🔒 Protected (Requires valid JWT — own profile only)

**Authentication**: Bearer Token

**Request Headers**:

<table id="bkmrk-header-type-required"><thead><tr><th>Header</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>`Authorization`</td><td>string</td><td>Yes</td><td>`Bearer <jwt_token>`</td></tr></tbody></table>

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Profile retrieved successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "userName": "john_doe",
    "firstName": "John",
    "lastName": "Doe",
    "middleName": null,
    "email": "john@example.com",
    "phoneNumber": "+254712345678",
    "bio": "Building cool things.",
    "location": "Nairobi, Kenya",
    "profilePictureUrls": [
      "https://cdn.example.com/profiles/john_main.jpg"
    ],
    "isVerified": true,
    "isEmailVerified": true,
    "isPhoneVerified": true,
    "isTwoFactorEnabled": false,
    "isAccountLocked": false,
    "followersCount": 1420,
    "followingCount": 310,
    "postsCount": 42,
    "shopsCount": 1,
    "eventsCount": 3,
    "createdAt": "2025-12-01T14:00:00",
    "editedAt": "2026-05-10T08:22:00",
    "roles": ["ROLE_USER"],
    "securityStrength": {
      "score": 50,
      "level": "MEDIUM",
      "description": "Your account security is good but can be improved",
      "recommendations": ["Enable two-factor authentication"]
    }
  }
}

```

**Success Response Fields**:

<table id="bkmrk-field-description-id"><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td>`id`</td><td>Account UUID</td></tr><tr><td>`userName`</td><td>Public username</td></tr><tr><td>`firstName`</td><td>First name</td></tr><tr><td>`lastName`</td><td>Last name</td></tr><tr><td>`middleName`</td><td>Middle name (may be null)</td></tr><tr><td>`email`</td><td>Linked email address (private — only visible to owner)</td></tr><tr><td>`phoneNumber`</td><td>Linked phone number (private — only visible to owner)</td></tr><tr><td>`bio`</td><td>Profile bio text</td></tr><tr><td>`location`</td><td>Location string</td></tr><tr><td>`profilePictureUrls`</td><td>Array of profile picture URLs (first is primary)</td></tr><tr><td>`isVerified`</td><td>Whether the account has been verified</td></tr><tr><td>`isEmailVerified`</td><td>Whether the email has been verified</td></tr><tr><td>`isPhoneVerified`</td><td>Whether the phone has been verified</td></tr><tr><td>`isTwoFactorEnabled`</td><td>Whether 2FA is active</td></tr><tr><td>`isAccountLocked`</td><td>Whether the account is deactivated or locked</td></tr><tr><td>`followersCount`</td><td>Number of accepted followers</td></tr><tr><td>`followingCount`</td><td>Number of accounts the user is following</td></tr><tr><td>`postsCount`</td><td>Number of published posts</td></tr><tr><td>`shopsCount`</td><td>Number of active shops</td></tr><tr><td>`eventsCount`</td><td>Number of published events</td></tr><tr><td>`createdAt`</td><td>ISO 8601 account creation timestamp</td></tr><tr><td>`editedAt`</td><td>ISO 8601 last profile update timestamp</td></tr><tr><td>`roles`</td><td>Array of assigned role strings</td></tr><tr><td>`securityStrength.score`</td><td>Security health score 0–100</td></tr><tr><td>`securityStrength.level`</td><td>`VERY_WEAK`, `WEAK`, `MEDIUM`, or `STRONG`</td></tr><tr><td>`securityStrength.recommendations`</td><td>Actionable steps to improve security score</td></tr></tbody></table>

**Error Response JSON Sample**:

```json
{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Token has expired",
  "action_time": "2026-05-19T10:30:45",
  "data": "Token has expired"
}

```

---

## 2. Get Profile by User ID

**Purpose**: Returns the public profile summary of any user by their UUID. Authenticated callers additionally receive relationship context (isFollowing, isBlocked, etc.).

**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}/api/v1/profile/id/{userId}`

**Access Level**: 🌐 Public (Relationship info available when authenticated)

**Authentication**: Bearer Token (optional)

**Request Headers**:

<table id="bkmrk-header-type-required-1"><thead><tr><th>Header</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>`Authorization`</td><td>string</td><td>No</td><td>`Bearer <jwt_token>` — include to get relationship data</td></tr></tbody></table>

**Path Parameters**:

<table id="bkmrk-parameter-type-requi"><thead><tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th><th>Validation</th></tr></thead><tbody><tr><td>`userId`</td><td>UUID</td><td>Yes</td><td>The UUID of the user whose profile to fetch</td><td>Valid UUID format</td></tr></tbody></table>

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Profile retrieved successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "userId": "550e8400-e29b-41d4-a716-446655440000",
    "userName": "john_doe",
    "firstName": "John",
    "lastName": "Doe",
    "profilePictureUrl": "https://cdn.example.com/profiles/john_main.jpg",
    "bio": "Building cool things.",
    "location": "Nairobi, Kenya",
    "isVerified": true,
    "followersCount": 1420,
    "followingCount": 310,
    "postsCount": 42,
    "shopsCount": 1,
    "eventsCount": 3,
    "createdAt": "2025-12-01T14:00:00",
    "isOwnProfile": false,
    "relationship": {
      "isFollowing": true,
      "isFollowedBy": false,
      "isBlocked": false,
      "isBlockedBy": false
    }
  }
}

```

**Success Response Fields**:

<table id="bkmrk-field-description-us"><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td>`userId`</td><td>Account UUID</td></tr><tr><td>`userName`</td><td>Public username</td></tr><tr><td>`firstName`</td><td>First name</td></tr><tr><td>`lastName`</td><td>Last name</td></tr><tr><td>`profilePictureUrl`</td><td>Primary profile picture URL (first in the list, may be null)</td></tr><tr><td>`bio`</td><td>Profile bio</td></tr><tr><td>`location`</td><td>Location string</td></tr><tr><td>`isVerified`</td><td>Whether the account has been platform-verified</td></tr><tr><td>`followersCount`</td><td>Number of accepted followers</td></tr><tr><td>`followingCount`</td><td>Number of accounts the user follows</td></tr><tr><td>`postsCount`</td><td>Number of published posts</td></tr><tr><td>`shopsCount`</td><td>Number of active shops</td></tr><tr><td>`eventsCount`</td><td>Number of published events</td></tr><tr><td>`createdAt`</td><td>ISO 8601 account creation timestamp</td></tr><tr><td>`isOwnProfile`</td><td>`true` if the authenticated caller is viewing their own profile</td></tr><tr><td>`relationship`</td><td>Present only when the caller is authenticated and viewing someone else's profile</td></tr><tr><td>`relationship.isFollowing`</td><td>`true` if the caller follows this user</td></tr><tr><td>`relationship.isFollowedBy`</td><td>`true` if this user follows the caller</td></tr><tr><td>`relationship.isBlocked`</td><td>`true` if the caller has blocked this user</td></tr><tr><td>`relationship.isBlockedBy`</td><td>`true` if this user has blocked the caller</td></tr></tbody></table>

**Error Response JSON Sample**:

```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "User not found",
  "action_time": "2026-05-19T10:30:45",
  "data": "User not found"
}

```

---

## 3. Get Profile by Username

**Purpose**: Returns the public profile summary of any user by their username. Authenticated callers additionally receive relationship context. Functionally identical to endpoint 2 but accessed via username instead of UUID.

**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}/api/v1/profile/u/{username}`

**Access Level**: 🌐 Public (Relationship info available when authenticated)

**Authentication**: Bearer Token (optional)

**Request Headers**:

<table id="bkmrk-header-type-required-2"><thead><tr><th>Header</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>`Authorization`</td><td>string</td><td>No</td><td>`Bearer <jwt_token>` — include to get relationship data</td></tr></tbody></table>

**Path Parameters**:

<table id="bkmrk-parameter-type-requi-1"><thead><tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th><th>Validation</th></tr></thead><tbody><tr><td>`username`</td><td>string</td><td>Yes</td><td>The public username to look up</td><td>Supports `@username` format</td></tr></tbody></table>

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Profile retrieved successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "userId": "550e8400-e29b-41d4-a716-446655440000",
    "userName": "john_doe",
    "firstName": "John",
    "lastName": "Doe",
    "profilePictureUrl": "https://cdn.example.com/profiles/john_main.jpg",
    "bio": "Building cool things.",
    "location": "Nairobi, Kenya",
    "isVerified": true,
    "followersCount": 1420,
    "followingCount": 310,
    "postsCount": 42,
    "shopsCount": 1,
    "eventsCount": 3,
    "createdAt": "2025-12-01T14:00:00",
    "isOwnProfile": false,
    "relationship": {
      "isFollowing": false,
      "isFollowedBy": false,
      "isBlocked": false,
      "isBlockedBy": false
    }
  }
}

```

**Success Response Fields**: Same as endpoint 2 — see [Get Profile by User ID](#2-get-profile-by-user-id).

**Error Response JSON Sample**:

```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "User not found",
  "action_time": "2026-05-19T10:30:45",
  "data": "User not found"
}

```

---

## 4. Update Profile

**Purpose**: Updates the authenticated user's social/display information. Only accepts social fields — username changes, email changes, and phone changes are handled by the Account API. All fields are optional; only fields provided will be updated.

**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}/api/v1/profile/update`

**Access Level**: 🔒 Protected (Requires valid JWT — own profile only)

**Authentication**: Bearer Token

**Request Headers**:

<table id="bkmrk-header-type-required-3"><thead><tr><th>Header</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>`Authorization`</td><td>string</td><td>Yes</td><td>`Bearer <jwt_token>`</td></tr><tr><td>`Content-Type`</td><td>string</td><td>Yes</td><td>`application/json`</td></tr></tbody></table>

**Request JSON Sample**:

```json
{
  "firstName": "John",
  "lastName": "Doe",
  "middleName": "K",
  "bio": "Building cool things one commit at a time.",
  "location": "Nairobi, Kenya",
  "profilePictureUrls": [
    "https://cdn.example.com/profiles/john_main.jpg",
    "https://cdn.example.com/profiles/john_alt.jpg"
  ]
}

```

**Request Body Parameters**:

<table id="bkmrk-parameter-type-requi-2"><thead><tr><th>Parameter</th><th>Type</th><th>Required</th><th>Description</th><th>Validation</th></tr></thead><tbody><tr><td>`firstName`</td><td>string</td><td>No</td><td>First name</td><td>1–30 characters</td></tr><tr><td>`lastName`</td><td>string</td><td>No</td><td>Last name</td><td>1–30 characters</td></tr><tr><td>`middleName`</td><td>string</td><td>No</td><td>Middle name</td><td>Max 30 characters</td></tr><tr><td>`bio`</td><td>string</td><td>No</td><td>Short bio shown on profile</td><td>No length constraint enforced here</td></tr><tr><td>`location`</td><td>string</td><td>No</td><td>Location string shown on profile</td><td>No length constraint enforced here</td></tr><tr><td>`profilePictureUrls`</td><td>array of strings</td><td>No</td><td>Ordered list of image URLs</td><td>Max 5 items; each URL must be a valid `https://` image URL ending in `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`; max 500 characters per URL</td></tr></tbody></table>

> **Note**: To change your username, use `POST /api/v1/account/username/change`. To change your email or phone number, use the Account Linking API (`/api/v1/account/link`).

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Profile updated successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "userName": "john_doe",
    "firstName": "John",
    "lastName": "Doe",
    "middleName": "K",
    "email": "john@example.com",
    "phoneNumber": "+254712345678",
    "bio": "Building cool things one commit at a time.",
    "location": "Nairobi, Kenya",
    "profilePictureUrls": [
      "https://cdn.example.com/profiles/john_main.jpg",
      "https://cdn.example.com/profiles/john_alt.jpg"
    ],
    "isVerified": true,
    "isEmailVerified": true,
    "isPhoneVerified": true,
    "isTwoFactorEnabled": false,
    "isAccountLocked": false,
    "createdAt": "2025-12-01T14:00:00",
    "editedAt": "2026-05-19T10:30:45",
    "roles": ["ROLE_USER"],
    "securityStrength": {
      "score": 50,
      "level": "MEDIUM",
      "description": "Your account security is good but can be improved",
      "recommendations": ["Enable two-factor authentication"]
    }
  }
}

```

**Success Response Fields**:

<table id="bkmrk-field-description-id-1"><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td>`id`</td><td>Account UUID</td></tr><tr><td>`userName`</td><td>Public username (unchanged by this endpoint)</td></tr><tr><td>`firstName`</td><td>Updated first name</td></tr><tr><td>`lastName`</td><td>Updated last name</td></tr><tr><td>`middleName`</td><td>Updated middle name (may be null)</td></tr><tr><td>`email`</td><td>Current email (unchanged by this endpoint)</td></tr><tr><td>`phoneNumber`</td><td>Current phone number (unchanged by this endpoint)</td></tr><tr><td>`bio`</td><td>Updated bio</td></tr><tr><td>`location`</td><td>Updated location</td></tr><tr><td>`profilePictureUrls`</td><td>Updated list of profile picture URLs</td></tr><tr><td>`isVerified`</td><td>Account verification status</td></tr><tr><td>`isEmailVerified`</td><td>Email verification status</td></tr><tr><td>`isPhoneVerified`</td><td>Phone verification status</td></tr><tr><td>`isTwoFactorEnabled`</td><td>2FA status</td></tr><tr><td>`isAccountLocked`</td><td>Account lock status</td></tr><tr><td>`createdAt`</td><td>Account creation timestamp</td></tr><tr><td>`editedAt`</td><td>Updated timestamp reflecting this change</td></tr><tr><td>`roles`</td><td>Assigned role strings</td></tr><tr><td>`securityStrength`</td><td>Current security score and recommendations</td></tr></tbody></table>

**Error Response JSON Sample**:

```json
{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "firstName": "First name must be between 1 and 30 characters",
    "profilePictureUrls": "Maximum 5 profile pictures allowed"
  }
}

```

---

## Quick Reference — All Profile Endpoints

<table id="bkmrk-%23-method-endpoint-au"><thead><tr><th>\#</th><th>Method</th><th>Endpoint</th><th>Auth</th><th>Description</th></tr></thead><tbody><tr><td>1</td><td><span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span></td><td>`/profile/me`</td><td>🔒 Required</td><td>Full profile — own account only</td></tr><tr><td>2</td><td><span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span></td><td>`/profile/id/{userId}`</td><td>🌐 Optional</td><td>Public profile by UUID</td></tr><tr><td>3</td><td><span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span></td><td>`/profile/u/{username}`</td><td>🌐 Optional</td><td>Public profile by username</td></tr><tr><td>4</td><td><span style="background-color: #ffc107; color: black; padding: 2px 6px; border-radius: 3px; font-size: 11px;">PUT</span></td><td>`/profile/update`</td><td>🔒 Required</td><td>Update display info</td></tr></tbody></table>

---

## What Belongs in Profile vs Account

<table id="bkmrk-i-want-to%E2%80%A6-use-view-"><thead><tr><th>I want to…</th><th>Use</th></tr></thead><tbody><tr><td>View my full profile</td><td>`GET /profile/me`</td></tr><tr><td>View someone else's profile</td><td>`GET /profile/u/{username}` or `GET /profile/id/{id}`</td></tr><tr><td>Update my name, bio, location, or picture</td><td>`PUT /profile/update`</td></tr><tr><td>Change my username</td><td>`POST /api/v1/account/username/change`</td></tr><tr><td>Change my email address</td><td>`POST /api/v1/account/link/email/initiate`</td></tr><tr><td>Change my phone number</td><td>Phone OTP verification flow</td></tr><tr><td>Change my password</td><td>`POST /api/v1/account/password/change`</td></tr><tr><td>Enable or disable 2FA</td><td>`POST /api/v1/account/2fa/enable` or `/disable`</td></tr><tr><td>Verify my email</td><td>`POST /api/v1/account/email/request-verification` → `/email/verify`</td></tr><tr><td>View my security status</td><td>`GET /api/v1/account/security-info`</td></tr><tr><td>Deactivate my account</td><td>`DELETE /api/v1/account/deactivate`</td></tr></tbody></table>