Products · Payouts
Bulk Disbursements
Upload a CSV or call the API to fan out payments to thousands of recipients across Waafi, D-Money, EAB and CAC. 4-eyes approval baked in.
5 endpointspo_
How it works
- 1
Create a payout batch
POST to/v1/payouts/bulkwith either a multipart CSV upload or a JSONrowsarray. Each row specifies a recipient, amount, provider, and a per-rowreferencethat acts as the idempotency key for that individual payout. - 2
Batch enters pending_approval
The batch is created inpending_approvalstatus and cannot disburse until a second authorised user approves it. The creator cannot self-approve — this is enforced at the API level, not just the portal. - 3
Second user approves
POST to/v1/payouts/bulk/{id}/approveusing an API key belonging to a different principal. Attempting to approve with the same key that created the batch returns403 self_approval_not_allowed. - 4
Batch fans out
On approval the orchestrator fans out each row in parallel. Every row becomes an individualpo_payout record, routed to the correct provider rail. Row-level failures are isolated — a failed row does not block others. - 5
Webhooks fire per row
payout.created,payout.succeeded, andpayout.failedfire for each individual payout row. The batch-level object transitions tocompletedonce all rows have reached a terminal state.
CSV format
When uploading a file, use UTF-8 encoding with a header row. Column order is fixed; all five columns are required.
| Column | Format | Notes |
|---|---|---|
recipient_msisdn | E.164 string | Use for mobile-money providers. Mutually exclusive with recipient_account. |
recipient_account | string | Bank account identifier. Mutually exclusive with recipient_msisdn. |
amount | integer | DJF minor units (DJF is zero-decimal — pass whole DJF, e.g. 5000 = DJF 5,000). |
provider | enum | waafi · dmoney · eab · cac |
reference | string | Your unique per-row reference. Used as idempotency key for that individual payout. |
description | string | Human-readable label. Appears on receipts and in the portal. ≤140 chars. |
Endpoints
POST
/v1/payouts/bulkCreate a new payout batch (JSON rows or multipart CSV).
GET
/v1/payouts/bulk/{id}Retrieve a batch and its aggregate status.
POST
/v1/payouts/bulk/{id}/approveApprove a pending_approval batch. Requester ≠ approver enforced.
POST
/v1/payouts/bulk/{id}/cancelCancel a batch before any rows have been processed.
GET
/v1/payoutsList individual po_ payout rows. Filter by batch_id, status, provider.
Create parameters
| Name | Type | Description |
|---|---|---|
rowsrequired | array | Array of payout row objects when using JSON. Omit when uploading a CSV file via multipart. |
currencyrequired | ISO 4217 | Currency for all rows in this batch. Currently only "DJF" is supported for bulk payouts. |
description | string | Batch-level label for portal and reporting. ≤255 chars. |
rows[].recipient_msisdn | E.164 | Mobile number of the recipient. Required for waafi and dmoney. |
rows[].recipient_account | string | Bank account identifier. Required for eab and cac. |
rows[].amountrequired | integer | Amount in DJF minor units. |
rows[].providerrequired | enum | Provider for this row: waafi · dmoney · eab · cac. |
rows[].referencerequired | string | Your unique reference for this row. Idempotent — re-submitting the same reference within a batch is a no-op. |
rows[].description | string | Row-level description. Appears on the recipient's statement where the provider supports it. |
⚠
4-eyes enforcement
The API key that created the batch cannot also approve it. Use separate keys for the requester and approver roles, or have a second authorised user approve in the merchant portal. Attempting to self-approve returns
403 self_approval_not_allowed.Limits
| Limit | Default | Increase available |
|---|---|---|
| Rows per batch | 10,000 | Yes — contact support |
| Total batch value | DJF 50,000,000 | Yes — requires additional approval tier |
| Concurrent pending batches | 5 | Yes — contact support |
| CSV file size | 10 MB | No |
Example — create and approve a batch
POST /v1/payouts/bulkbash
curl -X POST https://api.merashub.com/v1/payouts/bulk \
-H "Authorization: Bearer sk_live_requester_..." \
-H "Content-Type: application/json" \
-d '{
"currency": "DJF",
"description": "May 2026 agent commissions",
"rows": [
{
"recipient_msisdn": "+25377000001",
"amount": 12500,
"provider": "waafi",
"reference": "comm_may_agt_001",
"description": "Commission payout — agent 001"
},
{
"recipient_msisdn": "+25377000002",
"amount": 9800,
"provider": "dmoney",
"reference": "comm_may_agt_002",
"description": "Commission payout — agent 002"
}
]
}'201 Created — pending_approvaljson
{
"id": "batch_01HZGK3MNP7Q2W9RX5YE8TF4JD",
"status": "pending_approval",
"currency": "DJF",
"row_count": 2,
"total_amount_minor": 22300,
"description": "May 2026 agent commissions",
"created_at": "2026-05-25T11:00:00Z",
"created_by": "key_requester_..."
}POST /v1/payouts/bulk/{id}/approvebash
curl -X POST https://api.merashub.com/v1/payouts/bulk/batch_01HZGK3MNP7Q2W9RX5YE8TF4JD/approve \ -H "Authorization: Bearer sk_live_approver_..." # Must be a different key from the one that created the batch
200 OK — processingjson
{
"id": "batch_01HZGK3MNP7Q2W9RX5YE8TF4JD",
"status": "processing",
"approved_at": "2026-05-25T11:04:21Z",
"approved_by": "key_approver_...",
"row_count": 2,
"rows_succeeded": 0,
"rows_failed": 0,
"rows_pending": 2
}ℹ
Handling failed rows
Failed individual payouts within a batch do not block others. Check
payout.failed webhooks and retry specific po_ IDs via POST /v1/payouts/{id}/retry. Each retry reuses the original row reference as the idempotency key, so duplicate retries are safe.