The Saving Goals module enables creation of savings targets (Hajj, emergency fund, etc.) with periodic auto-deduction from a source account into a dedicated holding account per goal. Endpoints are split across two controllers: SavingGoalsController at /saving-goals for CRUD + manual deduction, and OrgSavingGoalController for organization-level statistics summaries. Each goal has an owner_type (INDIVIDUAL/ORGANIZATION/MORIA), a lifecycle status, and a liquidity_type flag (loose vs locked).
Property
Value
Base URL
{HOST}/v1
Auth
Bearer JWT (header Authorization) or cookie access_token
FE creates a saving goal via POST /saving-goals — the service automatically creates a separate holding account (Architecture B). Users can pause, update parameters, run a manual deduction when auto-deduction fails, and delete the goal. Organization/MORIA admins can read aggregate summaries via GET /summary/saving-goals.
Method
Path
Summary
POST
/v1/saving-goals
Create a new saving goal (auto-creates a holding account)
GET
/v1/saving-goals
Paginated list of saving goals with status/liquidity/type filters
Create a new saving goal. Ownership is always derived from the JWT (Architecture B): INDIVIDUAL → individual-owned goal for the caller; ORGANIZATION → organization-owned goal for the caller’s org; MORIA → may set owner_type=MORIA. Org admins may pass an optional user_id to create an individual-owned goal on behalf of a member of their own organization. The holding account is created automatically server-side.
New. Only meaningful when the caller is an organization admin — create an INDIVIDUAL-owned goal on behalf of a member of the admin’s own organization. Ignored for individual callers (must match own user.id if sent — otherwise 403). When omitted, an org caller creates an ORGANIZATION-owned goal and an individual caller creates a goal for themselves.
name
string
✓
Goal name (e.g. Hajj)
liquidity_type
enum LiquidityType
optional
loose (default) or locked
target_amount
string
✓
Target nominal as a numeric string (rupiah, integer)
start_date
date (ISO 8601)
✓
Accumulation start date (YYYY-MM-DD)
end_date
date (ISO 8601)
✓
Target completion date (YYYY-MM-DD)
deduction_amount
string
✓
Periodic auto-deduction nominal (rupiah)
deduction_date
string
✓
Day of month (1..28) when auto-deduction runs
owner_type
enum OwnerType
optional
Set to MORIA (requires user_type=MORIA) to create a Moria-owned goal. Ignored for INDIVIDUAL/ORGANIZATION callers — owner_type is inferred from the JWT.
Validation failed (invalid date, non-numeric amount, unknown field — including legacy account_id/organization_id)
401 Unauthorized
Bearer/cookie token invalid
403 Forbidden
Missing create-saving-goal permission · individual caller sent a user_id that does not match their own · org admin sent a user_id for a user outside their organization
List the saving goals belonging to the logged-in user or organization. Supports layered filters and pagination.
Scope depends on role:
INDIVIDUAL — own goals only (owner_id = caller), plus organization-owned goals in the caller’s own org they’re entitled to view.
ORGANIZATION admin — default (type=all) returns the combined set: org-owned goals (owner_type=ORGANIZATION, owner_id=admin's org) and goals owned by individual members of the same organization. type=organization returns only org-owned; type=individual returns only the org’s member-individual goals (scoped via users.organization_id — does not leak across organizations). When user_id is supplied (must belong to the admin’s org), it narrows to that one member’s goals.
MORIA — unrestricted across all owner types.
Web / mobile — honors x-client-type (default mobile). web → formatted list (formatSavingGoalList()); mobile → raw, with account and audit fields (created_by/updated_by/deleted_by) stripped.
individual, organization, all, due_saving_goals, moria. For INDIVIDUAL always forced to individual. For ORG admin: all (default) → combined org-owned + member-individual; organization → org-owned only; individual → member-individual only (org-scoped).
user_id
string (UUID)
optional
Filter by user; INDIVIDUAL may only use their own user.id
account_id
string (UUID)
optional
Filter by account; INDIVIDUAL may only use their own account
Detail of a single saving goal. Authorization is checked in layers: INDIVIDUAL must be the owner, ORGANIZATION admin must be from the same org, MORIA is unrestricted. The response format differs between WEB (richer) and MOBILE (raw entity).
Web / mobile — honors x-client-type (default mobile). web → formatted detail incl. account, customization, and contributions; mobile → raw entity with account stripped.
Update saving goal parameters (name, target, deduction, status). All fields are optional — send only what changed. A completed status from the client is reset to active by the server.
Run a manual deduction when auto-deduction fails or the user wants to top up outside the schedule. A goal already completed rejects the request and returns 200 with an informative message.
Aggregate saving goals summary for the organization of the logged-in user. Admin endpoint — only MORIA and ORGANIZATION are allowed. Useful for analytics dashboards (counts, totals, averages, progress).