Skip to content

Users

The users module provides a light CRUD for user profiles (read + update), endpoints for reading role and permissions, and avatar upload/delete. GET /users/:user_id is polymorphic: if user_id is omitted the server returns the currently logged-in user’s data; the response shape also differs between web and mobile clients.

PropertyValue
Base URL{HOST}/v1
AuthBearer JWT (header Authorization) or cookie access_token
Content-Typeapplication/json · avatar upload: multipart/form-data
Error envelope{ "message": string | string[], "statusCode": number, "error": string }
ValidationGlobal ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown field → 400
Related modulesauthentication, organizations, acl, file-manager
Document versionv1 · 2026-05-20
AudienceInternal FE devs (mobile + web)

FE typically calls GET /users/:user_id without user_id after login to fetch the active user’s profile. GET /users is used by admins to list organization members (or, for the MORIA role, across organizations via org_id). Avatar updates have their own path (POST/DELETE /users/me/profile-image) that writes directly to S3 — profile_image cannot be modified via PATCH /users/:user_id.

MethodPathSummary
GET/v1/usersList users (org members / cross-org for MORIA)
GET/v1/users/monthly-income-rangeReference data for monthly income ranges
GET/v1/users/:user_idUser detail (or self if param omitted)
PATCH/v1/users/:user_idUpdate user profile
GET/v1/users/:user_id/roleFetch the user’s role (without permissions)
GET/v1/users/:user_id/role/permissionsFetch the permissions the user holds
POST/v1/users/me/profile-imageUpload/replace profile picture (multipart)
DELETE/v1/users/me/profile-imageRemove profile picture

List users (organization members, or cross-organization for the MORIA role). Standard paginated response. The result shape depends on X-Client-Type (web/mobile).

bearer MORIA, ORGANIZATION, INDIVIDUAL read-user RESOURCE_FETCHED
ParamTypeDefaultNotes
org_idUUIDRequired if caller is MORIA. For other roles, defaults to the user’s organization.
pagenumber1Page number
limitnumber10Records per page
order'asc' | 'desc'descOrder by created_at
user_statusenum UserStatusoptionalactive, inactive, suspended
user_typeenum UserTypeoptionalmoria, organization, individual
{
"status": "success",
"statusCode": 200,
"message": "Users retrieved successfully",
"data": {
"limit": 10,
"count": 124,
"currentPage": 1,
"totalPages": 13,
"users": [
{
"id": "770e8400-e29b-41d4-a716-446655440222",
"first_name": "Aisha",
"last_name": "Putri",
"email": "amal@example.com",
"user_type": "individual",
"user_status": "active",
"organization_id": "660e8400-e29b-41d4-a716-446655440111",
"created_at": "2026-05-20T09:15:00.000Z"
}
]
}
}
StatusWhen it occurs
400 Bad RequestMORIA calls without org_id
401 UnauthorizedAttempted to view another organization (non-MORIA)
403 ForbiddenPermission mismatch
404 Not FoundOrganization not found

GET /v1/users/monthly-income-range bearer

Section titled “GET /v1/users/monthly-income-range ”

Reference data for monthly income ranges for profile/KYC dropdowns. Standard pagination.

bearer MORIA, ORGANIZATION, INDIVIDUAL read-user
ParamTypeDefaultNotes
pagenumber1Page number
limitnumber10Records per page
order'asc' | 'desc'descOrder by created_at
{
"status": "success",
"statusCode": 200,
"message": "Monthly income ranges retrieved successfully",
"data": {
"limit": 10,
"count": 6,
"currentPage": 1,
"totalPages": 1,
"ranges": [
{ "id": "...", "label": "0 – 5.000.000", "min": 0, "max": 5000000 }
]
}
}
StatusWhen it occurs
401 UnauthorizedInvalid Bearer/cookie
403 ForbiddenPermission mismatch

User detail. If user_id is omitted the server returns the logged-in user’s data. The result shape depends on the caller’s UserType and the X-Client-Type header.

bearer read-user RESOURCE_FETCHED
ParamTypeNotes
user_idUUIDOptional. If omitted → active user (self).
{
"status": "success",
"statusCode": 200,
"message": "Login user data fetched successfully",
"data": {
"user": {
"id": "770e8400-e29b-41d4-a716-446655440222",
"first_name": "Aisha",
"last_name": "Putri",
"email": "amal@example.com",
"user_type": "individual",
"user_status": "active",
"profile_image": null,
"phone_number": "+628156489101",
"organization": { "id": "660e8400-e29b-41d4-a716-446655440111", "name": "Moria Fund" },
"address": { "country": "Indonesia", "city": "Jakarta" }
}
}
}

On mobile clients the role, password, and security.two_factor_authentication_secret fields are stripped from the payload. On web clients the data shape is formatted differently by getUserByIdFormatted.

StatusWhen it occurs
400 Bad Requestuser_id is not a UUID (if sent)
401 UnauthorizedAttempted to access a user from another organization
403 ForbiddenPermission mismatch
404 Not FoundUser not found

PATCH /v1/users/:user_id bearer

Section titled “PATCH /v1/users/:user_id ”

Update a user profile. Only for users in the same organization. All fields are optional — send only what changes. The profile_image field is intentionally absent from the DTO.

bearer update-user RESOURCE_UPDATED
ParamTypeNotes
user_idUUIDID of the user being updated

Request body — UpdateUserDto (all fields optional)

Section titled “Request body — UpdateUserDto (all fields optional)”
FieldTypeNotes
first_name, last_name, middle_namestringBasic identity
phone_numberstringInternational format (+62…)
id_card_numberstringKTP number (16 digits)
educationenum Educationprimary_school, junior_high, senior_high, diploma, bachelor, postgraduate, other
mother_name, relativesstringAdditional KYC data
purpose, source_of_income, montly_incomestringFinancial profile (typo montly_income retained)
genderenum Gendermale, female
date_of_birthdateISO 8601 (YYYY-MM-DD)
place_of_birthstringCity of birth
religionenum Religionislam, christianity, hinduism, buddhism, confucianism, other
marital_statusenum MaritalStatussingle, married, divorced, widowed
province, city, country, district, subdistrict, villagestringAddress
rt, rw, postal_codestringAddress detail
address_typeenum AddressTypeDefault INDIVIDUAL
{
"first_name": "Aisha",
"phone_number": "+628156489102",
"marital_status": "married",
"city": "Bandung"
}
{
"status": "success",
"statusCode": 200,
"message": "User updated successfully",
"data": {
"user": {
"id": "770e8400-e29b-41d4-a716-446655440222",
"first_name": "Aisha",
"phone_number": "+628156489102",
"marital_status": "married",
"updated_at": "2026-05-20T10:00:00.000Z"
}
}
}
StatusWhen it occurs
400 Bad RequestValidation failed (invalid enum, bad date format)
401 UnauthorizedUpdating a user from another organization
403 ForbiddenMissing update-user permission
404 Not FoundUser not found

GET /v1/users/:user_id/role bearer

Section titled “GET /v1/users/:user_id/role ”

Fetch the role attached to a user (without permissions and role_type). If the user has no role yet, the response is successful with no data field.

bearer read-role RESOURCE_FETCHED
ParamTypeNotes
user_idUUIDTarget user ID
{
"status": "success",
"statusCode": 200,
"message": "user role fetched successfully",
"data": {
"role": {
"id": "03be5259-f281-478e-a8d0-e7e825e525f2",
"name": "organization_admin",
"description": "Admin role for the organization"
}
}
}

If the user has no role: 200 response with message: "No role assigned to this user" and no data field.

StatusWhen it occurs
403 ForbiddenPermission mismatch
404 Not FoundUser not found

GET /v1/users/:user_id/role/permissions bearer

Section titled “GET /v1/users/:user_id/role/permissions ”

Fetch all permissions the user holds via their role. If they have no role, the response is successful with no data field.

bearer read-permission RESOURCE_FETCHED
ParamTypeNotes
user_idstringUser ID (no UUID pipe at the controller — still send a valid UUID)
{
"status": "success",
"statusCode": 200,
"message": "user role permissions fetched successfully",
"data": {
"permissions": [
{ "id": "...", "name": "read-user" },
{ "id": "...", "name": "update-user" }
]
}
}
StatusWhen it occurs
403 ForbiddenPermission mismatch
404 Not FoundUser not found

POST /v1/users/me/profile-image bearer

Section titled “POST /v1/users/me/profile-image ”

Upload or replace the active user’s profile picture. The backend accepts a multipart file (max 5 MB, JPEG/PNG/WebP), uploads to S3 (public-read), removes the old object, and stores the key in the users.profile_image column.

bearer update-user RESOURCE_UPDATED
FieldTypeRequiredNotes
filebinaryyesJPEG / PNG / WebP, maximum size 5 MB
{
"status": "success",
"statusCode": 200,
"message": "profile image updated successfully",
"data": {
"profile_image": "users/770e8400.../avatar-1716200000.jpg"
}
}
StatusWhen it occurs
400 Bad RequestInvalid file (empty, unsupported MIME, size > 5 MB)
401 UnauthorizedInvalid Bearer/cookie
403 ForbiddenMissing update-user permission
  • Upload to S3 (public-read bucket) and remove the old object (if any).
  • Update the users.profile_image column with the new key.
  • Emit BusinessEvent RESOURCE_UPDATED (impact LOW).

DELETE /v1/users/me/profile-image bearer

Section titled “DELETE /v1/users/me/profile-image ”

Delete the active user’s profile picture. The backend removes the S3 object referenced by users.profile_image and clears that column.

bearer update-user RESOURCE_DELETED
{
"status": "success",
"statusCode": 200,
"message": "profile image removed successfully",
"data": {}
}
StatusWhen it occurs
401 UnauthorizedInvalid Bearer/cookie
403 ForbiddenMissing update-user permission
  • Remove S3 object (if any).
  • Set users.profile_image = NULL.

  • active, inactive, suspended
  • moria, organization, individual
  • primary_school, junior_high, senior_high
  • diploma, bachelor, postgraduate, other
  • islam, christianity, hinduism
  • buddhism, confucianism, other
  • single, married, divorced, widowed
{
"message": "you can't view another organization",
"statusCode": 401,
"error": "Unauthorized"
}

message can be a string or an array of strings (multi-field validation errors).

  • X-Client-Type: web — “formatted” response shape
  • X-Client-Type: mobile — lean shape (no role, password, security)
  • 400 body/query/param validation
  • 401 cross-organization access
  • 403 permission mismatch
  • 404 user not found