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.
| Property | Value |
|---|---|
| Audience | Partner integrators · backend engineers |
| Base URL | /v1 · JSON over HTTPS |
| Auth | Bearer token (JWT) · per-endpoint permissions |
| Lifecycle | pending · active · completed · cancelled · rejected · deactivated |
| Currency | IDR · amounts as decimal numbers (smallest unit) |
| Integration | ~1–2 sprints for a typical mobile or web client |
What Charitable Cause does
Section titled “What Charitable Cause does”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.
Where the money sits
Section titled “Where the money sits”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;
Status transitions
Section titled “Status transitions”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
Endpoint reference
Section titled “Endpoint reference”| Method | Path | Purpose | Permission |
|---|---|---|---|
POST | /v1/charitable-causes | Create a new cause (starts in pending) | create-charitable-cause |
GET | /v1/charitable-causes | List causes (paginated, filterable) | read-charitable-cause |
GET | /v1/charitable-causes/:id | Fetch a single cause by id | read-charitable-cause |
PATCH | /v1/charitable-causes/:id | Update editable metadata (name, description, target, media) | update-charitable-cause |
DELETE | /v1/charitable-causes/:id | Cancel a cause (terminal; sweeps remaining balance) | delete-charitable-cause |
POST | /v1/charitable-causes/:id/assign-manager | Appoint an additional manager (creator only) | update-charitable-cause |
PATCH | /v1/charitable-causes/:id/approve | Approve a pending cause (admin only) | approve-charitable-cause |
GET | /v1/summary/charitable-causes | Aggregate statistics for an organization’s causes | read-charitable-cause |
GET | /v1/donations/open | List unallocated open donations (admin only) | allocate-open-donation |
POST | /v1/donations/allocate | Allocate 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.
Creating a cause
Section titled “Creating a cause”Request
POST /v1/charitable-causesAuthorization: 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" } }}Approval flow
Section titled “Approval flow”Approving a pending cause
PATCH /v1/charitable-causes/:id/approveAuthorization: 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
| Step | Actor |
|---|---|
| Submit cause for review | Cause creator (individual or org admin) |
| Review and approve | Moria super-admin |
| Reject (out of band) | Moria super-admin — sets status: "rejected" |
| Accept donations | Any verified user, after approval |
| Withdraw collected funds | Cause owner or appointed manager |
Donations attempted on a pending or rejected cause are rejected at the payments layer before any debit occurs.
Donating to a cause
Section titled “Donating to a cause”---
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.
Rails and donation shapes
Section titled “Rails and donation shapes”Balance rail (synchronous)
POST /v1/payments/balanceAuthorization: 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/gatewayAuthorization: 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 donations and admin allocation
Section titled “Open donations and admin allocation”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/allocateAuthorization: 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.
Manager appointment
Section titled “Manager appointment”Request
POST /v1/charitable-causes/:id/assign-managerAuthorization: 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)
Listing and filtering
Section titled “Listing and filtering”Request
GET /v1/charitable-causes?status=active &organization_id=o7b3-...-eee1 &page=1&limit=20Authorization: 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
| Param | Value | Notes |
|---|---|---|
status | pending · active · completed · cancelled · rejected · deactivated · all | Filter by state |
organization_id | uuid | Scope to one organization’s causes |
user_id | uuid | Filter by cause creator |
account_id | uuid | Scope to a single account |
page | integer, default 1 | 1-indexed |
limit | integer, default 20 | Page size |
order | asc · desc | Sort 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.
Errors you will encounter
Section titled “Errors you will encounter”| Status | Typical trigger | What it means · how to respond |
|---|---|---|
400 | Validation or state | Body 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. |
401 | Bearer token missing or invalid | Token expired, malformed, or absent. Re-authenticate and retry. |
403 | Permission or ownership missing | Caller 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. |
404 | Unknown cause or donation id | Cause or donation does not exist, was soft-deleted, or is invisible to the caller. Refresh the list and remove stale entries. |
422 | Insufficient balance | A balance-rail donation cannot proceed because the donor’s source account has insufficient funds. Prompt the donor to top up via the payment gateway. |
500 | Internal | Retry 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, permissions, and visibility
Section titled “Auth, permissions, and visibility”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
| Permission | Used by |
|---|---|
create-charitable-cause | POST create |
read-charitable-cause | GET list · GET one · GET summary |
update-charitable-cause | PATCH update · POST assign-manager · withdrawal |
delete-charitable-cause | DELETE |
approve-charitable-cause | PATCH approve (admin) |
donate-charitable-cause | POST /payments/* with this reference type |
allocate-open-donation | GET /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”Integration checklist
Section titled “Integration checklist”Build with confidence
Section titled “Build with confidence”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.