# Account Mng

# Account API Documentation

**Author**: Josh S. Sakweli, Backend Lead Team  
**Last Updated**: 2026-05-19  
**Version**: v1.0

**Base URL**: `{base_url}/api/v1/account`

**Short Description**: The Account API manages everything related to a user's identity, credentials, and security. This includes password management, two-factor authentication, email verification, session control, trusted devices, and linking external auth providers. All operations in this API are private — only the authenticated account owner can act on their own account.

**Why Account and Profile Are Separate**:

> **Account** = who you *are* in the system (credentials, security, identity verification).
> **Profile** = how you *appear* to others (display name, bio, location, picture).
>
> This separation exists for a clear reason: account operations carry security implications and are always private — only you can change your password, revoke a session, or deactivate your account. Profile operations are social and can be partially visible to others. Mixing them creates confusion about what is private vs public, and makes it harder to apply the right access control. The Account API is your security dashboard; the Profile API is your public face.

**Hints**:
- All endpoints except `GET /username/check`, `GET /username/search`, and `GET /username/{username}` require a valid Bearer JWT token
- `X-Session-Id` header (UUID) is required for session-specific operations
- Username checks are case-insensitive — all usernames are lowercased automatically
- OTP codes expire in 10 minutes unless otherwise stated
- User-selectable OTP channels are: `SMS`, `WHATSAPP`, `EMAIL`, `SMS_AND_WHATSAPP`

---

## 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
| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | `true` for successful operations, `false` for errors |
| `httpStatus` | string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
| `message` | string | Human-readable message describing the result |
| `action_time` | string | ISO 8601 timestamp of when the response was generated |
| `data` | object/string | Response payload on success, error details on failure |

### Standard Error Types
- `400 BAD_REQUEST`: Invalid request data, business rule violation, or item already exists
- `401 UNAUTHORIZED`: Missing, expired, or invalid JWT token
- `403 FORBIDDEN`: Verification failure (wrong password, wrong OTP)
- `404 NOT_FOUND`: Account or resource not found
- `422 UNPROCESSABLE_ENTITY`: Validation errors with field-level detail
- `500 INTERNAL_SERVER_ERROR`: Unexpected server error

---

# Security

## 1. Get Security Info

**Purpose**: Returns the authenticated user's account security overview including verification status, 2FA state, and a computed security strength score.

**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/account/security-info`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Security information retrieved successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "isEmailVerified": true,
    "isPhoneVerified": true,
    "isTwoFactorEnabled": false,
    "isAccountLocked": false,
    "lastPasswordChange": "2026-04-10T08:22:00",
    "accountCreatedAt": "2025-12-01T14:00: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**:
| Field | Description |
|-------|-------------|
| `isEmailVerified` | Whether the account's email has been verified |
| `isPhoneVerified` | Whether the account's phone number has been verified |
| `isTwoFactorEnabled` | Whether 2FA is currently active |
| `isAccountLocked` | Whether the account is locked or deactivated |
| `lastPasswordChange` | ISO 8601 timestamp of the last account edit |
| `accountCreatedAt` | ISO 8601 timestamp of account creation |
| `roles` | Array of role strings assigned to the account |
| `securityStrength.score` | Integer 0–100 representing security health |
| `securityStrength.level` | `VERY_WEAK`, `WEAK`, `MEDIUM`, or `STRONG` |
| `securityStrength.description` | Human-readable summary of the score |
| `securityStrength.recommendations` | List of actionable steps to improve score |

**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. Enable Two-Factor Authentication

**Purpose**: Enables 2FA on the authenticated user's account. Requires password confirmation as a security gate.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/2fa/enable`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "password": "MyStr0ngP@ss!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `password` | string | Yes | Current account password | Not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Two-factor authentication enabled successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Password is incorrect",
  "action_time": "2026-05-19T10:30:45",
  "data": "Password is incorrect"
}
```

---

## 3. Disable Two-Factor Authentication

**Purpose**: Disables 2FA on the authenticated user's account. Requires password confirmation.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/2fa/disable`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "password": "MyStr0ngP@ss!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `password` | string | Yes | Current account password | Not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Two-factor authentication disabled successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Two-factor authentication is not enabled",
  "action_time": "2026-05-19T10:30:45",
  "data": "Two-factor authentication is not enabled"
}
```

---

## 4. Deactivate Account

**Purpose**: Locks the authenticated user's account (soft deactivation). The account is not deleted — data is preserved and support can reactivate it. Requires password confirmation.

**Endpoint**: <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> `{base_url}/api/v1/account/deactivate`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "password": "MyStr0ngP@ss!",
  "reason": "Taking a break from the platform"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `password` | string | Yes | Current account password | Not blank |
| `reason` | string | No | Optional reason for deactivation | Max 500 characters |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Account deactivated successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Password is incorrect",
  "action_time": "2026-05-19T10:30:45",
  "data": "Password is incorrect"
}
```

---

# Password Management

## 5. Change Password

**Purpose**: Changes the account password. Supports two paths: provide `currentPassword` if the account already has one, or provide `otp` for passwordless accounts or if the user forgot their current password.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/password/change`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "currentPassword": "OldP@ss123!",
  "newPassword": "NewStr0ng@Pass!",
  "confirmPassword": "NewStr0ng@Pass!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `currentPassword` | string | Conditional | Required if account has a password | One of `currentPassword` or `otp` must be provided |
| `otp` | string | Conditional | OTP alternative for passwordless accounts | One of `currentPassword` or `otp` must be provided |
| `newPassword` | string | Yes | The new password | Min 8 characters, not blank |
| `confirmPassword` | string | Yes | Must match `newPassword` | Not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Password changed",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "success": true,
    "hadPassword": true,
    "message": "Password changed successfully"
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `success` | Always `true` on success |
| `hadPassword` | `true` if this was a change, `false` if it was a first-time set |
| `message` | Human-readable confirmation |

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "newPassword": "Password must be at least 8 characters"
  }
}
```

---

## 6. Send OTP for Password Change

**Purpose**: Sends an OTP to the user's verified phone/email to enable password change without knowing the current password. Returns a temp token for the verification step.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/password/change-with-otp/send-otp`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "channel": "SMS"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `channel` | string | Yes | Where to send the OTP | enum: `SMS`, `WHATSAPP`, `EMAIL`, `SMS_AND_WHATSAPP` |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "OTP sent",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiJ9...",
    "maskedValue": "+254***7890",
    "expiresIn": 600
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `tempToken` | Short-lived token to submit in the verify step |
| `maskedValue` | Masked destination (phone or email) the OTP was sent to |
| `expiresIn` | Seconds until the OTP expires |

---

## 7. Change Password with OTP

**Purpose**: Completes the OTP-based password change flow using the temp token and OTP from the previous step.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/password/change-with-otp/verify`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "tempToken": "eyJhbGciOiJIUzI1NiJ9...",
  "otp": "482910",
  "newPassword": "NewStr0ng@Pass!",
  "confirmPassword": "NewStr0ng@Pass!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `tempToken` | string | Yes | Token from the send-otp step | Not blank |
| `otp` | string | Yes | 6-digit OTP received | Exactly 6 characters |
| `newPassword` | string | Yes | New password | Min 8 characters, not blank |
| `confirmPassword` | string | Yes | Must match `newPassword` | Not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Password changed successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "success": true,
    "hadPassword": true,
    "message": "Password changed successfully"
  }
}
```

---

## 8. Check If Password Can Be Set

**Purpose**: Checks whether the authenticated user is eligible to set a password. Useful for passwordless accounts (OAuth-only) who want to add a password.

**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/account/password/can-set`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "You can set a password",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "canSetPassword": true,
    "authProvider": "GOOGLE"
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `canSetPassword` | `true` if the account does not yet have a password |
| `authProvider` | The current primary auth provider (EMAIL, GOOGLE, APPLE, etc.) |

---

## 9. Set Password (First Time)

**Purpose**: Sets a password for the first time on an account that currently has no password (e.g., a Google OAuth account). Use `/password/change` for subsequent changes.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/password/set`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "newPassword": "MyStr0ngP@ss!",
  "confirmPassword": "MyStr0ngP@ss!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `newPassword` | string | Yes | Password to set | Min 8 characters, not blank |
| `confirmPassword` | string | Yes | Must match `newPassword` | Not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Password set successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "success": true,
    "hadPassword": false,
    "message": "Password set successfully"
  }
}
```

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Password already set",
  "action_time": "2026-05-19T10:30:45",
  "data": "Password already set"
}
```

---

# Username Management

## 10. Check Username Availability

**Purpose**: Checks whether a given username is valid and available. Public endpoint — no auth required. Returns alternative suggestions when the username is taken.

**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/account/username/check`

**Access Level**: 🌐 Public

**Authentication**: None

**Query Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|---------|
| `username` | string | Yes | Username to check | Auto-lowercased |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Username available",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "username": "john_doe",
    "available": true,
    "suggestions": null
  }
}
```

**When Unavailable**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Username not available",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "username": "john_doe",
    "available": false,
    "suggestions": ["john_doe1", "john_doe99", "johndoe_"]
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `username` | The lowercased username that was checked |
| `available` | `true` if the username is both valid and not taken |
| `suggestions` | List of alternative usernames (only present when `available` is `false`) |

---

## 11. Check If Username Can Be Changed

**Purpose**: Checks whether the authenticated user is currently allowed to change their username (based on change frequency limits).

**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/account/username/can-change`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "You can change your username",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "canChange": true,
    "currentUsername": "john_doe"
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `canChange` | `true` if the user is allowed to change username now |
| `currentUsername` | The user's current public username |

---

## 12. Change Username

**Purpose**: Changes the authenticated user's public username. Subject to rate/frequency limits — check `/can-change` first. The change is recorded in the username history.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/username/change`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "username": "new_username"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `username` | string | Yes | Desired new username | Auto-lowercased; must be valid and available |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Username changed successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "oldUsername": "john_doe",
    "newUsername": "new_username"
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `oldUsername` | The previous username |
| `newUsername` | The newly assigned username |

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Username change limit reached",
  "action_time": "2026-05-19T10:30:45",
  "data": "Username change limit reached"
}
```

---

## 13. Search Users

**Purpose**: Full-text search for accounts by username or display name. Public endpoint. Supports pagination.

**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/account/username/search`

**Access Level**: 🌐 Public

**Authentication**: None

**Query Parameters**:
| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `q` | string | Yes | Search query (supports `@username` format) | Min 2 characters | — |
| `page` | integer | No | Zero-based page number | Min: 0 | 0 |
| `size` | integer | No | Results per page | Max: 20 | 4 |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Users found",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "users": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "userName": "john_doe",
        "displayName": "John Doe",
        "avatarUrl": "https://cdn.example.com/avatar.jpg"
      }
    ],
    "totalCount": 1,
    "hasMore": false
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `users` | Array of matching user objects |
| `users[].id` | Account UUID |
| `users[].userName` | Public username |
| `users[].displayName` | Display name (may be null) |
| `users[].avatarUrl` | First profile picture URL (may be null) |
| `totalCount` | Total number of matching results |
| `hasMore` | `true` if more pages exist |

---

## 14. Get User by Username

**Purpose**: Looks up a minimal user card by exact username. Returns only public-facing identity fields. Only works for active, non-locked accounts that have completed primary onboarding.

**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/account/username/{username}`

**Access Level**: 🌐 Public

**Authentication**: None

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `username` | string | Yes | Username to look up (supports `@username` format) | Auto-lowercased |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "User found",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "userName": "john_doe",
    "displayName": "John Doe",
    "avatarUrl": "https://cdn.example.com/avatar.jpg"
  }
}
```

**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"
}
```

---

# Sessions

## 15. Get Active Sessions

**Purpose**: Returns all active sessions for the authenticated account. Optionally marks the caller's current session when `X-Session-Id` is provided.

**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/account/sessions`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `X-Session-Id` | string (UUID) | No | Current session UUID to mark it in the response |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Sessions retrieved",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "sessions": [
      {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "deviceId": "device_abc123",
        "deviceName": "iPhone 15",
        "platform": "IOS",
        "ipAddress": "197.248.1.1",
        "location": "Nairobi, KE",
        "lastActiveAt": "2026-05-19T09:00:00",
        "createdAt": "2026-04-01T12:00:00",
        "currentSession": true
      }
    ],
    "totalCount": 1,
    "currentSession": { ... }
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `sessions` | Array of all active session objects |
| `sessions[].id` | Session UUID |
| `sessions[].deviceName` | Human-readable device name |
| `sessions[].platform` | Device platform (IOS, ANDROID, WEB, etc.) |
| `sessions[].ipAddress` | IP address the session was created from |
| `sessions[].location` | Approximate geographic location |
| `sessions[].lastActiveAt` | When this session last made a request |
| `sessions[].currentSession` | `true` if this is the caller's current session |
| `totalCount` | Total number of active sessions |
| `currentSession` | The caller's current session object (if `X-Session-Id` provided) |

---

## 16. Sign Out (Current Session)

**Purpose**: Invalidates the session identified by the `X-Session-Id` header.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/sessions/sign-out`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `X-Session-Id` | string (UUID) | Yes | The session to invalidate |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Signed out successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

---

## 17. Sign Out Other Devices

**Purpose**: Invalidates all sessions except the current one. Requires password or OTP confirmation as a security gate.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/sessions/sign-out-others`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `X-Session-Id` | string (UUID) | Yes | The session to KEEP (current session) |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "password": "MyStr0ngP@ss!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `password` | string | Conditional | Account password | One of `password` or `otp` required |
| `otp` | string | Conditional | OTP alternative | One of `password` or `otp` required |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Signed out of other devices",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

---

## 18. Sign Out All Devices

**Purpose**: Invalidates all active sessions including the current one. Requires password or OTP confirmation.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/sessions/sign-out-all`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "password": "MyStr0ngP@ss!"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `password` | string | Conditional | Account password | One of `password` or `otp` required |
| `otp` | string | Conditional | OTP alternative | One of `password` or `otp` required |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Signed out of all devices",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

---

## 19. Revoke Specific Session

**Purpose**: Revokes a specific session by its ID. Used to forcefully end a session from another device.

**Endpoint**: <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> `{base_url}/api/v1/account/sessions/{sessionId}`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `sessionId` | UUID | Yes | ID of the session to revoke | Valid UUID format |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Session revoked",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

---

# Devices

## 20. Verify Device

**Purpose**: Completes new device verification during login. When a login attempt is made from an unrecognized device, this endpoint finalizes the flow after the user confirms ownership with an OTP. Returns full auth tokens on success.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/device/verify`

**Access Level**: 🌐 Public (No JWT — called before full auth is established)

**Authentication**: None

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `User-Agent` | string | Yes | Device user-agent (auto-sent by clients) |

**Request JSON Sample**:
```json
{
  "deviceVerificationToken": "dvt_abc123...",
  "otp": "482910",
  "deviceId": "unique-device-fingerprint-id",
  "deviceName": "iPhone 15 Pro",
  "platform": "IOS"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `deviceVerificationToken` | string | Yes | Token issued during the login challenge step | Not blank |
| `otp` | string | Yes | 6-digit OTP sent to user for device verification | Exactly 6 numeric digits |
| `deviceId` | string | Yes | Unique client-side device fingerprint | Not blank |
| `deviceName` | string | No | Human-readable device name | — |
| `platform` | string | No | Device platform (IOS, ANDROID, WEB, etc.) | — |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Device verified. Login successful.",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
    "onboarding": {
      "isPrimaryComplete": true,
      "hasUsername": true
    },
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "userName": "john_doe",
      "firstName": "John"
    }
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `accessToken` | JWT access token for subsequent API calls |
| `refreshToken` | JWT refresh token for getting new access tokens |
| `onboarding` | Flags indicating what onboarding steps remain |
| `user` | Basic user info object |

---

## 21. Get Trusted Devices

**Purpose**: Returns the list of devices that have been verified and trusted on this account.

**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/account/devices`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `X-Device-Id` | string | No | Current device ID to mark it in the response |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Devices retrieved",
  "action_time": "2026-05-19T10:30:45",
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "deviceId": "unique-device-fingerprint-id",
      "deviceName": "iPhone 15 Pro",
      "platform": "IOS",
      "lastUsedAt": "2026-05-19T09:00:00",
      "createdAt": "2026-04-01T12:00:00"
    }
  ]
}
```

---

## 22. Revoke Device

**Purpose**: Removes a trusted device. The next login from that device will require device verification again.

**Endpoint**: <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> `{base_url}/api/v1/account/devices/{deviceKeyId}`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `deviceKeyId` | UUID | Yes | ID of the device key to revoke | Valid UUID format |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Device revoked",
  "action_time": "2026-05-19T10:30:45",
  "data": null
}
```

---

# Account Linking

## 23. Initiate Email Link

**Purpose**: Starts the process of linking an email address to the account. Sends a verification OTP to the provided email and returns a temp token for the verify step.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/link/email/initiate`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "email": "john@example.com"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `email` | string | Yes | Email address to link | Valid email format, not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Verification code sent",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "tempToken": "eyJhbGciOiJIUzI1NiJ9...",
    "maskedValue": "jo***@example.com",
    "expiresIn": 600,
    "linked": false,
    "message": "Verification code sent to jo***@example.com"
  }
}
```

**Success Response Fields**:
| Field | Description |
|-------|-------------|
| `tempToken` | Short-lived token to submit in the verify step |
| `maskedValue` | Masked email the OTP was sent to |
| `expiresIn` | Seconds until the OTP expires |

---

## 24. Verify and Link Email

**Purpose**: Completes the email linking process by submitting the OTP and temp token from the initiate step.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/link/email/verify`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "tempToken": "eyJhbGciOiJIUzI1NiJ9...",
  "otp": "482910"
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `tempToken` | string | Yes | Token from the initiate step | Not blank |
| `otp` | string | Yes | 6-digit OTP sent to the email | Exactly 6 characters |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Email linked successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "linked": true,
    "message": "Email linked successfully"
  }
}
```

---

## 25. Unlink Email

**Purpose**: Removes the linked email from the account.

**Endpoint**: <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> `{base_url}/api/v1/account/link/email`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Email unlinked",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "unlinked": true,
    "message": "Email unlinked successfully"
  }
}
```

---

## 26. Link OAuth Provider

**Purpose**: Links a Google or Apple account to the authenticated account. Useful for adding a social login option to an email/phone account.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `{base_url}/api/v1/account/link/oauth`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:
```json
{
  "provider": "GOOGLE",
  "idToken": "ya29.a0AfH6SMC..."
}
```

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `provider` | string | Yes | OAuth provider to link | enum: `GOOGLE`, `APPLE` |
| `idToken` | string | Yes | ID token from the provider's SDK | Not blank |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "GOOGLE linked successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "linked": true,
    "message": "GOOGLE linked successfully"
  }
}
```

---

## 27. Unlink OAuth Provider

**Purpose**: Removes a linked OAuth provider from the account.

**Endpoint**: <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> `{base_url}/api/v1/account/link/oauth/{provider}`

**Access Level**: 🔒 Protected (Requires valid JWT)

**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <jwt_token>` |

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `provider` | string | Yes | OAuth provider to unlink | `GOOGLE` or `APPLE` (case-insensitive) |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "GOOGLE unlinked",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "unlinked": true,
    "message": "GOOGLE unlinked successfully"
  }
}
```

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot unlink last auth provider",
  "action_time": "2026-05-19T10:30:45",
  "data": "Cannot unlink last auth provider"
}
```

---

## Quick Reference — All Account Endpoints

| # | Method | Endpoint | Auth | Description |
|---|--------|----------|------|-------------|
| 1 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/security-info` | 🔒 | Get security overview |
| 2 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/2fa/enable` | 🔒 | Enable 2FA |
| 3 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/2fa/disable` | 🔒 | Disable 2FA |
| 4 | <span style="background-color: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">DELETE</span> | `/account/deactivate` | 🔒 | Soft-deactivate account |
| 5 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/password/change` | 🔒 | Change password |
| 6 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/password/change-with-otp/send-otp` | 🔒 | Request OTP for password change |
| 7 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/password/change-with-otp/verify` | 🔒 | Change password via OTP |
| 8 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/password/can-set` | 🔒 | Check if password can be set |
| 9 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/password/set` | 🔒 | Set password (first time) |
| 10 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/username/check` | 🌐 | Check username availability |
| 11 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/username/can-change` | 🔒 | Check username change eligibility |
| 12 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/username/change` | 🔒 | Change username |
| 13 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/username/search` | 🌐 | Search users |
| 14 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/username/{username}` | 🌐 | Lookup user by username |
| 15 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/sessions` | 🔒 | List active sessions |
| 16 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/sessions/sign-out` | 🔒 | Sign out current session |
| 17 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/sessions/sign-out-others` | 🔒 | Sign out all other sessions |
| 18 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/sessions/sign-out-all` | 🔒 | Sign out all sessions |
| 19 | <span style="background-color: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">DELETE</span> | `/account/sessions/{sessionId}` | 🔒 | Revoke specific session |
| 20 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/device/verify` | 🌐 | Verify new device |
| 21 | <span style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">GET</span> | `/account/devices` | 🔒 | List trusted devices |
| 22 | <span style="background-color: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">DELETE</span> | `/account/devices/{deviceKeyId}` | 🔒 | Revoke trusted device |
| 23 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/link/email/initiate` | 🔒 | Start email linking |
| 24 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/link/email/verify` | 🔒 | Complete email linking |
| 25 | <span style="background-color: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">DELETE</span> | `/account/link/email` | 🔒 | Unlink email |
| 26 | <span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">POST</span> | `/account/link/oauth` | 🔒 | Link OAuth provider |
| 27 | <span style="background-color: #dc3545; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;">DELETE</span> | `/account/link/oauth/{provider}` | 🔒 | Unlink OAuth provider |