Skip to main content
POST
Withdraw Funds
Initiate a withdrawal from your wallet to a beneficiary. You can reference a saved beneficiary by ID or provide inline beneficiary details for a one-time transfer. Either beneficiaryId or inlineBeneficiary must be provided. The required fields inside inlineBeneficiary depend on the currency and withdrawal_method.
Paying out for a specific merchant (tenant keys): if your API key is a white-label tenant key managing multiple merchants, add the X-Account-Id header set to the merchant’s account ID — the payout is then deducted from that merchant’s wallet. Without it, the withdrawal runs against your own (parent) account. The account ID is the id returned by POST /accounts (or from GET /accounts).
For USD, the top-level amount is US cents — e.g. 100 cents = $1.00 USD transferred (consistent with ledger *_amount fields returned on transactions).
IP whitelisting — this endpoint requires your API key to have at least one whitelisted IP configured. Without one the request is refused with 403 and the message "This endpoint requires IP whitelisting. Add at least one whitelisted IP to your API key before using withdraw."

Top-level Parameters

* One of beneficiaryId or inlineBeneficiary is required.

inlineBeneficiary — Required Fields by Currency

NGN


Choosing a USD rail

USD payouts run on one of three bank rails, selected with withdrawal_method. The choice drives settlement speed, the fields you must supply, and the fee — so pass it explicitly on both the quote and the withdrawal.
Fees are configured per rail, so ach and domestic_wire to the same bank generally cost different amounts. Send withdrawal_method (or beneficiaryId) to POST /wallet/fee to quote the exact fee this withdrawal will be charged.

USD — ACH

Unlike international_wire, ACH needs no supporting documents — send it as a normal JSON body.
routing_number is required for ach and domestic_wire. Omitting it returns a 400 with "routing_number is required for ACH and domestic wire withdrawals".This is newly enforced. It has always been documented as required for domestic_wire, but the server previously accepted domestic wire requests without it and the payout then stranded. If you have been omitting it, those requests now fail fast instead.

USD — Domestic Wire


USD — International Wire

USD international wire requires reference documents/invoices to be attached, so this is the one withdrawal type that must be sent as multipart/form-data (every other currency/method uses a JSON body). Include one or more files under the documents field.
Send inlineBeneficiary as a single JSON-stringified field — one form field named inlineBeneficiary whose value is the full JSON object. Do not spread it across bracketed fields like inlineBeneficiary[account_name]=…; multipart form fields are not reconstructed into a nested object, so the request will fail with 400 "Either beneficiaryId or inlineBeneficiary must be provided".

USD — Crypto (USDT / USDC)

Crypto withdrawals are sent from your USD wallet. Set withdrawal_method to "crypto_usdt" or "crypto_usdc" to send the USD value as a stablecoin to a crypto wallet address.

XAF


Choosing the payer name

A payout reaches the beneficiary under your own business name by default. If Named Payouts is enabled for your account, pass payerNameId to send it under one of your registered affiliated payer names instead — a related entity of yours, or the end-customer you are paying on behalf of.
payerNameId is accepted on USD international wire only. Attaching one to any other rail — ACH, domestic wire, crypto, NGN, XAF, mobile money — is refused with a 400:
Because international wire is the one rail that must be sent as multipart/form-data, a payout carrying payerNameId is always a multipart request — never a JSON body.
The beneficiary referenced by beneficiaryId must itself be an international_wire beneficiary; the rail is read from the beneficiary when you do not send inlineBeneficiary. Omit payerNameId and the payout uses your default payer name if you have set one, and your own business name if you have not. The id must be a payer name on your own account, and it must not be rejected or archived. Every one of those is refused here with a 400 and no payout is created: a rejected or archived name with "That payer name was rejected and cannot be used" / "That payer name has been archived and can no longer be used", and an id that does not exist or belongs to another account with "Payer name not found". Note that this endpoint answers 400 for the unknown id too, rather than 404: a payer name is scoped to your own account, so an id you cannot use simply reads as an unusable name on the payout you tried to create. A name that has not finished registration yet is accepted, but the payout waits until the name is usable before the money moves, so send time-sensitive payments only under a name reported as active. The Named Payouts guide covers the statuses and the webhook that tells you when a name is ready.
Affiliated payer names must be registered and reviewed before they can be used — you cannot pass a free-text payer name on a payout. Register them with Create Payer Name.

Attaching your own metadata

Pass a metadata object to carry your own identifiers on the payout — an order id, a ledger key, whatever you reconcile against. Rolla stores it verbatim and never interprets it, and returns it unchanged on every response and webhook for that transaction, so you can match our record to yours without keeping a mapping table.
It comes back on the transaction and on each webhook for it:
Limits. The object is stored on the transaction and replayed on every webhook delivery for it, so it is bounded: Exceeding any of these returns 400 and the payout is not created.
Metadata is returned to anyone who can read the transaction and is sent to your webhook endpoints. Don’t put secrets, card numbers, or personal data in it that you wouldn’t want echoed back.
On multipart/form-data requests (USD international wire), send metadata as a single JSON-stringified form field, the same way inlineBeneficiary is sent.

Example Response

source_amount is what left your wallet and destination_amount is what the beneficiary receives. With the default deductFeesFromBalance: true, source_amount is amount + fee_amount and destination_amount equals the amount you sent. The beneficiary block is present only when the payout carried a beneficiary snapshot. Two other fields are conditional: metadata appears when you sent any, and uetr / tracking_codes appear on cross-border payouts once the rail returns them, which is never on the response to this call. For those, read the payout back from GET /wallet/transaction/{transactionId} or wait for the webhook rather than expecting them here.
Use beneficiaryId to pay a saved beneficiary. Use inlineBeneficiary for one-time transfers. Set saveBeneficiary: true to save the inline beneficiary automatically for reuse.
Fees: on this API, fees come out of your wallet balance by default and the beneficiary receives the exact amount you specified. Set deductFeesFromBalance: false to take the fee out of the withdrawal amount instead, so the beneficiary receives less than amount.

Authorizations

X-API-Key
string
header
required

Your Rolla API key

Body

Either beneficiaryId or inlineBeneficiary must be provided.

amount
number
required

Amount in smallest units (kobo for NGN; US cents for USD — 100 = 1 USD). Must be a whole number and at least 1.

Required range: x >= 1
Example:

5000

currency
string
required

Currency code of the wallet the payout leaves

Required string length: 3 - 4
Example:

"NGN"

description
string
required

Narration for the transaction. Required, 1-500 characters after trimming.

Required string length: 1 - 500
Example:

"Vendor payment"

externalReference
string

Your own unique reference for this transaction. Used on USD and other non-NGN bank rails. NGN and Mobile Money payouts always get a Rolla-generated reference instead (e.g. NG-NFNUJTUW), because the provider imposes its own format.

Maximum string length: 255
beneficiaryId
string<uuid>

ID of a saved beneficiary. Use this OR inlineBeneficiary.

inlineBeneficiary
object

One-time beneficiary details (use instead of beneficiaryId)

saveBeneficiary
boolean
default:false

Save the inline beneficiary for future use

deductFeesFromBalance
boolean
default:true

Defaults to true on this API: the fee is taken from your wallet balance on top of amount and the beneficiary receives the exact amount. Set false to take the fee out of the amount instead, so the beneficiary receives less.

metadata
object

Your own key/value data, stored verbatim on the transaction and returned on every response and webhook for it. At most 20 keys; keys up to 64 characters; values must be a string (up to 500 characters), number, boolean or null — not nested objects or arrays.

Example:
payerNameId
string<uuid>

Which of your registered affiliated payer names the beneficiary should see on this payout. Only an international wire can carry one; naming one on any other rail is a 400. Because international wire must be sent as multipart/form-data, a payout carrying payerNameId is always a multipart request. Omit to use your default payer name if you have set one, and your own business name if you have not. Requires Named Payouts to be enabled.

Response

Withdrawal initiated successfully

status
integer
Example:

200

message
string
Example:

"Withdrawal initiated successfully"

success
boolean
Example:

true

data
object