Docs
API 2026-05-27.dahlia Dashboard

Payments

Balance and payouts

Use with AI

Read your balance, see your statement, and pay out to your bank.

Every sale you take adds to your store's balance at Fluveo, kept in your store's currency. A new sale sits in pending for a while. Once it settles it moves to available, and only available money can leave for your bank. This page reads the balance, sends a payout and follows it until the money lands.

Read your balance#

GET /v1/balance returns two lists, each with one entry per currency, in the smallest unit of the currency:

  • available: what you can send to your bank right now.
  • pending: sales that have not settled yet.

available can go below zero. It is what your account holds less any amount your store owes Fluveo. A dispute takes its amount and its fee as soon as it opens, and a refund the balance did not cover counts while it waits. A refund or a dispute can take out more than the balance held, and the balance then shows the real, negative number, never zero. While available is below a payout's amount, the payout is refused with balance_insufficient, or with payouts_not_allowed when the account cannot pay out now for another reason.

Read your balance
curl https://api.fluveo.com/v1/balance \
  -H "Authorization: Bearer sk_test_..."
const response = await fetch("https://api.fluveo.com/v1/balance", {
  headers: { Authorization: "Bearer sk_test_..." },
});
const balance = await response.json();
console.log(balance.available, balance.pending);
import requests

response = requests.get(
    "https://api.fluveo.com/v1/balance",
    headers={"Authorization": "Bearer sk_test_..."},
)
balance = response.json()
print(balance["available"], balance["pending"])
<?php
$ch = curl_init("https://api.fluveo.com/v1/balance");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$balance = json_decode(curl_exec($ch), true);
print_r($balance["available"]);

The answer, trimmed:

Response (trimmed)
{
  "object": "balance",
  "available": [{ "amount": 12500, "currency": "usd" }],
  "pending": [{ "amount": 4000, "currency": "usd" }],
  "livemode": false
}

When pending money becomes available, Fluveo sends one balance.available event, whose data.object is your balance as GET /v1/balance shows it at that moment. Listen for it if you pay out automatically.

Refunds and disputes also draw on your balance. See Refund a payment.

What a payout may take#

Available money is not always free to leave. A payout already on its way, a hold on your account, or an amount your store still owes lowers what a new payout may take. So does a refund of a sale whose own money has not arrived yet, until that money arrives. A payout that asks for more is refused, and the error message names the reason (see Errors you may see).

When your store has a reserve, the part of each sale it holds stays out of available until it is released. Reserves shows how much is held and when it comes back.

Create a payout#

  1. Check your bank account#

    Payouts go to the bank account your store registered for payouts. A store without one cannot pay out.

    Every payout names the bank account it pays in destination, as a ba_... id. You may leave destination out of the request to pay your payout bank account, or send the id of one of your store's bank accounts to pay that one. Any other id is refused, so money never goes to an account your store did not register.

    GET /v1/accounts/{account}/external_accounts lists your store's bank accounts, and a payout read with expand[]=destination shows the one it pays. Each shows the last four digits of the account number in last4 and the bank's name in bank_name, or null when Fluveo has no name for it. The test bank of routing number 110000000 is named FLUVEO TEST BANK.

  2. Send the payout#

    Give the amount in the currency's smallest unit, plus your store's currency. Add an Idempotency-Key made for this one payout. If the call is sent again with the same key and body, you get the first po_ back and nothing is paid a second time.

    Pay out $95.00
    curl https://api.fluveo.com/v1/payouts \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: payout-2026-11-07" \
      -d amount=9500 \
      -d currency=usd \
      -d "description=Weekly payout"
    const response = await fetch("https://api.fluveo.com/v1/payouts", {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_...",
        "Idempotency-Key": "payout-2026-11-07",
      },
      body: new URLSearchParams({ amount: "9500", currency: "usd", description: "Weekly payout" }),
    });
    const payout = await response.json();
    console.log(payout.id, payout.status);
    import requests
    
    response = requests.post(
        "https://api.fluveo.com/v1/payouts",
        headers={
            "Authorization": "Bearer sk_test_...",
            "Idempotency-Key": "payout-2026-11-07",
        },
        data={"amount": 9500, "currency": "usd", "description": "Weekly payout"},
    )
    payout = response.json()
    print(payout["id"], payout["status"])
    <?php
    $ch = curl_init("https://api.fluveo.com/v1/payouts");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer sk_test_...",
            "Idempotency-Key: payout-2026-11-07",
        ],
        CURLOPT_POSTFIELDS => http_build_query([
            "amount" => 9500,
            "currency" => "usd",
            "description" => "Weekly payout",
        ]),
    ]);
    $payout = json_decode(curl_exec($ch), true);
    echo $payout["id"] . " " . $payout["status"];
  3. Follow it to the end#

    A new payout is pending. Fluveo first sets the whole amount aside in your balance, and only then sends it. While the bank is receiving it, it can read in_transit. paid means your bank has it. A bank that refuses it turns it failed, and the amount returns to your balance. A payout can also turn failed after paid, when the bank sends the money back later; that amount returns to your balance too.

Parameters
  • amountintegerRequired

    In the smallest unit of the currency, at least 1.

  • currencystringRequired

    Your store's currency, as a three-letter code in lowercase. Any other currency is refused.

  • descriptionstringOptional

    Your own text, up to 5,000 characters.

  • statement_descriptorstringOptional

    Text that may show on your bank statement. It cannot hold <, >, ' or ".

  • methodstringOptional

    Only standard, the default. instant is refused with 400 instant_payouts_unsupported.

  • destinationstringOptional

    The id (ba_...) of one of your store's bank accounts, as GET /v1/accounts/{account}/external_accounts lists them. Leave it out to use your payout bank account for the currency.

  • metadataobjectOptional

    Your own key-value pairs, sent as metadata[key]=value.

Payout statuses#

StatusMeaning
pendingAccepted and not sent yet. You can still cancel it while none of it has left.
in_transitSent; your bank has not confirmed it yet.
paidYour bank has the money.
failedThe bank refused it, or returned it after it was paid. The amount is back in your balance, and failure_code and failure_message explain.
canceledYou canceled it before it was sent. The amount is back in your balance.

A payout keeps the destination it was created with. If your store removes or replaces that bank account before the money leaves, the payout fails with failure_code account_closed (no_account when no account is left on file), and the amount returns to your balance. Fluveo never redirects it to the new account.

Events#

EventWhen
payout.createdThe payout was created.
payout.updatedThe payout's status changed (sent beside each of the events below), the bank trace it shows (trace_id) changed, or you changed its metadata.
payout.paidAll of its money reached your bank.
payout.failedThe payout failed, or a paid payout had a bank return. The money is back in your balance.
payout.canceledThe payout was canceled before it was sent.

Payout schedule#

Your store starts on manual: money leaves only when you create a payout. To be paid out on a schedule instead, set it with POST /v1/balance_settings, and read it back with GET /v1/balance_settings.

Pay out every Monday
curl https://api.fluveo.com/v1/balance_settings \
  -H "Authorization: Bearer sk_test_..." \
  -d "payments[payouts][schedule][interval]=weekly" \
  -d "payments[payouts][schedule][weekly_payout_days][0]=monday"
  • payments[payouts][schedule][interval] is manual (the default), daily, weekly or monthly. daily pays out each weekday, Monday to Friday.
  • With weekly, send the days in payments[payouts][schedule][weekly_payout_days][], from monday to friday.
  • With monthly, send the days of the month in payments[payouts][schedule][monthly_payout_days][], from 1 to 31. A day the month lacks pays on its last day.

On each scheduled day, Fluveo creates a payout of what your store may pay out then (see What a payout may take). It is an ordinary po_ with automatic: true, and it sends the same events as any payout. You can still create a manual payout beside the schedule. Set interval=manual to stop the schedule.

Cancel, read and list payouts#

  • POST /v1/payouts/{id}/cancel stops a payout that is still pending, before any of it was sent. After that the cancel is refused, so treat a payout as final once you create it.
  • POST /v1/payouts/{id} changes only metadata.
  • GET /v1/payouts/{id} reads one payout.
  • GET /v1/payouts lists payouts, newest first. Filter with status, created, arrival_date or destination, and page with limit (1 to 100), starting_after or ending_before.
Cancel a payout
curl -X POST https://api.fluveo.com/v1/payouts/po_.../cancel \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: payout-2026-11-07-cancel"

Your statement: balance transactions#

Each charge, refund and payout leaves a balance transaction (txn_) with its amount, fee, net, type and source (the object that caused it).

The statement lines of one payment
curl -G https://api.fluveo.com/v1/balance_transactions \
  -H "Authorization: Bearer sk_test_..." \
  -d source=ch_... \
  -d limit=20
  • GET /v1/balance_transactions lists lines, newest first. Filter with type, source, currency or created; page with limit, starting_after or ending_before (see Pagination). Add expand[]=data.source to get each source object in the same answer.
  • GET /v1/balance_transactions/{id} reads one line.
  • payout=po_... with an automatic payout (one your payout schedule made, automatic: true) lists the lines that payout paid out, its own payout line included. Another payout's payout line, manual or automatic, is never in it: that payout paid its money out. The reserved_funds line that moved part of a sale into your reserve is in the list of the payout it made smaller; the reserve's own side (reserve_hold and reserve_release, balance risk_reserved) never is. Other payouts, open disputes or debts can keep the listed lines' net from adding to 0. A manual payout is refused with 400 on payout: "Balance transaction history can only be filtered on automatic transfers, not manual." A value that is not one of your store's payouts answers 400 resource_missing on payout. To find the line any payout left, filter with source=po_....

Errors you may see#

ErrorWhy
400 balance_insufficientThe payout asks for more than you can pay out now, as when what your store owes takes the available balance below it. Read GET /v1/balance and ask for less.
400 payouts_not_allowedYour account cannot pay out right now: payouts are not enabled yet, identity verification is not approved, there is a hold or an amount owed, or an earlier payout is not settled. The message says which.
400 "Sorry, you don't have any external accounts in that currency"No payout bank account is on file for the store.
400 amount_too_large on amountThe amount is above your account's limit for one payout.
400 parameter_invalid_integer on amountThe amount is above 9999999999, the most any payout can be.
400 instant_payouts_unsupportedYou sent method=instant.
400 on currencyThe currency is not your store's.
400 resource_missing on destinationThe destination is not one of your store's bank accounts. It is checked after the form of the request (an unknown parameter, a missing or invalid amount or currency, metadata, a description over 5,000 characters), and before method, a statement_descriptor with a forbidden character, the amount's maximum, a currency that is not your store's and your balance.

A refused payout reserves and sends nothing.

See Errors for the shape of every error.

Test it#

  1. Take a few test payments with pm_card_visa (see Accept a payment).
  2. GET /v1/balance: the payments show in pending or available.
  3. If available is more than 0, create a small payout, such as amount=100: you get a po_ in pending and payout.created. If it is refused instead, the message names the reason.
  4. Ask for more than your available balance: 400 balance_insufficient (or payouts_not_allowed), and no payout is made.
  5. Send method=instant: 400 instant_payouts_unsupported.
  6. Send step 3 again with the same Idempotency-Key: you get the same po_ back.

A test payout needs available test money. A test Pix or Boleto paid with the payer email succeed_immediately@example.com adds it once its money is available. See Pix.

Test a failed payout#

In test mode, set your payout bank account (in the dashboard's payout settings) to one of these test bank accounts, with the routing number 110000000. The next payout goes to it and fails with that code: you get payout.failed, the payout's failure_code, and the amount back in your balance.

Test account numberfailure_code
000111111116no_account
000111111113account_closed

After a failure the bank account shows as errored, and payouts stay off until you set a working bank account, such as 000123456789. These numbers work only in test mode.

Next#