Skip to content

Pools

The Pools module manages organization-level fund pools used as aggregate containers for investments and takaful. Each pool has a pool_type (investment / takaful), currency, total balance, and one optional pool_config that defines ROI rate, ROI cycle, contribution limits, lock-in days, and notice days for withdrawal. A single PoolsController at /pools; pool creation is not exposed here (handled during onboarding/initialization), but pool config can be created and updated.

PropertyValue
Base URL{HOST}/v1
AuthBearer JWT (header Authorization) or access_token cookie
Content-Typeapplication/json
Error envelope{ "message": string | string[], "statusCode": number, "error": string }
ValidationGlobal ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown fields → 400
Related modulesorganizations, accounts, investments, takaful, withdrawal
Document versionv1 · 2026-05-20
AudienceInternal FE devs (mobile + web)

Organization admins read the list of pools they own and the detail of each pool / its config. Only the MORIA superadmin can create or full-replace a pool config (Put). The pool’s name / status / currency fields can be updated by MORIA and ORGANIZATION. All actions are logged via BusinessEvent.

MethodPathAuthSummary
GET/v1/poolsbearerList pools for one organization (paginated)
GET/v1/pools/:pool_idbearerDetail of one pool
GET/v1/pools/config/:pool_idbearerConfig detail for one pool
POST/v1/pools/configbearerCreate pool config (MORIA only)
PUT/v1/pools/configbearerFull-replace pool config (MORIA only)
PATCH/v1/pools/:pool_idbearerUpdate pool fields (name, currency, status)

List pools for one organization. The organization_id query is required and must be a UUID. The result is paginated with page, limit, and ordered by created_at (asc/desc).

bearer MORIA, ORGANIZATION read-pool RESOURCE_FETCHED
ParamTypeDefaultNotes
organization_idstring (UUID)Required — validated by ParseUUIDPipe
pagenumber1Page number
limitnumber10Records per page
order'asc' | 'desc'descOrder by created_at
{
"status": "success",
"statusCode": 200,
"message": "Pools retrieved successfully",
"data": {
"limit": 10,
"count": 5,
"currentPage": 1,
"totalPages": 1,
"pools": [
{
"id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"organization_id": "123e4567-e89b-12d3-a456-426614174000",
"account_id": "123e4567-e89b-12d3-a456-426614174001",
"pool_type": "investment",
"name": "Emergency Fund Pool",
"total_balance": "25000000.0000",
"currency": "IDR",
"status": "active",
"created_at": "2026-01-10T08:30:00.000Z",
"updated_at": "2026-05-20T08:30:00.000Z"
}
]
}
}
StatusWhen it occurs
400 Bad Requestorganization_id is not a UUID / empty
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenMissing read-pool permission or role is not MORIA/ORGANIZATION

Detail of one pool. No explicit role gate on the controller — only the read-pool permission and a valid Bearer are needed.

bearer read-pool RESOURCE_FETCHED
ParamTypeNotes
pool_idUUIDValidated via ParseUUIDPipe
{
"status": "success",
"statusCode": 200,
"message": "Pool retrieved successfully",
"data": {
"pool": {
"id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"organization_id": "123e4567-e89b-12d3-a456-426614174000",
"account_id": "123e4567-e89b-12d3-a456-426614174001",
"pool_type": "investment",
"name": "Emergency Fund Pool",
"total_balance": "25000000.0000",
"currency": "IDR",
"status": "active",
"created_at": "2026-01-10T08:30:00.000Z",
"updated_at": "2026-05-20T08:30:00.000Z"
}
}
}
StatusWhen it occurs
400 Bad Requestpool_id is not a UUID
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenMissing read-pool permission
404 Not FoundPool not found

GET /v1/pools/config/:pool_id bearer

Section titled “GET /v1/pools/config/:pool_id ”

Detail of one pool’s configuration (ROI rate, ROI cycle, contribution limits, lock-in, notice days). Separate permission: read-pool-config.

bearer read-pool-config RESOURCE_FETCHED
ParamTypeNotes
pool_idUUIDValidated via ParseUUIDPipe
{
"status": "success",
"statusCode": 200,
"message": "Pool config for pool id be2cb7da-e217-401c-8d07-b01e64adfb34 retrieved successfully",
"data": {
"pool_config": {
"id": "990e8400-e29b-41d4-a716-446655440444",
"pool_id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"roi_rate": "0.0500",
"roi_cycle": "monthly",
"min_contribution": "1000.0000",
"max_contribution": "100000.0000",
"lock_in_days": 30,
"min_withdrawal": "500.0000",
"notice_days": 7,
"is_active": true,
"created_at": "2026-01-10T08:30:00.000Z",
"updated_at": "2026-05-20T08:30:00.000Z"
}
}
}
StatusWhen it occurs
400 Bad Requestpool_id is not a UUID
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenMissing read-pool-config permission
404 Not FoundPool config not found

Create a new pool config for a pool. Only the MORIA (superadmin) role can call this endpoint.

bearer MORIA create-pool-config RESOURCE_CREATED
FieldTypeRequiredNotes
pool_idstring (UUID)yesID of the pool to be configured · IsUUID
roi_ratestring (numeric)optionalROI rate (e.g. "0.05" = 5%) · IsNumberString
roi_cycleenum ROICycleoptionalmonthly, quarterly, yearly
min_contributionstring (numeric)optionalMinimum contribution (rupiah, integer/decimal)
max_contributionstring (numeric)optionalMaximum contribution
lock_in_daysintegeroptionalMinimum days funds are locked before withdrawal
min_withdrawalstring (numeric)optionalMinimum withdrawal amount
notice_daysintegeroptionalNotice days required before withdrawal
{
"pool_id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"roi_rate": "0.05",
"roi_cycle": "monthly",
"min_contribution": "1000.00",
"max_contribution": "100000.00",
"lock_in_days": 30,
"min_withdrawal": "500.00",
"notice_days": 7
}
{
"status": "success",
"statusCode": 200,
"message": "Pool configuration created successfully",
"data": {
"pool_config": {
"id": "990e8400-e29b-41d4-a716-446655440444",
"pool_id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"roi_rate": "0.0500",
"roi_cycle": "monthly",
"min_contribution": "1000.0000",
"max_contribution": "100000.0000",
"lock_in_days": 30,
"min_withdrawal": "500.0000",
"notice_days": 7,
"is_active": true,
"created_at": "2026-05-20T08:30:00.000Z"
}
}
}

Note: the current controller code sets the statusCode field to 200 even though the HTTP response status is 201 Created (Nest default for @Post()). FE should rely on the HTTP status, not the body statusCode for this endpoint.

StatusWhen it occurs
400 Bad Requestpool_id is not a UUID · invalid enum/numeric
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenRole is not MORIA or missing create-pool-config permission
404 Not FoundPool with pool_id not found
  • Emit BusinessEvent RESOURCE_CREATED (impact MEDIUM, request body captured).

Full-replace pool config (every optional field sent will overwrite). MORIA role only.

bearer MORIA update-pool-config RESOURCE_UPDATED
FieldTypeRequiredNotes
pool_idstring (UUID)yesID of the pool whose config is updated
roi_ratestring (numeric)optionalNew ROI rate
roi_cycleenum ROICycleoptionalmonthly, quarterly, yearly
min_contributionstring (numeric)optional
max_contributionstring (numeric)optional
lock_in_daysintegeroptional
min_withdrawalstring (numeric)optional
notice_daysintegeroptional
{
"pool_id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"roi_rate": "0.06",
"roi_cycle": "quarterly",
"lock_in_days": 60
}
{
"status": "success",
"statusCode": 200,
"message": "Pool configuration updated successfully",
"data": {
"pool_config": { "...": "shape matches GET /v1/pools/config/:pool_id" }
}
}
StatusWhen it occurs
400 Bad RequestInvalid body (pool_id not UUID, wrong enum, wrong numeric)
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenRole is not MORIA or missing update-pool-config permission
404 Not FoundPool / pool config not found
  • Emit BusinessEvent RESOURCE_UPDATED (impact MEDIUM, request body captured).

PATCH /v1/pools/:pool_id bearer

Section titled “PATCH /v1/pools/:pool_id ”

Update the pool fields themselves (not the config) — name, currency, status. Callable by both MORIA and ORGANIZATION.

bearer MORIA, ORGANIZATION update-pool RESOURCE_UPDATED
ParamTypeNotes
pool_idUUIDValidated via ParseUUIDPipe
FieldTypeRequiredNotes
namestringoptionalNew pool name
currencystringoptionalCurrency code (DB default IDR)
statusenum PoolStatusoptionalactive, inactive
{
"name": "Emergency Fund Pool 2026",
"status": "active"
}
{
"status": "success",
"statusCode": 200,
"message": "Pool information updated successfully",
"data": {
"pool": { "...": "shape matches GET /v1/pools/:pool_id" }
}
}
StatusWhen it occurs
400 Bad Requestpool_id is not a UUID · invalid status enum
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenRole is not MORIA/ORGANIZATION · missing update-pool permission
404 Not FoundPool not found
  • Emit BusinessEvent RESOURCE_UPDATED (impact MEDIUM, request body captured).

  • investment — pool for investment products
  • takaful — pool for takaful products
  • active · inactive
  • monthly · quarterly · yearly
  • read-pool — list + pool detail
  • read-pool-config — config detail
  • create-pool-config — create config (MORIA)
  • update-pool-config — replace config (MORIA)
  • update-pool — update pool (MORIA/ORG)
{
"message": "Validation failed (uuid is expected)",
"statusCode": 400,
"error": "Bad Request"
}

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

  • 400 body/query/param validation invalid
  • 401 token expired / missing
  • 403 wrong role · missing permission
  • 404 pool / pool config not found
  • 500 internal — show a generic toast
  • The pool creation endpoint itself (POST /pools) is not exposed in the current controller — pools are created by the backend during onboarding/initialization. FE does not need a create-pool form.
  • The total_balance field is always a numeric string (precision 20, scale 4) — render as rupiah with client-side formatting.
  • roi_rate is fractional (e.g. 0.0500 = 5%); do not multiply by 100 on display if you are already using a percent helper.