x-api-key + x-timestamp headers — and the Quote and Create endpoints additionally require the x-signature request signature. See Authentication for both.
Typical flow: list banks → quote → create → poll get. Status updates after create (SUCCESS/FAILED) also arrive via Disbursement Webhooks.
1. List available banks
GET /api/v3/wallets/{wallet_id}/banks
Call this first to obtain valid bank_code values for the wallet’s payout vendor.
Depending on the wallet’s payout vendor, the list can include e-wallets (e.g.
dana, gopay, ovo, linkaja, shopeepay) alongside banks. E-wallets are disbursed through the same endpoints — pick the e-wallet’s code as bank_code and use the recipient’s registered phone number as account_no (see Account number for e-wallets).2. Quote a disbursement (non-binding fee preview)
POST /api/v3/disbursements/quote
Request
Account number for e-wallets
account_no is forwarded to the payout vendor as-is (no reformatting on our side) and verified in real time via the vendor’s account inquiry — both Quote and Create run this inquiry, and the verified account_holder_name is echoed back so you can confirm the recipient before money moves.
- Bank codes: the recipient’s bank account number, digits only.
- E-wallet codes (
dana,gopay,ovo, …): the phone number the e-wallet account is registered with, digits only in local Indonesian format — e.g.081234567890(leading0, no+62, no spaces or dashes).
422 — "bank account verification failed" for an unknown account, "bank account is inactive" for a known-but-inactive one. Because Quote runs the same inquiry with no side effects, it’s the safe way to validate a recipient (and their name) before creating the disbursement.
Response
3. Create a disbursement
POST /api/v3/disbursements
Request
Response
Status codes:
201 Created— fresh disbursement created.200 OKwith headerIdempotent-Replayed: true— theIdempotency-Keywas already used and the disbursement isSUCCESSorREQUESTED; the stored result is returned. Replays of unsuccessful disbursements answer with an error status instead — see below.
Idempotency & replay semantics
A replay is faithful: retrying with the sameIdempotency-Key and the same parameters answers exactly as the original attempt did — same HTTP status, same body — with the Idempotent-Replayed: true response header as the only addition. Do not treat every replay as a success; key your success handling off the HTTP status:
Parameter fingerprint. Reusing a key with different parameters returns
422 and performs nothing — this protects you from believing a new payload was disbursed when only the old one was. The compared fields are client_wallet_id, amount (scale-insensitive: "100000" equals "100000.00"), bank_code (case-insensitive), account_no (whitespace-trimmed), and transfer_type. memo is not compared. On a 422, either resend the original payload with that key, or use a new key for the new payload.
Concurrency. Two simultaneous creates with the same key can race; the loser may get 409 “concurrent request with the same Idempotency-Key; retry” — safe to retry with the same key.
Other error codes on Create:
4. Get a disbursement
GET /api/v3/disbursements/{id}
Returns the disbursement if it belongs to your client; otherwise 404. x-signature is optional here (and on List available banks) — sign it if you want, unsigned requests with a valid x-timestamp work too.
5. Disbursement status values
PENDING_VERIFICATION → VERIFIED → REQUESTED → SUCCESS, with terminal failure states FAILED, EXPIRED, and CANCELLED.
6. FAQ — prerequisites & common rejections
Why did I get 402 “Insufficient balance”?
Your wallet’s available balance must cover the full amount of the disbursement (the gross amount — the platform fee is deducted from it, not added on top).
Available balance is:
- Incoming payments only count after settlement. A QR payment that has been paid but not yet settled does not increase your available balance. Settlement typically lands the next business day (T+1). To top up: receive QR payments into the wallet, then wait for settlement before disbursing.
- In-flight disbursements lock their full amount. Every disbursement that has not reached a terminal state (
SUCCESS/FAILED/EXPIRED/CANCELLED) holds a lock on its amount, reducing what’s available for new ones. Locks are released when the disbursement finishes or expires.
Why did I get 422 “bank account verification failed” or “bank account is inactive”?
account_no is verified against the receiving bank in real time (a bank inquiry) before the disbursement is created — an invalid or inactive account is rejected up front, and nothing is charged.
Common causes:
Tip: the Quote endpoint runs the same verification without creating anything — use it to validate the account (and preview the account holder’s name) before the real create.
What are the minimum and maximum disbursement amounts?
Minimum. The minimum applies to the net amount (youramount minus the platform fee) and depends on transfer_type:
Because the minimum is on the net amount, the minimum
amount you can send is slightly higher and depends on your fee configuration. You don’t need to compute it yourself:
- A too-small create (or quote) fails with
422"withdrawal amount is below minimum: <X>"— where<X>is the exact minimum grossamountfor your account and that transfer type. - The Quote endpoint returns your fee and net amount for any candidate
amount, so you can verify before creating.
BIFASTtransfers are subject to the BI-FAST network’s per-transaction cap set by Bank Indonesia.RTOL(online transfer) caps are lower and vary per receiving bank.
503 "Vendor payout request failed" — no money moves and your balance is unaffected (the balance lock is released when the disbursement fails). For amounts near or above network caps, split the payout into multiple disbursements — each with its own Idempotency-Key — or contact support to confirm the current limits for your route.
Your practical ceiling is also bounded by your available balance (see the 402 question above) — the full amount must be covered at create time.