> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tagada.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Payouts, balances & fees

> Where the money goes, how to read your payout history and balance ledger over the API, and how Tagada and partner fees are collected.

# Payouts, balances & fees

Every TPA settles into a **balance account** at the acquirer. This page covers how funds flow out of it, how to observe that over the API, and how fees are taken along the way.

***

## The money model in 30 seconds

```
Charge (customer card)
  │
  ├── Tagada commission        → Tagada          (split at charge time)
  ├── Partner markup           → your partner balance account
  └── Net remainder            → the TPA's settlement balance account
                                   │
                                   └── bank sweep → the TPA's bank account (payout)
```

* **Standalone TPAs (`non_mor`)** — each merchant is the legal merchant. The net remainder settles to *their* balance account, and Tagada pays out to *their* bank. Your markup is carved out automatically at charge time and lands on **your partner balance account** — you never have to invoice or collect it.
* **MoR sub-merchants (`mor`)** — you are the Merchant-of-Record. All sub-merchants share **your root TPA's balance account**: the whole net remainder (after Tagada's commission) settles to **you**, and Tagada pays out to **your bank**. Redistributing to sub-merchants is your responsibility, off-platform. Your "fees" are implicit: you receive everything and keep your share when you redistribute.

<Note>
  **MoR partners: always query your root TPA.** Sub-merchant children (`parentTpaId` set) have no balance account or payouts of their own — payout and ledger data lives on the **parent** TPA. Money-movement operations are blocked on children by design.
</Note>

***

## Prerequisite: a payout bank account

Payouts require a **bank account registered on the TPA** (the bank must belong to the TPA's legal entity). Once it's on file, Tagada wires the daily bank sweep: any positive settled balance is pushed to the bank automatically.

If your TPA processes but has no bank account registered yet, funds simply **accumulate safely on the balance account** — nothing is lost, payouts start as soon as banking details are in place. Register the account with [`tpas.createBankAccount()`](#register-a-bank-account-self-serve) (or submit the `banking.iban` requirement via [`tpas.requirements.update()`](/developer-tools/partners/accounts) during onboarding).

### Check which bank accounts are on file

`tpas.getBanking()` lists the registered payout bank accounts, masked — one per currency, plus an optional catch-all that receives converted residual balances:

```ts theme={null}
const banking = await partner.partners.processing.tpas.getBanking('tpa_your_root');

// {
//   object: 'banking_overview',
//   tpaId: 'tpa_your_root',
//   payfacProvider: 'adyen',
//   allowedCurrencies: ['EUR', 'USD', 'GBP', ...],
//   instruments: [{
//     object: 'payout_instrument',
//     id: 'poi_...',
//     currency: 'EUR',
//     status: 'active',          // active | pending (bank verification)
//     isCatchAll: false,
//     bankAccount: { iban: '••••3607', country: 'FR', ... },  // masked
//     createdAt: '2026-07-10T09:00:00Z',
//   }],
// }
```

REST: `GET https://api.tagadapay.io/api/tagadapay/v1/partner/tpas/:id/banking`.

A TPA can hold **one bank account per currency** — each gets its own daily sweep in that currency, so EUR funds go to the EUR account and USD funds to the USD account, with no FX. MoR sub-merchants return an empty list by design — funds pool on your root TPA.

### Register a bank account (self-serve)

`tpas.createBankAccount()` registers the payout bank account for a currency — no ops ticket needed:

```ts theme={null}
// US account for the USD balance:
const instrument = await partner.partners.processing.tpas.createBankAccount('tpa_your_root', {
  currency: 'USD',
  bankAccount: {
    accountNumber: '000123456789',
    routingNumber: '021000021',
    holderName: 'Acme Inc.',        // MUST match the TPA's legal entity
    accountType: 'checking',
    country: 'US',
  },
});
// → { object: 'payout_instrument', id: 'poi_...', currency: 'USD',
//     status: 'pending', bankAccount: { accountNumber: '••••6789', ... } }

// EUR IBAN for the EUR balance:
await partner.partners.processing.tpas.createBankAccount('tpa_your_root', {
  currency: 'EUR',
  bankAccount: { iban: 'FR76…', holderName: 'Acme Inc.' },
});
```

REST: `POST https://api.tagadapay.io/api/tagadapay/v1/partner/tpas/:id/banking` with body `{ "currency": "USD", "bankAccount": { … } }`.

Bank account fields by country: `{ iban }` (SEPA) · `{ accountNumber, routingNumber, country: 'US' }` (US) · `{ accountNumber, sortCode }` (UK) · `{ accountNumber, bsb }` (AU). Always pass `holderName`.

Rules to know:

* **`status: 'pending'` first.** The acquirer verifies the bank belongs to the TPA's legal entity (name matching, sometimes a bank statement). It flips to `active` automatically — poll `getBanking()`. Funds accumulate safely on the balance while verification runs.
* **Adding is self-serve, replacing is not.** A currency that already has an account returns `409 bank_account_exists` — bank swaps are the classic payout-fraud vector, so replacements stay ops-reviewed. Contact your account manager.
* **MoR sub-merchants → `409 mor_sub_merchant_banking`.** Register on your root TPA; children share its balance.
* Pairs with [acquiring routes](/developer-tools/partners/settlement-currencies): `enableRoute('EUR')` makes EUR **settle** natively, this endpoint makes it **pay out** natively. Without the matching bank account, the sweep converts at payout.

***

## Read your payouts over the API

Payout history (bank settlements) is on the **Processing API** — same base and auth as charging: a Processing Key (`tp_sk_…`) scoped to the TPA, or your Partner Key with the `account` parameter.

```ts theme={null}
const partner = new Tagada(process.env.TAGADA_PARTNER_KEY!);

// MoR: use your ROOT TPA id — not a sub-merchant child
const payouts = await partner.partners.processing.payouts.list({
  account: 'tpa_your_root',
  limit: 50,
  // status: 'paid',       // optional filter
});

// {
//   object: 'list',
//   data: [{
//     object: 'payout',
//     id: '...',
//     payfacProvider: 'adyen',
//     amountMinor: 152300,        // €1,523.00
//     currency: 'EUR',
//     status: 'paid',             // pending | in_transit | paid | failed
//     scheduledAt: '2026-07-09T04:00:00Z',
//     paidAt: '2026-07-09T09:12:41Z',
//     bankAccountLast4: '1234',
//     failureCode: null,
//   }, ...],
//   hasMore: false,
// }
```

REST equivalent: `GET https://api.tagadapay.io/api/tagadapay/v1/payouts?account=tpa_xxx`.

***

## Trigger a payout on demand

Don't want to wait for the daily sweep? `tpas.payout()` pays the available balance to the TPA's registered bank account **immediately**:

```ts theme={null}
// MoR: trigger on your ROOT TPA (children share its balance)
const run = await partner.partners.processing.tpas.payout('tpa_your_root');

// {
//   object: 'payout_run',
//   tpaId: 'tpa_your_root',
//   payoutId: '...',        // the provider payout id
//   amountMinor: 152300,    // €1,523.00 actually paid
//   skipped: false,
//   reason: null,
//   error: null,
// }

// Partial payout — clamped to the safe maximum, you can never over-withdraw:
await partner.partners.processing.tpas.payout('tpa_your_root', { amountMinor: 50000 });
```

REST: `POST https://api.tagadapay.io/api/tagadapay/v1/partner/tpas/:id/payout` (body `{ "amountMinor": 50000 }` optional).

What is paid: `available balance − rolling reserve − outstanding debt`, same engine and safety gates as the automated sweep. When no payout is possible the call returns `409` with `skipped: true` and a `reason`:

| `reason`                      | Meaning                                                  |
| ----------------------------- | -------------------------------------------------------- |
| `below_minimum`               | Less than 1.00 (major unit) available after reserve/debt |
| `not_provisioned`             | The TPA has no acquirer account yet                      |
| `processing_suspended`        | The TPA is suspended — no money movement                 |
| `sub_merchant_parent_settles` | MoR child — trigger the payout on the ROOT TPA           |
| `provider_*_not_supported`    | This acquirer only supports scheduled payouts            |

<Note>
  A **verified bank account** must be on file (see prerequisite above) — otherwise the payout is refused with an explicit error. On multi-currency balances the payout targets the largest balance covered by an active bank account in that currency; check coverage first with `tpas.getBanking()`.
</Note>

***

## Read your live balance

`balance.retrieve()` reads the balance **live from the acquirer** at request time — no waiting for ledger ingestion. One bucket per currency, in minor units:

```ts theme={null}
const balance = await partner.partners.processing.balance.retrieve({
  account: 'tpa_your_root',   // MoR: your ROOT TPA
});

// {
//   object: 'balance',
//   account: 'tpa_your_root',
//   payfacProvider: 'adyen',
//   heldBy: 'tpa_your_root',
//   shared: false,
//   balances: [
//     { currency: 'EUR', availableMinor: 152300, pendingMinor: 20000, reservedMinor: 0 },
//     { currency: 'USD', availableMinor: 5000,   pendingMinor: 0,     reservedMinor: 0 },
//   ],
//   retrievedAt: '2026-07-10T07:30:00Z',
// }
```

REST: `GET https://api.tagadapay.io/api/tagadapay/v1/balance?account=tpa_xxx`.

* `availableMinor` — settled funds, eligible for the next bank sweep.
* `pendingMinor` — captured but not yet settled ("available soon").
* `reservedMinor` — held back as reserve.
* Stripe-backed TPAs also return `instantAvailableMinor` (instant-payout-eligible subset).

<Note>
  **MoR sub-merchants share the root balance.** Querying a sub-merchant child returns the **root TPA's shared balance** (`shared: true`, `heldBy` = the root) — there is no per-sub-merchant balance. For that reason the call requires a key authorized for the root TPA (your partner key qualifies); a key scoped to the sub-merchant alone gets a `403 shared_balance_access_denied`.
</Note>

A TPA that isn't fully provisioned yet (no acquirer account) returns `409 balance_unavailable`.

<Note>
  **Which currency do funds settle in?** Each charge settles in the currency of its acquiring route — by default your TPA's home currency (other currencies are converted at settlement). To settle EUR charges natively in EUR (no FX), enable an acquiring route: see [Settlement currencies & acquiring routes](/developer-tools/partners/settlement-currencies).
</Note>

***

## Read your balance ledger (fees included)

The **balance transactions** ledger shows every movement on the balance account — charges credited, refunds debited, payouts leaving — with the fee breakdown per entry:

```ts theme={null}
const ledger = await partner.partners.processing.balanceTransactions.list({
  account: 'tpa_your_root',
  from: '2026-07-01T00:00:00Z',
  // type: 'payout',       // optional filter
});

// data: [{
//   object: 'balance_transaction',
//   id: '...',
//   type: 'payment',
//   sourceId: 'charge id / payout id',
//   amountMinor: 20000,    // gross
//   feeMinor: 830,         // fees taken on this entry
//   netMinor: 19170,       // what actually settled
//   currency: 'EUR',
//   createdAt: '...',
// }, ...]
```

REST: `GET https://api.tagadapay.io/api/tagadapay/v1/balance_transactions?account=tpa_xxx`.

The same key also gives you `charges.list`, `disputes.list` and `chargebackAlerts.list` on the same base — see [API keys](/developer-tools/partners/api-keys).

<Note>
  **Ledger depth varies by acquirer.** Stripe-backed TPAs have full per-entry fee detail today. For Adyen-backed TPAs the enriched per-entry ledger is progressively rolling out — payout history is live, and the detailed fee ledger is being backfilled. If a query returns less than you expect, it's an ingestion-coverage matter, not missing money: the balance account at the acquirer is always the source of truth, and your account manager can pull a full statement on demand.
</Note>

***

## Payout webhooks

Rather than polling, subscribe your TPA webhook endpoint to money-movement events:

* `payout.created`, `payout.paid`, `payout.failed`
* `balance_transaction.created`
* `platform_fee.created` / `partner_fee.created` (and `.refunded`)

See [Webhooks & events](/developer-tools/node-sdk/webhooks-events).

***

## How fees are collected

You never have to *collect* anything — fees are carved out at charge time via acquirer-level splits:

| Leg                               | Who gets it                          | When                                |
| --------------------------------- | ------------------------------------ | ----------------------------------- |
| Tagada commission (your buy rate) | Tagada                               | At capture, per charge              |
| Your partner markup (sell − buy)  | **Your partner balance account**     | At capture, per charge              |
| Net remainder                     | The TPA's settlement balance account | At capture; pays out via bank sweep |
| Scheme/interchange (`PaymentFee`) | Booked against the merchant leg      | Computed by the acquirer            |

Your contractual pricing (buy rate, markup grid, passthrough fees) is queryable at any time:

```ts theme={null}
const { data: acquirers } = await partner.partners.processing.acquirers.list();
// acquirers[0].buyRate   — what Tagada charges you
// acquirers[0].markup    — your margin grid
// acquirers[0].sellRate  — what your merchants pay
```

You can also price any merchant individually — a per-TPA markup set at `tpas.create({ markup })` or later with `tpas.updateMarkup()` replaces your partner-level grid for that merchant. See [Accounts → Per-merchant pricing](/developer-tools/partners/accounts#per-merchant-pricing-markup).

For **MoR**, remember the simplification: there is no per-sub-merchant markup settlement — everything net of Tagada's commission lands on your root balance account, and your margin is whatever you keep when redistributing.

***

## What's coming next

Actively being built (talk to your account manager for timelines):

* **Full Adyen per-entry fee ledger** — interchange / scheme / commission breakdown per settlement line.
* **Partner-level rollup** — payouts and earnings aggregated across all your merchants and sub-merchants in one call, plus a partner dashboard view.
