Skip to content

Charitable Cause

The Charitable Cause module enables crowdfunding projects: organizations or individuals register a cause (funding target, regulations, benefit), assign managers, and receive donations. User-facing endpoints live in CharitableCauseController (/charitable-causes) for CRUD + assign manager. Admin endpoints live in CharitableCauseAdminController for summary, cause approval, and allocation of open donations (donations without a specific cause) to ACTIVE causes.

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

Typical flow: create cause → platform admin approves → donor contributes via the payments module (donation flow) → the creator may assign additional managers. Donations without a specific cause (type OPEN) are pooled and allocated by MORIA superadmin via the admin endpoint POST /donations/allocate.

MethodPathAuthSummary
GET/v1/charitable-causesbearerList causes (scoped by role)
GET/v1/charitable-causes/:idbearerDetail of a single cause
POST/v1/charitable-causesbearerCreate a new cause
PATCH/v1/charitable-causes/:idbearerUpdate a cause
DELETE/v1/charitable-causes/:idbearerDeactivate (soft-cancel)
POST/v1/charitable-causes/:id/assign-managerbearerAssign an additional manager
GET/v1/summary/charitable-causesbearerStatistics summary (admin)
GET/v1/donations/openbearerList open donations (MORIA only)
POST/v1/donations/allocatebearerAllocate a batch of open donations to a cause (MORIA only)
PATCH/v1/charitable-causes/:id/approvebearerApprove a cause (MORIA only)

GET /v1/charitable-causes bearer

Section titled “GET /v1/charitable-causes ”

Paginated list of causes. Scope depends on role:

  • INDIVIDUAL — controlled by the scope param. Default (marketplace) returns donatable causes (approved, active, in_progress) within the caller’s own organization — the browse/donate view. scope=mine returns the causes the caller created/owns across all statuses (so a creator can track a still-pending submission).
  • ORGANIZATION admin — the org’s causes (all statuses), for review/approval.
  • MORIA — all causes.

Web / mobile — honors x-client-type (default mobile). See Client types (web vs mobile) for the full rules. Concretely: web nests audit fields as created/updated/deleted { at, by } objects, replaces relations with formatted manager/organization objects, and exposes the attached photo_document/video_document/report/signature (each as { id, created, document_type, document(url) } when present); mobile returns the raw entity with flat created_at/updated_at + created_by/updated_by/deleted_by and foreign-key ids (organization_id, owner_id, user_id, account_id, address_id). Both clients include the document slots (photo_document/video_document/report/signature) — web formatted, mobile as the raw Documents row (or null).

bearer read-charitable-cause
ParamTypeDefaultNotes
pagenumber1Page number
limitnumber10Records per page
scopeenum marketplace | minemarketplaceINDIVIDUAL only. marketplace (default) → donatable causes in the caller’s org; mine → causes the caller created/owns, all statuses. Ignored for ORGANIZATION/MORIA.
statusenum CharitableCauseStatusoptionalExplicit status filter; overrides the default set (all means no filter). For marketplace the default set is approved/active/in_progress; for mine it is all statuses.
order'asc' | 'desc'descSort by created_at
organization_idstring (UUID)optionalMarketplace lookup; INDIVIDUAL may only use their own organization
user_idstring (UUID)optionalUser filter; layered authorization
account_idstring (UUID)optionalAccount filter; layered authorization
{
"status": "success",
"statusCode": 200,
"message": "all charitable causes fetched successfully",
"data": {
"limit": 20,
"count": 73,
"currentPage": 1,
"totalPages": 4,
"causes": [
{
"id": "5d1220ab-4a14-4e94-942f-feef4e15ebeb",
"created_at": "2026-05-26T08:12:12.578Z",
"updated_at": "2026-05-26T08:12:12.578Z",
"created_by": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"updated_by": "00000000-0000-0000-0000-000000000000",
"deleted_by": "00000000-0000-0000-0000-000000000000",
"account_id": "6522b35b-bb0d-4742-9b36-1714e69df229",
"address_id": "fbc3181d-2917-4103-b546-9060d2ad1011",
"organization_id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"owner_id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"user_id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"live_stream_url": null,
"description": "Verifying that a newly created cause is fetchable.",
"name": "QA Verify Cause 1779783131",
"slug_name": "qa_verify_cause_1779783131",
"currency": "IDR",
"target_amount": "5000000.0000",
"collected_amount": "0.0000",
"completion_percentage": 0,
"status": "pending",
"cause_owner": "individual",
"regulations": "Standard Shariah-compliant disbursement.",
"benefit": "Supports flood relief.",
"photo_document": [],
"video_document": null,
"report": null,
"signature": null
}
]
},
"lang": "en"
}

photo_document is an array of raw Documents rows (cause images; [] when none, currently capped at one): e.g. photo_document: [{ "id": "…", "url": "https://s3-dev.moriafund.com/moria-bucket-dev/image/….jpg", "document_type": "image", … }]. The single-slot documents (video_document/report/signature) return the raw Documents row (or null). In all cases the url is the absolute, ready-to-use public URL — no need to prepend a base. The web shape formats each (and photo_document stays an array of formatted docs).

{
"status": "success",
"statusCode": 200,
"message": "all charitable causes fetched successfully",
"data": {
"limit": 20,
"count": 73,
"currentPage": 1,
"totalPages": 4,
"causes": [
{
"id": "5d1220ab-4a14-4e94-942f-feef4e15ebeb",
"created": {
"at": "2026-05-26T08:12:12.578Z",
"by": {}
},
"updated": {
"at": "2026-05-26T08:12:12.578Z",
"by": {}
},
"deleted": {
"at": null,
"by": {}
},
"photo_document": [],
"video_document": null,
"description": "Verifying that a newly created cause is fetchable.",
"name": "QA Verify Cause 1779783131",
"currency": "IDR",
"target_amount": "5000000.0000",
"collected_amount": "0.0000",
"completion_percentage": 0,
"status": "pending",
"regulations": "Standard Shariah-compliant disbursement.",
"benefit": "Supports flood relief.",
"manager": {
"id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"email": "[email protected]",
"first_name": "Admin",
"last_name": "Primary",
"phone_number": "+6288560649118",
"profile_image": null
},
"organization": {
"id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"name": "moria fund",
"email": "[email protected]",
"phone_number": "+1234567890",
"logo_id": null
},
"report": null,
"signature": null
}
]
},
"lang": "en"
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenWrong scope (see auth notes)

GET /v1/charitable-causes/:id bearer

Section titled “GET /v1/charitable-causes/:id ”

Detail of a single cause. Response format differs between MOBILE (raw) and WEB (formatted with aggregation). Scope organization_id is passed from the user.

Web / mobile — honors x-client-type (default mobile). See Client types (web vs mobile) for the full rules. Concretely: web nests audit fields as created/updated/deleted { at, by } objects, replaces relations with formatted manager/organization objects, exposes the attached photo_document/video_document/report/signature (each as { id, created, document_type, document(url) } when present), and returns the contributions summary under donations; mobile returns the raw entity with flat audit columns + foreign-key ids (organization_id, owner_id, user_id, account_id, address_id) and the summary under member_contributions (documents are still attached as FK-resolved fields).

bearer read-charitable-cause
ParamTypeNotes
idUUIDCause ID
{
"status": "success",
"statusCode": 200,
"message": "Cause retrieved successfully",
"data": {
"cause": {
"id": "5d1220ab-4a14-4e94-942f-feef4e15ebeb",
"created_at": "2026-05-26T08:12:12.578Z",
"updated_at": "2026-05-26T08:12:12.578Z",
"created_by": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"updated_by": "00000000-0000-0000-0000-000000000000",
"deleted_by": "00000000-0000-0000-0000-000000000000",
"account_id": "6522b35b-bb0d-4742-9b36-1714e69df229",
"address_id": "fbc3181d-2917-4103-b546-9060d2ad1011",
"organization_id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"owner_id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"user_id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"live_stream_url": null,
"description": "Verifying that a newly created cause is fetchable.",
"name": "QA Verify Cause 1779783131",
"slug_name": "qa_verify_cause_1779783131",
"currency": "IDR",
"target_amount": "5000000.0000",
"collected_amount": "0.0000",
"completion_percentage": 0,
"status": "pending",
"cause_owner": "individual",
"regulations": "Standard Shariah-compliant disbursement.",
"benefit": "Supports flood relief.",
"photo_document": [],
"video_document": null,
"report": null,
"signature": null,
"member_contributions": [
{
"user_id": "",
"total_contribution": "0.00",
"contribution_count": 0
}
]
}
},
"lang": "en"
}
{
"status": "success",
"statusCode": 200,
"message": "Cause retrieved successfully",
"data": {
"cause": {
"id": "5d1220ab-4a14-4e94-942f-feef4e15ebeb",
"created": {
"at": "2026-05-26T08:12:12.578Z",
"by": {}
},
"updated": {
"at": "2026-05-26T08:12:12.578Z",
"by": {}
},
"deleted": {
"at": null,
"by": {}
},
"photo_document": [],
"video_document": null,
"description": "Verifying that a newly created cause is fetchable.",
"name": "QA Verify Cause 1779783131",
"currency": "IDR",
"target_amount": "5000000.0000",
"collected_amount": "0.0000",
"completion_percentage": 0,
"status": "pending",
"regulations": "Standard Shariah-compliant disbursement.",
"benefit": "Supports flood relief.",
"manager": {
"id": "5ebbf5f6-9e18-421c-8f1d-d6286adcd2b0",
"email": "[email protected]",
"first_name": "Admin",
"last_name": "Primary",
"phone_number": "+6288560649118",
"profile_image": null
},
"organization": {
"id": "2fd30e18-b135-4c31-9967-32f684bb58d8",
"name": "moria fund",
"email": "[email protected]",
"phone_number": "+1234567890",
"logo_id": null
},
"report": null,
"signature": null,
"donations": [
{
"user_id": "",
"total_contribution": "0.00",
"contribution_count": 0
}
]
}
},
"lang": "en"
}
StatusWhen it occurs
400 Bad Requestid is not a UUID
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNo access to the cause
404 Not FoundCause not found

POST /v1/charitable-causes bearer

Section titled “POST /v1/charitable-causes ”

Create a new cause. Ownership is always derived from the JWT (Architecture B): INDIVIDUAL → individual-owned cause with the caller’s address; ORGANIZATION → organization-owned cause with the org address. Org admins may pass an optional user_id to create an individual-owned cause on behalf of a member of their own organization (the cause address is taken from that user). The initial status is pending until a MORIA admin approves it.

bearer create-charitable-cause RESOURCE_CREATED
FieldTypeRequiredNotes
user_idstring (UUID)optionalNew. Only meaningful when the caller is an organization admin — create an INDIVIDUAL-owned cause 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 cause and an individual caller creates a cause for themselves.
descriptionstringCause description
currencystringoptionalCurrency (default IDR)
target_amountnumberFunding target (number, not string)
regulationsstringCause regulations & terms
benefitstringCause benefit & impact
namestringCause name
locationstringoptionalCause location (e.g. Jakarta, Indonesia)
{
"name": "Bantu Korban Banjir",
"description": "Bantuan untuk korban banjir Jakarta",
"currency": "IDR",
"target_amount": 10000000,
"regulations": "Dana digunakan untuk kebutuhan pokok korban",
"benefit": "Membantu 500 keluarga terdampak",
"location": "Jakarta, Indonesia"
}
{
"status": "success",
"statusCode": 201,
"message": "Cause created successfully",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Bantu Korban Banjir",
"target_amount": 10000000,
"current_amount": 0,
"currency": "IDR",
"status": "pending"
}
}
StatusWhen it occurs
400 Bad RequestValidation failed (non-numeric target_amount, required field empty, legacy account_id/organization_id field sent)
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenMissing create-charitable-cause 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

PATCH /v1/charitable-causes/:id bearer

Section titled “PATCH /v1/charitable-causes/:id ”

Update a cause. Only the creator or a manager may perform this. All fields are optional.

bearer update-charitable-cause RESOURCE_UPDATED
ParamTypeNotes
idUUIDCause ID
FieldTypeNotes
descriptionstringNew description
currencystringCurrency
target_amountnumberNew funding target
regulationsstringNew regulations
benefitstringNew benefit
namestringNew name
statusenum CharitableCauseStatusNew status
{ "name": "Bantu Korban Banjir 2026", "target_amount": 15000000 }
{
"status": "success",
"statusCode": 200,
"message": "Cause updated successfully",
"data": { "...": "CauseDto shape" }
}
StatusWhen it occurs
400 Bad RequestValidation failed
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot the creator/manager
404 Not FoundCause not found

DELETE /v1/charitable-causes/:id bearer

Section titled “DELETE /v1/charitable-causes/:id ”

Deactivate a cause (soft-cancel). Status changes to deactivated; the row is not removed and donations already received are not refunded.

bearer delete-charitable-cause RESOURCE_DELETED
ParamTypeNotes
idUUIDCause ID
{
"status": "success",
"statusCode": 200,
"message": "Cause cancelled successfully",
"data": { "...": "CauseDto shape, status='deactivated'" }
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot the creator
404 Not FoundCause not found

POST /v1/charitable-causes/:id/assign-manager bearer

Section titled “POST /v1/charitable-causes/:id/assign-manager ”

Designate another user as a manager of the cause. Only the creator is allowed — additional managers may update the cause but may not assign further managers.

bearer update-charitable-cause
ParamTypeNotes
idUUIDCause ID
FieldTypeRequiredNotes
user_idstring (UUID)ID of the user to become a manager
{ "user_id": "be2cb7da-e217-401c-8d07-b01e64adfb34" }
{
"status": "success",
"statusCode": 201,
"message": "Manager assigned successfully",
"data": { "manager": { "user_id": "...", "cause_id": "..." } }
}
StatusWhen it occurs
400 Bad RequestUser is already a manager of this cause
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot the creator (only the creator may assign)
404 Not FoundCause or user not found

GET /v1/summary/charitable-causes bearer

Section titled “GET /v1/summary/charitable-causes ”

Cause statistics summary for the caller’s organization. Admin endpoint (MORIA / ORGANIZATION). MORIA can also view summaries across organizations.

bearer MORIA, ORGANIZATION read-charitable-cause
{
"status": "success",
"statusCode": 200,
"message": "charitable cause summary fetched successfully",
"data": {
"organization_id": "660e8400-e29b-41d4-a716-446655440111",
"organization_name": "Moria Fund",
"total_causes": "12",
"active_causes": "5",
"completed_causes": "3",
"pending_causes": "2",
"total_target_amount": "100000000.00",
"total_collected_amount": "65000000.00"
}
}
StatusWhen it occurs
401 UnauthorizedToken invalid / querying another org (mismatch)
403 ForbiddenRole is not MORIA/ORGANIZATION

List OPEN donations not yet allocated to a specific cause. MORIA superadmin only — typically used to allocate donations to causes in need.

bearer MORIA allocate-open-donation RESOURCE_UPDATED
ParamTypeDefaultNotes
pagenumber1Page number
limitnumber20Records per page
{
"status": "success",
"statusCode": 200,
"message": "Unallocated open donations fetched",
"data": {
"limit": 20,
"count": 8,
"currentPage": 1,
"totalPages": 1,
"donations": [
{
"id": "be2cb7da-e217-401c-8d07-b01e64adfb34",
"donor_user_id": "...",
"amount": "100000.00",
"donation_type": "open",
"status": "open",
"created_at": "2026-05-20T08:30:00.000Z"
}
]
}
}
StatusWhen it occurs
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot MORIA superadmin

POST /v1/donations/allocate bearer

Section titled “POST /v1/donations/allocate ”

Allocate a batch of OPEN donations to a single active cause. Atomic — if any donation is invalid (already allocated / cause not active), the entire batch is rolled back.

bearer MORIA allocate-open-donation RESOURCE_UPDATED

Request body — AllocateDonationsBatchDto

Section titled “Request body — AllocateDonationsBatchDto”
FieldTypeRequiredNotes
donation_idsstring[] (UUID)List of donations to allocate — all must be OPEN, not yet allocated, and exist
cause_idstring (UUID)Target cause; must be in status ACTIVE
{
"donation_ids": [
"be2cb7da-e217-401c-8d07-b01e64adfb34",
"a1f2c3d4-5678-90ab-cdef-1234567890ab"
],
"cause_id": "550e8400-e29b-41d4-a716-446655440000"
}
{
"status": "success",
"statusCode": 200,
"message": "Donations allocated",
"data": { "allocated_count": 2, "cause_id": "550e8400-e29b-41d4-a716-446655440000" }
}
StatusWhen it occurs
400 Bad RequestSome donations are non-OPEN / already allocated / cause not active
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot MORIA superadmin
404 Not FoundDonation or cause not found

PATCH /v1/charitable-causes/:id/approve bearer

Section titled “PATCH /v1/charitable-causes/:id/approve ”

Approve a cause currently in pending. MORIA superadmin only. After approval, the status moves to approved / active per the service logic.

bearer MORIA approve-charitable-cause RESOURCE_UPDATED
ParamTypeNotes
idUUIDCause ID
{
"status": "success",
"statusCode": 200,
"message": "Cause approved successfully",
"data": { "...": "CauseDto shape, status='active'" }
}
StatusWhen it occurs
400 Bad RequestCause was already approved
401 UnauthorizedBearer/cookie token invalid
403 ForbiddenNot MORIA superadmin
404 Not FoundCause not found

  • pending — awaiting MORIA approval
  • new — newly created (internal variant)
  • active — accepting donations
  • approved — approved by admin
  • in_progress — disbursement in progress
  • completed — target reached / finished
  • cancelled — cancelled
  • rejected — approval rejected
  • deactivated — soft-cancelled
  • all — filter sentinel (no filter)
  • specific — donor picks the cause
  • open — no cause, allocated by admin
  • subscription — recurring
  • organization · individual · moria
{
"message": "target_amount must be a number",
"statusCode": 400,
"error": "Bad Request"
}
  • 400 validation · donation already allocated · cause already approved
  • 401 token expired / missing
  • 403 not creator/manager · not MORIA
  • 404 cause/donation not found
  • 500 internal — show a generic toast