Skip to content

Users Endpoints ​

Base path: /api/v1/users

All endpoints require a valid Authorization: Bearer <access_token> header. See Authentication for the full auth flow.


Role-Based Access Summary ​

Endpointmemberadminowner
GET /me✅✅✅
PATCH /me✅✅✅
PATCH /me/password✅✅✅
POST /me/photo✅✅✅
GET / (list team)✅✅✅
POST /invite❌✅✅
PATCH /:id/role❌❌✅
POST /:id/deactivate❌❌✅
POST /:id/reactivate❌❌✅
POST /:id/reset-password❌❌✅
DELETE /:id❌❌✅

GET /api/v1/users/me ​

Returns the authenticated user's own profile. password_hash is never included in any response.

Response — 200 OK ​

json
{
  "id": "<uuid>",
  "accountId": "<uuid>",
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "jane@acme.com",
  "phone": "+254712345678",
  "role": "owner",
  "isVerified": true,
  "isActive": true,
  "profilePhotoUrl": "https://pub-xxx.r2.dev/photos/user-id.jpg",
  "lastLoginAt": "2025-01-01T00:00:00.000Z",
  "createdAt": "2025-01-01T00:00:00.000Z",
  "updatedAt": "2025-01-01T00:00:00.000Z"
}

PATCH /api/v1/users/me ​

Updates the authenticated user's profile fields. All fields are optional — supply only what you want to change.

Request Body ​

json
{
  "firstName": "Jane",
  "lastName": "Smith",
  "email": "jane.smith@acme.com",
  "phone": "+254712345678"
}
FieldTypeNotes
firstNamestring
lastNamestring
emailstringMust be unique; triggers re-verification if changed
phonestringOptional phone number

Response — 200 OK ​

Returns the updated user object (same shape as GET /me).


PATCH /api/v1/users/me/password ​

Changes the authenticated user's password. Requires the current password for verification.

Request Body ​

json
{
  "currentPassword": "oldPassword123",
  "newPassword": "newPassword456"
}
FieldTypeRequiredNotes
currentPasswordstringYesMust match the stored bcrypt hash
newPasswordstringYesMinimum 8 characters

Response — 204 No Content ​

Errors ​

CodeCause
401 UnauthorizedcurrentPassword is incorrect
400 Bad RequestnewPassword under 8 characters

POST /api/v1/users/me/photo ​

Uploads a profile photo. The file is stored in Cloudflare R2 and the profile_photo_url field on the user is updated to the public R2 URL.

Content-Type: multipart/form-data

Request ​

FieldTypeNotes
fileFileJPEG, PNG, or WebP. Max 5 MB

Response — 200 OK ​

json
{
  "url": "https://pub-xxx.r2.dev/profile-photos/<user-id>.jpg"
}

Why Cloudflare R2? ​

R2 was chosen over AWS S3 for photo storage because:

  • Zero egress fees — files served via Cloudflare's CDN have no bandwidth charges
  • Same SDK — R2 is S3-compatible; the @aws-sdk/client-s3 library works unchanged
  • Consistent vendor — we already use Cloudflare for DNS, CDN, and Pages; reducing the number of vendors simplifies billing and IAM

Errors ​

CodeCause
400 Bad RequestFile too large or unsupported format

GET /api/v1/users ​

Lists all active team members belonging to the authenticated user's account. Deactivated members are excluded.

Response — 200 OK ​

json
[
  {
    "id": "<uuid>",
    "accountId": "<uuid>",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@acme.com",
    "role": "owner",
    "isVerified": true,
    "isActive": true,
    "lastLoginAt": "2025-01-01T00:00:00.000Z",
    "createdAt": "2025-01-01T00:00:00.000Z"
  }
]

POST /api/v1/users/invite ​

Creates a new team member under the caller's account. An invitation email with credentials can optionally be sent.

Required role: owner or admin

Request Body ​

json
{
  "firstName": "John",
  "lastName": "Smith",
  "email": "john@acme.com",
  "password": "tempPassword1",
  "role": "member"
}
FieldTypeRequiredNotes
firstNamestringYes
lastNamestringYes
emailstringYesMust be unique across all users
passwordstringYesMinimum 8 characters
rolestringYesadmin or member — cannot invite owner

Response — 201 Created ​

Returns the created user object.

Errors ​

CodeCause
403 ForbiddenCaller is a member and lacks permission
409 ConflictEmail already registered
422 UnprocessableValidation failure

PATCH /api/v1/users/:id/role ​

Changes the role of another team member. The owner role cannot be changed.

Required role: owner only

Path Parameters ​

ParameterDescription
idUUID of the user whose role is being changed

Request Body ​

json
{
  "role": "admin"
}

Allowed values: admin, member

Response — 200 OK ​

Returns the updated user object.

Errors ​

CodeCause
403 ForbiddenCaller is not owner
400 Bad RequestAttempting to change the owner's role
404 Not FoundUser not found in this account

POST /api/v1/users/:id/deactivate ​

Deactivates a team member. Deactivated users cannot log in. Their data is preserved.

Required role: owner only

Response — 200 OK ​

Returns the updated user object with isActive: false.

Errors ​

CodeCause
400 Bad RequestAttempting to deactivate yourself or the owner
404 Not FoundUser not in this account

POST /api/v1/users/:id/reactivate ​

Reactivates a previously deactivated team member.

Required role: owner only

Response — 200 OK ​

Returns the updated user object with isActive: true.


POST /api/v1/users/:id/reset-password ​

Admin-initiated password reset. Sets a temporary password on behalf of a team member. The member should change it on next login.

Required role: owner only

Request Body ​

json
{
  "newPassword": "TempPass123!"
}

Response — 200 OK ​

json
{
  "message": "Password reset successfully."
}

Errors ​

CodeCause
400 Bad RequestnewPassword under 8 characters or resetting own password
404 Not FoundUser not found in this account

DELETE /api/v1/users/:id ​

Permanently removes a team member from the account.

Required role: owner only

Response — 204 No Content ​

Errors ​

CodeCause
403 ForbiddenCaller is not owner
400 Bad RequestAttempting to remove the owner or yourself
404 Not FoundUser not found in this account

Internal use only — Sema Link Engineering