Bulk Payouts
- Base path
/api/v1/wallet- Identity
- Ed25519 request signature —
X-Key-Id,X-Date,X-Nonce,Digest(when there is a body),X-Signature. ±5 min clock window, single-use nonce, optional per-key IP allow-list. - Authorization
- The key must carry the wallet product and the wallet permission the call requires; a call without it gets
403 WALLET_INSUFFICIENT_SCOPE. Permissions per endpoint: Key permissions. - Cross-surface
- Nine bulk-payout endpoints exist on s2s and are SHARED with the panel. 5 panel endpoints have no s2s equivalent:
POST bulk-payouts/:id/cancel,GET bulk-payouts/:id/preview,GET bulk-payouts/:id/statement,GET bulk-payouts/counts,GET bulk-payout-statements/period.
Bulk payouts let you pay many recipients from a single list — payroll, a refund batch, campaign rewards, dealer commissions. The batch is approved as a whole, items are processed one by one, and each item carries its own status.
Batch creation, the approval chain and item tracking are built. Batches whose targets are inside the wallet work in the closed loop; items targeting an IBAN require a connected bank integration. While no integration is connected, IBAN-targeted items are not accepted. Status: Roadmap.
Flow
PARTIALLY_FAILED is an item failure, not a batch one: the items that passed were paid. Do not recreate the batch — use retry-failed, or the successful items get paid twice.Create the batch
POST /api/v1/wallet/bulk-payoutsSend an Idempotency-Key header with this call; the submit, approve, reject, process and retry-failed calls do not take one.
| Field | Type | Required | Description |
|---|---|---|---|
programId | string | ✅ | |
sourceWalletAccountId | string | ✅ | Account the payouts leave from |
currency | string | ✅ | Batch-wide currency |
items | array | ✅ | Line items |
reason | string | — | Audit reason |
Each item:
| Field | Type | Required | Description |
|---|---|---|---|
targetType | string | ✅ | Target kind (wallet / IBAN) |
targetRef | string | ✅ | Wallet number, phone or IBAN |
beneficiaryName | string | — | Expected for IBAN targets |
amount | string | ✅ | Minor-unit |
{
"programId": "5b9e2f3a-7c41-4d8e-a6f2-9e1b3c4d5f60",
"sourceWalletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
"currency": "TRY",
"reason": "July dealer commissions",
"items": [
{ "targetType": "WALLET", "targetRef": "90455132", "amount": "125000" },
{ "targetType": "WALLET", "targetRef": "+905339876543", "amount": "87500" }
]
}Submit for approval
POST /api/v1/wallet/bulk-payouts/{id}/submitApprove or reject
POST /api/v1/wallet/bulk-payouts/{id}/approve # { "approve": true, "reason": "…" }
POST /api/v1/wallet/bulk-payouts/{id}/reject # { "reason": "…" }Maker-checker is mandatory: the person who created the batch cannot approve it. This is enforced by principal comparison, not a role gate — even though OWNER/ADMIN hold both permissions, the same user cannot approve their own batch.
On the s2s surface the approver is the signing key: approve with a different key from the one that created the batch. The maker key needs wallet:bulk:create and wallet:bulk:approve (to create and submit); the checker key needs wallet:bulk:approve. See Key permissions.
Process
POST /api/v1/wallet/bulk-payouts/{id}/processItems are processed in order. While running, the batch is PROCESSING.
Retry failures
POST /api/v1/wallet/bulk-payouts/{id}/retry-failedOnly failed items are retried; successful items are never paid twice.
Tracking
GET /api/v1/wallet/bulk-payouts # batches (filter by programId, status)
GET /api/v1/wallet/bulk-payouts/{id} # batch summary
GET /api/v1/wallet/bulk-payouts/{id}/items # items (filter by status)Item status is independent of batch status: a COMPLETED batch may still contain failed items. Record payments in your own system from item statuses, not the batch status.
If the balance runs short, the batch is processed partially. Never recreate a PARTIALLY_FAILED batch from scratch — use retry-failed, otherwise the successful items get paid a second time.
Statements
For the accounting statement of payout batches:
GET /api/panel/v1/wallet/bulk-payouts/:id/statement # single batch
GET /api/panel/v1/wallet/bulk-payout-statements/period # periodThe period statement is filtered by source wallet account (sourceWalletAccountId) and date range (fromDate, toDate — defaults to the last 30 days).
The period statement path is bulk-payout-statements/period — the trailing /period segment is mandatory. Dropping it and calling bulk-payout-statements hits an endpoint that does not exist.
Panel side: Panel — Bulk Payouts.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Approval rejected | Approver is the same person as the creator | A different operator must approve |
Batch PARTIALLY_FAILED | Some targets invalid or balance insufficient | Inspect the item list, run retry-failed |
| The same payout went out twice | The batch was recreated | Use retry-failed, not a new batch |
| IBAN-targeted items rejected | No bank integration connected | See the roadmap box above |