Panel — Bulk Payouts
- Base path
/api/panel/v1/wallet- Identity
- Panel session (JWT) —
Authorization: Bearer <panel access token>. The token comes from panel login; an s2s signing key does NOT open these endpoints. - Authorization
- RBAC permission:
wallet:bulk:create·wallet:bulk:approveFour-eyes: the requester and the approver cannot be the same person — this is enforced by identity comparison, not by a role gate. - Cross-surface
- MOSTLY mirrored: 9 bulk-payout endpoints also exist in the s2s API (create, list, detail, items,
submit,approve,reject,process,retry-failed). Panel-only:POST bulk-payouts/:id/cancel,GET bulk-payouts/:id/preview,GET bulk-payouts/:id/statement,GET bulk-payouts/countsandGET bulk-payout-statements/period.
Runs many payouts — payroll, commissions, refund batches — from a single list.
Batches targeting wallets work today. IBAN-targeted items require a connected bank integration. Status: Roadmap.
Endpoints
GET bulk-payouts # batches (filter by programId, status)
POST bulk-payouts # create
GET bulk-payouts/:id # summary
GET bulk-payouts/:id/preview # preview before processing
GET bulk-payouts/:id/items # items
POST bulk-payouts/:id/submit # submit for approval
POST bulk-payouts/:id/approve # approve
POST bulk-payouts/:id/reject # reject
POST bulk-payouts/:id/process # process
POST bulk-payouts/:id/retry-failed # retry failures
POST bulk-payouts/:id/cancel # cancel
GET bulk-payouts/:id/statement # batch statement
GET bulk-payout-statements/period # period statement
GET bulk-payouts/counts # batch counts per statusStatus counters
GET bulk-payouts/counts # filter by programIdReturns server-side counts per status instead of making you page through the batch list.
{
"counts": [
{ "status": "PENDING_APPROVAL", "count": 12, "exact": true },
{ "status": "PROCESSING", "count": 500, "exact": false }
],
"cap": 500
}Two fields are easy to misread, and both mislead the operator:
exact: false⇒ the count hit the cap. Render it as "N+", never as an exact number.counts: [](empty array) ⇒ could not count, it does NOT mean "zero batches". Showing an empty list as "none" tells the operator they have nothing awaiting approval.
The counter endpoint uses the same permission as the list endpoint (wallet:bulk:create) and leaks nothing the list does not — it returns status and count only.
Preview
GET bulk-payouts/:id/previewAlways preview before submitting for approval. The preview shows invalid targets and the total before anything is processed. In a 500-line batch, one mis-mapped column can turn the whole list into payments to the wrong people.
Check: total amount, item count, invalid targets, source account balance.
Approval
POST bulk-payouts/:id/submit
POST bulk-payouts/:id/approve # { approve: true, reason? }
POST bulk-payouts/:id/reject # { reason }Maker-checker: the creator cannot approve. Enforced by principal comparison, not a role gate — even as OWNER/ADMIN you cannot approve your own batch.
Processing and retries
POST bulk-payouts/:id/process
POST bulk-payouts/:id/retry-failedNever recreate a partially failed batch — use retry-failed. Creating a new batch pays the successful items a second time.
Item status is independent of batch status: a COMPLETED batch can still contain failed items. Build your accounting records from item statuses.
Cancellation
POST bulk-payouts/:id/cancel # { reason }Cancels an unprocessed batch. Processed items are not reversed.
Statements
GET bulk-payouts/:id/statement
GET bulk-payout-statements/period # sourceWalletAccountId, date rangeBatch and period statements for accounting reconciliation.
Checklist
- Previewed; total and item count match expectations
- No invalid targets
- Sufficient balance on the source account
- Approved by a different operator
- Item statuses reviewed after processing
-
retry-failedused for failures (not a new batch)
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
| The same payout went twice | The batch was recreated | Use retry-failed |
| Batch partially processed | Insufficient balance or invalid targets | Inspect the item list |
| Approval rejected | Approver equals creator | A different operator must approve |
| IBAN items rejected | No bank integration connected | See the roadmap box |