Skip to content

Charitable Cause

The Charitable Cause API lets your end-users — individuals or organizations — launch fundraising projects for social causes and accept donations from any verified Moria user. Every cause is reviewed and approved before it can accept donations; donations flow through the same balance-and-gateway payment rails as the rest of the platform. This guide is aimed at backend engineers integrating the API: every endpoint, every state transition, every field your client will send and receive.

PropertyValue
AudiencePartner integrators · backend engineers
Base URL/v1 · JSON over HTTPS
AuthBearer token (JWT) · per-endpoint permissions
Lifecyclepending · active · completed · cancelled · rejected · deactivated
CurrencyIDR · amounts as decimal numbers (smallest unit)
Integration~1–2 sprints for a typical mobile or web client

A charitable cause is a fundraising project with a target amount, description, regulations, and a stated benefit. A user or organization creates a cause; a Moria reviewer approves it; once approved, donors can contribute from their Moria balance or via a payment-gateway top-up. The cause accumulates a collected_amount until it reaches the target or is closed. Funds in the cause pool are collected out through a withdrawal endpoint to the cause owner’s main balance.

What it is not. Charitable Cause is not a direct merchant payout — collected funds land in a Moria-managed pool bound to the cause, not in the owner’s external bank account. It does not run recurring donation subscriptions itself; recurring donations are a future surface. It does not auto-disburse on completion — collecting out of the pool is an explicit withdrawal call.


flowchart LR
    D([Donor])
    M([Cause manager])

    subgraph DONOR_WALLET[" Donor wallet "]
        DMAIN[("Donor<br/>main balance")]
    end

    subgraph CAUSE[" Charitable cause "]
        POOL[("Cause pool<br/>(collected_amount)")]
    end

    subgraph OWNER_WALLET[" Owner wallet "]
        OMAIN[("Owner<br/>main balance")]
    end

    TOPUP[/"Top-up via<br/>payment gateway"/]
    DONATE[/"POST /v1/payments/balance<br/>reference_type:<br/>charitable_cause_donate"/]
    WITHDRAW[/"POST /v1/withdrawals<br/>reference_type: donation"/]

    TOPUP -->|credit| DMAIN
    DMAIN -->|"donation (balance rail)"| POOL
    TOPUP -.->|"donation (gateway rail)"| POOL
    POOL -->|"collection initiated<br/>by manager"| OMAIN

    D -.->|reads progress| POOL
    M -.->|manages cause| POOL

    classDef money fill:#FDF8E8,stroke:#3C2C96,stroke-width:2px,color:#1E1B4B;
    classDef edge fill:#FFF3D0,stroke:#F4BE27,stroke-width:1.5px,color:#8B5A00;
    classDef user fill:#E8E1FF,stroke:#3C2C96,stroke-width:1.5px,color:#1E1B4B;
    class DMAIN,POOL,OMAIN money;
    class TOPUP,DONATE,WITHDRAW edge;
    class D,M user;

stateDiagram-v2
    direction LR

    [*] --> pending: POST /v1/charitable-causes<br/>(awaiting review)

    pending --> active: PATCH /v1/charitable-causes/<br/>:id/approve<br/>(admin only)
    pending --> rejected: admin rejects

    active --> completed: collected_amount<br/>reaches target_amount
    active --> cancelled: DELETE /:id<br/>(creator / owner)
    active --> deactivated: admin action

    completed --> [*]
    rejected --> [*]
    cancelled --> [*]
    deactivated --> [*]

    note right of pending
        Donations are NOT accepted
        until the cause is approved.
    end note

    note right of active
        Donations are accepted.
        Edits allowed until
        the cause completes.
    end note

MethodPathPurposePermission
POST/v1/charitable-causesCreate a new cause (starts in pending)create-charitable-cause
GET/v1/charitable-causesList causes (paginated, filterable)read-charitable-cause
GET/v1/charitable-causes/:idFetch a single cause by idread-charitable-cause
PATCH/v1/charitable-causes/:idUpdate editable metadata (name, description, target, media)update-charitable-cause
DELETE/v1/charitable-causes/:idCancel a cause (terminal; sweeps remaining balance)delete-charitable-cause
POST/v1/charitable-causes/:id/assign-managerAppoint an additional manager (creator only)update-charitable-cause
PATCH/v1/charitable-causes/:id/approveApprove a pending cause (admin only)approve-charitable-cause
GET/v1/summary/charitable-causesAggregate statistics for an organization’s causesread-charitable-cause
GET/v1/donations/openList unallocated open donations (admin only)allocate-open-donation
POST/v1/donations/allocateAllocate a batch of open donations to a cause (admin only)allocate-open-donation

Donations are not on this surface — they use the platform-wide payments endpoints with reference_type: "charitable_cause_donate". Collecting funds out of a cause pool uses the platform-wide withdrawals endpoint with reference_type: "donation". See the donations and withdrawal sections below.


Request

POST /v1/charitable-causes
Authorization: Bearer <token>
Content-Type: application/json
{
"account_id": "a8c1-...-c9f0",
"name": "Flood Relief West Java",
"description": "Emergency aid for families affected by the January floods.",
"target_amount": 50000000,
"currency": "IDR",
"regulations": "All funds disbursed via on-site partner.",
"benefit": "Food, shelter, clean water for 200 families.",
"location": "Jakarta, Indonesia",
"photo_document_id": "f1c2-...-9aa8",
"video_document_id": "b3d4-...-2eee"
}

Response · 201 Created

{
"data": {
"cause": {
"id": "9f3b-...-1d22",
"account_id": "a8c1-...-c9f0",
"name": "Flood Relief West Java",
"slug_name": "flood_relief_west_java",
"status": "pending",
"cause_owner": "individual",
"target_amount": "50000000.0000",
"collected_amount": "0.0000",
"completion_percentage": 0,
"currency": "IDR",
"created_at": "2026-05-18T03:12:44Z"
}
}
}

Approving a pending cause

PATCH /v1/charitable-causes/:id/approve
Authorization: Bearer <token>

Transitions the cause from pending to active. Only callers with the approve-charitable-cause permission (Moria super-admin role) can call it. An already-approved cause returns 400.

{
"data": {
"cause": {
"id": "9f3b-...-1d22",
"status": "active",
"approved_at": "2026-05-18T04:00:00Z"
}
}
}

Who does what

StepActor
Submit cause for reviewCause creator (individual or org admin)
Review and approveMoria super-admin
Reject (out of band)Moria super-admin — sets status: "rejected"
Accept donationsAny verified user, after approval
Withdraw collected fundsCause owner or appointed manager

Donations attempted on a pending or rejected cause are rejected at the payments layer before any debit occurs.


---
config:
  sequence:
    actorMargin: 240
    width: 180
    messageMargin: 45
    boxMargin: 16
    noteMargin: 14
---
sequenceDiagram
    autonumber
    actor D as Donor
    participant API as Moria API
    participant WALLET as Donor balance
    participant POOL as Cause pool

    Note over D,API: One donation, balance rail
    D->>API: POST /v1/payments/balance<br/>{ reference_type: "charitable_cause_donate",<br/>reference_id: "cause-uuid",<br/>source_account_id, amount }
    API->>API: validate cause is active<br/>and visible to donor
    API->>WALLET: debit amount
    API->>POOL: credit amount<br/>(collected_amount += amount)
    API->>API: record donation row<br/>(donation_type: "specific")
    API-->>D: 201 Created<br/>{ data: { transaction_id,<br/>new_balance, reference_id } }

    Note over D,POOL: Notes<br/>- The gateway rail (POST /v1/payments/gateway) uses the same reference_type and credits the pool asynchronously on callback.<br/>- "Open" donations carry no reference_id and sit unallocated until an admin assigns them to a cause.

Balance rail (synchronous)

POST /v1/payments/balance
Authorization: Bearer <token>
{
"reference_type": "charitable_cause_donate",
"reference_id": "9f3b-...-1d22",
"source_account_id": "a8c1-...-c9f0",
"amount": "100000"
}

Debits the donor’s specified Moria balance and credits the cause pool in a single transaction. Returns 201 with the donor’s new balance and the cause id.

Gateway rail (asynchronous)

POST /v1/payments/gateway
Authorization: Bearer <token>
{
"reference_type": "charitable_cause_donate",
"reference_id": "9f3b-...-1d22",
"amount": "100000",
"payment_id": "pay-..."
}

Returns a payment-gateway envelope (VA number / QR / payment link). The cause is credited when the gateway fires the payment.completed callback; until then, the cause’s collected_amount does not change.


Open donation flow

A donor who does not pick a specific cause sends an open donation — the same payments endpoint, but with reference_type: "charitable_cause_open_donate". The donation row is created with cause_id: null and donation_type: "open"; funds land in a Moria-managed unallocated pool. An admin then assigns these donations to one or more active causes.

Admin batch allocation

POST /v1/donations/allocate
Authorization: Bearer <token>
{
"donation_ids": [
"d1f2-...-aaaa",
"d2f3-...-bbbb"
],
"cause_id": "9f3b-...-1d22"
}

Open donations are useful when your client wants to offer a “donate to whatever Moria considers most urgent” path. Most integrators will not need them — use the charitable_cause_donate reference type with a specific reference_id for everyday donations.


Request

POST /v1/charitable-causes/:id/assign-manager
Authorization: Bearer <token>
{
"user_id": "u1b2-...-c3d4"
}

Only the cause creator can appoint additional managers. The creator is automatically appointed as the first manager when the cause is created, so this endpoint adds more.

What managers can do

  • Read the cause and its donation activity
  • Edit cause metadata (until the cause completes)
  • Initiate withdrawal of collected funds from the cause pool to the cause owner’s main balance
  • Cannot transfer ownership, cannot approve causes, cannot appoint other managers (creator only)

Request

GET /v1/charitable-causes?status=active
&organization_id=o7b3-...-eee1
&page=1&limit=20
Authorization: Bearer <token>

Returns paginated data containing causes visible to the caller. Individuals see causes within their organization context (or their own account’s causes); organization admins see causes for all members in their organization.

Query parameters

ParamValueNotes
statuspending · active · completed · cancelled · rejected · deactivated · allFilter by state
organization_iduuidScope to one organization’s causes
user_iduuidFilter by cause creator
account_iduuidScope to a single account
pageinteger, default 11-indexed
limitinteger, default 20Page size
orderasc · descSort direction

The response wraps the list inside data with pagination metadata (count, currentPage, totalPages, limit, causes). Plan your client to consume the object envelope, not a bare array — this is forward-compatible against future additions.


StatusTypical triggerWhat it means · how to respond
400Validation or stateBody fails schema validation; or the request hits a domain rule (e.g. donating to a non-active cause, allocating already-allocated donations, approving a cause already active). Show the message inline.
401Bearer token missing or invalidToken expired, malformed, or absent. Re-authenticate and retry.
403Permission or ownership missingCaller lacks the required permission (e.g. an individual trying to approve), is operating on a cause they do not own, or is trying to appoint a manager when not the creator. Do not retry — surface as an access-denied state.
404Unknown cause or donation idCause or donation does not exist, was soft-deleted, or is invisible to the caller. Refresh the list and remove stale entries.
422Insufficient balanceA balance-rail donation cannot proceed because the donor’s source account has insufficient funds. Prompt the donor to top up via the payment gateway.
500InternalRetry once with exponential backoff. If persistent, capture the request id from the error envelope and contact support.

Standard error envelope

{ "statusCode": 400, "message": "Cause is not active (status: pending)", "error": "Bad Request" }

Auth

  • Every endpoint expects an Authorization: Bearer <token> header
  • The token is a JWT issued by the platform’s auth flow — your client obtains it on behalf of the end-user and forwards it on every call
  • Each call is checked against the caller’s role and the permissions bound to that role

Permission matrix

PermissionUsed by
create-charitable-causePOST create
read-charitable-causeGET list · GET one · GET summary
update-charitable-causePATCH update · POST assign-manager · withdrawal
delete-charitable-causeDELETE
approve-charitable-causePATCH approve (admin)
donate-charitable-causePOST /payments/* with this reference type
allocate-open-donationGET /donations/open · POST /donations/allocate

What is not supported, and how to work around it

Section titled “What is not supported, and how to work around it”


Charitable Cause is a small, opinionated surface designed to compose with the rest of the platform — donations land via the shared payments rails, withdrawals exit via the shared withdrawals endpoint, and the cause itself simply expresses a contract: “this much, for this benefit, under these regulations, reviewed by Moria before going live.” Knowing the lifecycle, the money flow, and the few caveats above is enough to ship a production integration.