Payouts
A payout is the merchant's withdrawal of accumulated revenue from their Storlaunch balance to a bank account they control. Storlaunch supports two backing modes:
- Local manual payouts — merchant requests a payout, operator marks it in-transit, then paid. Used on Forjio's bookkeeping flow.
- Plugipay-backed payouts — once the merchant enables the Plugipay module, every payout endpoint here transparently proxies through to the Plugipay payouts API, which handles Xendit disbursement under the hood.
Storlaunch decides which backend to use per-request based on whether the payment module is active for the workspace.
All endpoints require an sk_* key.
Endpoints
Bank account
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/payouts/bank-account |
Read the configured bank account |
PATCH |
/v1/payouts/bank-account |
Update the bank account |
Balance
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/payouts/balance |
Available balance (settled revenue minus paid-out + pending) |
Payouts
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/payouts |
List payouts |
GET |
/v1/payouts/:id |
Retrieve a payout |
POST |
/v1/payouts |
Request a new payout |
POST |
/v1/payouts/:id/cancel |
Cancel a pending payout |
POST |
/v1/payouts/:id/mark-in-transit |
Mark in-transit (manual mode only) |
POST |
/v1/payouts/:id/mark-paid |
Mark paid (manual mode only) |
POST |
/v1/payouts/:id/mark-failed |
Mark failed (manual mode only) |
Bank account
GET /v1/payouts/bank-account
PATCH /v1/payouts/bank-account
PATCH body
| Field | Type | Required | Description |
|---|---|---|---|
bankCode |
string (≤32) | no | Bank code recognised by the disbursement provider (bca, mandiri, bni, ...). Required in Plugipay mode. |
bankName |
string (1–100) | yes | Human-readable bank name. |
bankAccountNumber |
string (1–50) | yes | The account number. We don't validate — Xendit does at disbursement time. |
bankAccountHolder |
string (1–100) | yes | Account holder's legal name. |
Response — 200 OK. Returns the configured bank account.
Balance
GET /v1/payouts/balance
{
"data": {
"available": 5230000,
"pending": 750000,
"currency": "IDR"
},
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
available— settled, withdrawable.pending— in payment-settlement (waiting for the provider to release funds, typically T+1 to T+3).
List / retrieve
GET /v1/payouts?status=…&limit=50&cursor=…
GET /v1/payouts/:id
Standard cursor pagination. Filter by status: pending, in_transit, paid, failed, cancelled.
Create
POST /v1/payouts
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount |
integer | yes | Smallest-unit. Must be > 0 and <= available. |
currency |
string (3) | yes | ISO 4217. Must match the balance currency. |
bankCode |
string | no | Override the configured bank code. |
bankName / bankAccountNumber / bankAccountHolder |
string | no | One-off override of the configured account (rare). |
note |
string (≤500) | no | Free text. |
Response — 201 Created. Returns the new payout row.
Errors specific to this endpoint
| Status | error.code |
When |
|---|---|---|
409 |
INSUFFICIENT_BALANCE |
amount > available. |
400 |
VALIDATION_ERROR |
Missing required field or wrong shape. |
409 |
BANK_ACCOUNT_NOT_SET |
No bank account configured and no one-off override supplied. |
curl -X POST https://storlaunch.forjio.com/api/v1/payouts \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"amount":5000000,"currency":"IDR","note":"April revenue"}'
Cancel
POST /v1/payouts/:id/cancel
Only valid while the payout is pending. Cancellation returns the amount to available immediately.
Status transitions (manual mode only)
POST /v1/payouts/:id/mark-in-transit
POST /v1/payouts/:id/mark-paid
POST /v1/payouts/:id/mark-failed
These exist for workspaces on the manual payouts backend, where a human operator is the disbursement layer. In Plugipay mode they're rejected with 409 — status comes from the disbursement provider's webhook instead.
| Body | Required | Description |
|---|---|---|
reference (in-transit / paid) |
no | Bank transfer reference number. |
failureReason (failed) |
yes | Free-text reason (Wrong account number, Customer suspected fraud, ...). |
The payout object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | pout_ + 26-char ULID. |
accountId |
string | no | The workspace. |
amount |
integer | no | Smallest-unit. |
currency |
string | no | ISO 4217. |
status |
enum | no | pending / in_transit / paid / failed / cancelled. |
bankCode |
string | yes | Bank code at issue time. |
bankName / bankAccountNumber / bankAccountHolder |
string | no | Snapshot of the bank account at request time. |
reference |
string | yes | Provider-side reference. Set when in_transit or paid. |
failureReason |
string | yes | Why it failed. |
note |
string | yes | Merchant note from creation. |
requestedAt |
string | no | Creation time. |
inTransitAt / paidAt / failedAt / cancelledAt |
string | yes | Lifecycle stamps. |
Events
Payout lifecycle events come from Plugipay when the payment module is active — subscribe to:
payout.initiatedpayout.paidpayout.failed
on your Plugipay endpoint, not Storlaunch's. The Storlaunch outbox does not mirror these events.
In manual mode, no public webhook events fire — poll the API.