Payouts
Send money out from a seller wallet to a registered payee's payout method.
A payout moves money from one of your seller wallets to a payee's registered bank account. Payouts settle asynchronously through the banking provider using the route selected from the payout currency and destination identifiers.
Important
Supported routes are EUR IBAN → SEPA, GBP UK local account → Faster Payments, and GBP/USD IBAN → cross-border transfer. UK local methods must use
currency: GBP,bankAccountType: uk_local,bankCountry: GB, an 8-digit account number, and a 6-digit sort code. US-local and SWIFT payout methods can be registered but are rejected before funds are held.
Note
Amount: decimal preferred. Send the decimal
amountas a string (e.g."19.99") — the same convention as the rest of the API. The legacy integeramountMinor(e.g.1999) is still accepted as a fallback for older integrations; when both are sent,amountwins. Exactly one is required.
Note
Idempotency precedence: body over header.
POST /payoutsreadsidempotencyKeyfrom the request body; theIdempotency-KeyHTTP header is used as a fallback when the body field is empty. When both are set, the body value wins.
Quick path: onboard and pay a new payee
Starting from scratch? The convenience endpoints collapse the full flow into a short, self-contained sequence using a dedicated cashout_balance wallet — funded directly from your balance, isolated from split earnings. The same supported bank routes and Bearer auth apply as for the primitive payout flow.
Important
This flow provisions a
cashout_balancewallet, so on a Live account it is gated per seller bywalletCreationEnabled(off by default). If it is not enabled for your account,POST /payees/setupreturns403 wallet creation is not enabled for this account; contact support. Contact support to enable it. A Demo account needs no switch: wallet creation is always on there.GET /accountreports the effective value asfeatures.walletCreation.
1. Create the payee, its payout method, and a cashout wallet — POST /payees/setup (one atomic call):
curl -sS -X POST "$FYNEX_API/payees/setup" \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Acme Supplies Ltd",
"role": "contractor",
"payoutMethod": { "currency": "EUR", "iban": "DE89370400440532013000" }
}'
Response (201 Created) returns all three records — keep payee.id, payoutMethod.id, and cashoutWallet.id:
{
"payee": { "id": 101, "role": "Contractor" },
"payoutMethod": { "id": 501, "status": "active" },
"cashoutWallet": { "id": 139, "type": "cashout_balance", "currency": "EUR", "status": "active" }
}
2. Fund the cashout wallet from your main balance — POST /wallets/{cashout_wallet_id}/fund (internal, same-currency):
curl -sS -X POST "$FYNEX_API/wallets/139/fund" \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amount": "12.50", "idempotencyKey": "'$(uuidgen)'" }'
Moves funds from your seller main wallet (same currency) into the payee's cashout wallet. Your main wallet must hold sufficient EUR.
Note
This step is optional for sellers enabled for payee payout auto-funding. On
POST /payees/{payee_id}/payouts, Fynex atomically moves only the cashout wallet's shortfall from the seller's same-currencymainwallet and then holds the payout amount. If either operation fails, neither movement is committed. On a Live account auto-funding is off by default and must be enabled by Fynex; on a Demo account it is always on. It does not apply to the primitivePOST /payoutsendpoint. If the payout is definitively rejected or cancelled, the auto-funded shortfall is returned to the seller's main wallet in the same transaction as the payout hold release. An ambiguous provider outcome stays held until it is reconciled, so the platform never returns funds that the provider may still settle. When bothpayeePayoutAutoFundingEnabledandwalletCreationEnabledare enabled for the seller (both are always on for a Demo account) and the payee has nocashout_balancewallet in the payout method's currency, this endpoint first provisions one automatically and then auto-funds it. This also applies when the payee already has an ordinaryotherortaxwallet, because those split-destination wallets are intentionally not debited by auto-funding. The seller's ownItselfpayee is excluded: its payouts keep using the existing sellermainwallet and never trigger cashout provisioning. Provisioning is idempotent and remains separate from the money-movement transaction, so a payout failure may leave the empty wallet available for a safe retry but cannot leave a partial ledger movement.
3. Pay the payee — POST /payees/{payee_id}/payouts (wallet auto-resolved):
curl -sS -X POST "$FYNEX_API/payees/101/payouts" \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"payoutMethodId": 501,
"amount": "12.50",
"description": "Invoice 2026-091 September commission",
"idempotencyKey": "'$(uuidgen)'"
}'
You don't pass a walletId — the payout draws from the payee's cashout_balance wallet (resolution prefers it). Response is the same 201 Created payout body as POST /payouts (status pending). Poll GET /payouts/{id} for the final status (see step 3 of the primitive flow below).
Note
payoutMethodIdis required — there is no implicit default. The convenience layer auto-resolves thewalletId; holds, idempotency, and banking-provider settlement use the same payout engine asPOST /payouts. Unless the seller account is enabled for payee payout auto-funding, a payout before the cashout wallet is funded returns409 insufficient balance.
The four primitive endpoints below still work unchanged when you need finer control (an existing payee, a specific source wallet, multiple payout methods, or the split-funded other wallet).
Prerequisites
- A seller wallet with sufficient balance in the target currency.
- A registered payee — create one with
POST /payments-api/v1/payees(see Payees) if you don't have one yet. - That payee must have at least one active payout method (bank account). Create one with
POST /payments-api/v1/payees/{payee_id}/payout-methods(see Payout Methods), then useGET /payees/{payee_id}/payout-methodsto look up the available methods and their ids.
Note
The step-by-step below assumes the payee and payout method already exist. If you are starting from scratch, use the quick path above, or follow the full order: create payee → create payout method → request payout.
Step-by-step
- Find the payee's payout methods
payee_id is the numeric payee ID from GET /payees.
curl -sS "$FYNEX_API/payees/42/payout-methods" \
-H "Authorization: Bearer $FYNEX_TOKEN" | jq
The response lists bank accounts with their id, currency, bankAccountType, and destination identifiers. Note the payoutMethodId you want to pay to, and the walletId you want to debit (confirm it has enough balance).
- Create the payout
curl -sS -X POST "$FYNEX_API/payouts" \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"walletId": 15,
"payoutMethodId": 501,
"amount": "1999.00",
"currencyCode": "EUR",
"description": "Invoice 2026-091 September commission",
"idempotencyKey": "'$(uuidgen)'"
}'
Response (201 Created):
{
"id": 9001,
"status": "processing",
"amountMinor": 199900,
"currencyCode": "EUR",
"providerReference": "ref_9f2c1a4b",
"bankAccountType": "uk_local",
"description": "Invoice 2026-091 September commission"
}
Save the id — you will need it to poll status.
- Poll for the final status
Poll GET /payouts/\{id\} until status reaches a terminal value, or subscribe a webhook endpoint to the opt-in PayoutCompleted and PayoutFailed events (see Webhooks).
curl -sS "$FYNEX_API/payouts/9001" \
-H "Authorization: Bearer $FYNEX_TOKEN" | jq .status
Poll every 30–60 seconds until status is completed, failed, or cancelled.
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
walletId |
int64 |
Yes | Internal numeric id of the seller wallet to debit. Not an IBAN or any external bank-account identifier — look up the id from the dashboard's wallets list (contact support if you do not yet have dashboard access). Must belong to the authenticated seller. |
payoutMethodId |
int64 |
Yes | Explicit payout method id from GET /payees/{id}/payout-methods. Must belong to the wallet's payee and be active. There is no implicit default — a missing or zero value is rejected with 400. (This removed the old wallet-default → payee-default → first-method fallback so a payout can never silently re-route.) |
amount |
string |
Yes* | Decimal amount in the payout currency, e.g. "19.99". Preferred over amountMinor. |
amountMinor |
int64 |
Yes* | Deprecated. Amount in minor units (e.g. 1999 for €19.99). Fallback when amount is omitted; ignored when amount is set. |
currencyCode |
string |
Yes | EUR, GBP, or USD, matching both the wallet and payout method. The destination shape selects SEPA, Faster Payments, or cross-border routing. |
description |
string |
No (will become required) | What the beneficiary sees on their bank statement, sent to the bank as remittance information. Send it on every payout now: it will become required. When supplied, leading/trailing whitespace is trimmed and internal runs collapse to one space; the result must be 1–140 characters of a-z A-Z 0-9 / - ? : ( ) . , ' + and space. Anything else — including an empty or blank string — is rejected with 400, never transliterated. It is split into up to four 35-character lines at word boundaries where possible. |
idempotencyKey |
string (UUID) |
No | If omitted, a UUID is auto-generated. Replays with the same key return the original payout; a replay with a different description returns 409. |
* Exactly one of amount or amountMinor is required.
Note
The
Idempotency-KeyHTTP header is read as a fallback when the body'sidempotencyKeyis empty. When both are set, the body wins.
SEPA payouts — mandatory parameters
EUR payouts are sent as SEPA credit transfers through the banking provider (payment scheme SEPA, charge bearer SHA). The required parameters span two calls — registering the destination payout method and creating the payout itself.
1. Payout method (destination) — POST /payees/{id}/payout-methods (or via the dashboard):
| Field | Required | Notes |
|---|---|---|
payeeId |
Yes | Payee that owns the destination. Over REST it comes from the {payee_id} path segment (not the body); over GraphQL it is a body field. |
currency |
Yes | Must be EUR. |
iban |
Yes | Destination IBAN — the only hard-required bank field. A payout to a method with no IBAN is rejected (payout method has no IBAN). The creditor country is derived from the IBAN prefix. |
bic |
No | Recommended. Forwarded to the banking provider as the creditor institution only when present. |
accountName |
No | Recommended — used as the SEPA creditor name. Falls back to bankName, then Payee {id} when omitted. |
The payout method type is bank_account — the only supported value.
2. Payout request — POST /payouts:
| Field | Required | Notes |
|---|---|---|
walletId |
Yes | Seller EUR wallet to debit; must hold sufficient EUR. |
payoutMethodId |
Yes | The EUR IBAN method from step 1. Must belong to the wallet's payee and be active — there is no implicit default. |
amount |
Yes* | Decimal in the payout currency ("1999.00" = €1,999.00). Preferred over amountMinor. |
amountMinor |
Yes* | Deprecated minor-units fallback (199900 = €1,999.00). *Exactly one of amount/amountMinor. |
currencyCode |
Yes | Must be EUR. |
description |
No (will become required) | 1–140 characters of a-z A-Z 0-9 / - ? : ( ) . , ' + and space; sent as the SEPA remittance information. |
idempotencyKey |
No | UUID; auto-generated if omitted. |
Note
The banking provider's payment scheme (
SEPA), payment reference, requested execution date, charge bearer (SHA), creditor country, and the unstructured remittance line are all set by Fynex — you do not send them.
The payout transitions through pending → processing → completed once the banking provider confirms the SEPA settlement.
GBP Faster Payments — mandatory parameters
GBP payouts to a UK local account are sent through the banking provider's Faster Payments rail. The client supplies domestic bank identifiers; Fynex normalizes them and applies the banking provider's national-clearing-code representation at dispatch.
1. Payout method (destination) — POST /payees/{id}/payout-methods (or via the dashboard):
| Field | Required | Notes |
|---|---|---|
currency |
Yes | Must be GBP. |
bankAccountType |
Yes | Must be uk_local. |
bankCountry |
Yes | Must be GB. |
accountNumber |
Yes | Eight-digit UK account number. Spaces are accepted and removed. |
sortCode |
Yes | Six-digit UK sort code. Spaces and hyphens are accepted and removed. |
accountName |
No | Recommended; used as the creditor name. |
2. Payout request — provide a GBP walletId, the UK-local payoutMethodId, the amount, and currencyCode: GBP. Bank identifiers stay on the payout method and are not repeated in the payout request.
Note
The banking provider's payment scheme and the
SCnational-clearing-code prefix are set by Fynex. Clients send the six sort-code digits only.
Caution
Card payouts (push-to-card) are not supported. Payouts settle only to bank accounts through the banking provider. There is no card-out / OCT endpoint.
Response fields
POST /payouts (201 Created)
The create response is intentionally minimal:
| Field | Type | Description |
|---|---|---|
id |
int64 |
Internal payout ID — use this for GET /payouts/\{id\} |
status |
string |
pending with approvalStatus: "awaiting_approval" when the payout needs review (the default on a Live account — see Approval below); otherwise usually processing |
approvalStatus |
string |
Review state: approved, awaiting_approval, compliance_review or rejected — see Approval below |
amountMinor |
int64 |
Minor units |
currencyCode |
string |
|
providerReference |
string |
Upstream provider's reference for this payout. Opaque — match, do not parse (omitempty) |
bankAccountType |
string |
Destination account identifier format: iban, uk_local, us_local, swift. Empty means unknown (omitempty) |
description |
string |
The normalized description sent to the bank. Omitted for payouts created before descriptions were required (omitempty) |
GET /payouts/{id} (200 OK)
The detail response adds timing fields:
| Field | Type | Description |
|---|---|---|
id |
int64 |
|
status |
string |
Current status |
approvalStatus |
string |
Review state — see Approval below |
rejectionReason |
string |
Set when the payout was rejected in review; always the same support message, never the reviewer's note (omitempty) |
approvedAt |
string |
RFC3339 UTC, when the payout was approved (omitempty) |
amountMinor |
int64 |
|
currencyCode |
string |
|
providerReference |
string |
Upstream provider's reference for this payout. Opaque — match, do not parse (omitempty) |
bankAccountType |
string |
Destination account identifier format: iban, uk_local, us_local, swift. Empty means unknown (omitempty) |
description |
string |
The normalized description sent to the bank. Omitted for payouts created before descriptions were required (omitempty) |
failureCode |
string |
Set when the payout fails (omitempty) |
failureMessage |
string |
Seller-safe explanation of failureCode, suitable to show as-is. Present on failed payouts and on cancelled ones — "payout cancelled before approval" when you cancelled it, "Our system paused your payout. Please contact support." when it was rejected in review — both with no failureCode; absent otherwise (omitempty) |
requestedAt |
string |
RFC3339 UTC |
processedAt |
string |
RFC3339 UTC (omitempty) |
completedAt |
string |
RFC3339 UTC (omitempty) |
Note
Branch on
failureCode, displayfailureMessage. The message is the fixed catalogue text for the code — never raw bank or operator notes — so it is stable enough to show to a seller but not meant to be parsed.
Status values
| Status | Meaning |
|---|---|
pending |
Created in Fynex, queued for processing |
processing |
Submitted to the payment network |
completed |
Funds left the wallet and reached the destination |
failed |
Network rejected the payment; balance returned to wallet |
cancelled |
Payout was cancelled before processing |
Approval
Payouts above your account's auto-approve limit — or held by another control on your account — wait for review by Fynex before they are processed. Such a payout is returned with status: "pending" and approvalStatus: "awaiting_approval"; approvalStatus is a separate field, not a status value, and it is present on every new payout. Unless Fynex has set an auto-approve limit for your account, the limit is 0, so every payout on a Live account starts awaiting approval.
On a Demo account every payout is approved automatically, whatever its amount and wherever it was created, so a test payout runs to completion with nobody at Fynex involved. A payout to a marketplace vendor can still be held by the vendor limit.
The same holds for payout methods: on a Demo account a bank account you add is approved at creation, so you can pay out to it at once. On a Live account a new or re-routed payout method starts pending unless Fynex has enabled auto-approval for your account. When a Demo account goes Live, every payout method nobody at Fynex reviewed returns to pending and must be approved before it pays out.
approvalStatus |
Meaning |
|---|---|
approved |
Released for processing — either approved automatically or after review (approvedAt is set on the detail response). |
awaiting_approval |
Waiting for review. You can still cancel it with POST /payouts/{id}/cancel; it then becomes cancelled with failureMessage "payout cancelled before approval". |
compliance_review |
Held for an automated compliance check. It cannot be cancelled (409) and resolves on its own. |
rejected |
Rejected in review: the payout becomes cancelled with failureMessage "Our system paused your payout. Please contact support." |
Listing payouts
curl -sS "$FYNEX_API/payouts?limit=20&offset=0" \
-H "Authorization: Bearer $FYNEX_TOKEN" | jq
Returns payouts ordered by requestedAt descending. Query params limit (1–100, default 20) and offset (default 0).
Insufficient balance
If the wallet's withdrawable balance does not cover amountMinor, the request returns 409 Conflict:
{ "error": "insufficient balance" }
The check is against withdrawableBalanceMinor, not availableBalanceMinor. Settled funds stay inside a T+N maturity hold (the seller's payoutHoldDays, 3 business days by default) before a payout can take them, so this 409 often means the money is in the wallet but has not matured yet — common for a marketplace paying out shortly after a split. A Demo account has no hold: its settled funds are withdrawable at once. Check the wallet first (see Wallets):
maturingBalanceMinorcovers the shortfall → wait until those funds mature, then retry with the sameidempotencyKey. A top-up is not held and would also unblock it, but it is unnecessary.- Otherwise the wallet really is short → top it up via incoming payments or a treasury transfer, then retry with the same key.
For an auto-funded payee payout (POST /payees/{id}/payouts), the check covers the cashout wallet plus your main wallet's withdrawable balance, so check the main wallet's maturingBalanceMinor too. A separate 409 merchant balance cannot cover the payout fee means the payout fits but its fee does not.
Replaying a successful create with the same idempotencyKey returns the original payout — it does not create a second one and does not return 409.
Common errors
| Status | Body | Cause |
|---|---|---|
400 |
walletId is required |
Missing or zero walletId |
400 |
payoutMethodId is required |
Missing or zero payoutMethodId (no implicit default) |
400 |
amount must be positive |
Zero or negative amount |
400 |
currencyCode is required |
Missing currency |
400 |
description is required |
description supplied as an empty or whitespace-only string |
400 |
description must be at most 140 characters |
description longer than 140 characters after whitespace normalization |
400 |
description contains unsupported characters; allowed: letters, digits, space and / - ? : ( ) . , ' + |
description contains any other character (accents, &, _, quotes, emoji, …) |
409 |
idempotency key was already used with a different description |
Same idempotencyKey replayed with a different description |
401 |
authorization token is required / invalid authorization token (code missing_api_key / invalid_api_key) |
Missing or invalid bearer token |
403 |
wallet does not belong to seller account |
walletId belongs to another seller |
404 |
wallet not found for this currency |
walletId exists but currency mismatches |
404 |
payee not found |
payee_id doesn't exist or belongs to another seller |
409 |
insufficient balance |
Withdrawable balance below the requested amount — funds missing, or still inside the maturity hold |
409 |
merchant balance cannot cover the payout fee |
The main wallet's withdrawable balance does not cover the payout fee |
422 |
currency not supported for payouts |
currencyCode has no enabled payout rail |
422 |
this payout account is saved, but payouts for its account format are not enabled yet |
The payout method identifier format is not enabled for that currency, or its required bank identifiers are invalid |
422 |
payout method does not belong to this wallet's payee |
payoutMethodId belongs to a different payee than the wallet |
422 |
provider rejection message | The banking provider rejected the payment at create time; check failureCode via GET /payouts/{id} |
See also
- Polling & SSE — Poll payout status or subscribe to status events.
- Idempotency & retries — Safe retry patterns for payout creation.
- Split rules — Splitting incoming funds across multiple payees? Use Split rules.
- Virtual accounts — Need a dedicated bank account for incoming payments? See Virtual accounts.