What a platform fee is
A platform fee is your cut of a sale, taken from the account’s proceeds. It comes out of what the account would otherwise keep, not out of Bachs’s processing fee. Those are two different amounts leaving the charge for two different reasons, and conflating them will get your accounting wrong.Setting one
Sendplatform_fee on create-checkout, as an amount in the sale’s base currency, the currency the price itself is set in:
platform_fee is always an amount, never a percentage. A percentage is pricing policy you already own somewhere; restating it on every checkout request would give it a second place to drift out of sync with the first. Send the amount you’ve already computed, in the product’s base currency, even if the customer ends up paying in a different one. See Currency below.
platform_fee is available on both create-checkout surfaces: acting as the account with X-Account-Id (a direct charge) and naming the account in transfer_data.destination (a destination charge).
Two ways to state the split
platform_fee says what you keep. There’s a second field, transfer_data.amount, that says the opposite: what the account receives. Send exactly one. Sending both, or neither on a destination charge, is rejected before the customer pays.
Both are amounts in the sale’s base currency. Sending both is a
VALIDATION_ERROR (400): they are two descriptions of the same split, and nothing about the request says which one should win.
The two forms are not interchangeable bookkeeping. Which one you send changes what the account is paid and what you can see afterward:
- Lead with
platform_fee(fee-first). You state your cut; the account gets the rest. This is aPlatformFeerecord, readable atGET /v1/platform_fees. On a destination charge, the transfer that pays the account out carries the sale’s full gross amount alongside that record, so the account’s own read of the transfer shows the sale and your cut side by side. - Lead with
transfer_data.amount(share-first). You state what the account gets, and your platform keeps the rest of the sale. NoPlatformFeerecord is created, because you never stated a cut, only a payout. The transfer that pays the account carries exactly that amount, its net, and nothing else. Read Who pays the processing fee before you set this close to the full sale: the amount you name is what the account receives in almost every case, but not in the one where the sale cannot fund our fee.
platform_fee only. There is no seller balance for transfer_data.amount to name a share of: the account is the merchant, and what it keeps is the gross minus your fee.
What bounds it
All of these return the standard error envelope with status 400.Currency
platform_fee and transfer_data.amount are always base-currency amounts, the currency the sale was priced in. The split is struck in that same currency, even when the customer pays in something else and the charge converts on its way to settlement. A platform fee on a converting charge settles correctly: it doesn’t need the account to hold the payment currency, only its own base currency.
Only USD is held by default. Any other currency has to be enabled on an account before it can receive money in it. See Balances.
Precision
Both terms are held to the precision of the currency they are stated in: two decimal places onUSD or NGN, six on a stablecoin rail. A value carrying more than that is rounded half-up to the nearest minor unit when the checkout is created, and the rounded value is what the response echoes, what settles, and what the PlatformFee record keeps.
So a percentage you compute yourself is worth rounding before you send it. platform_fee: "33.337" on a USD sale is stored and paid as "33.34", and the account’s share is the rest of the sale after that rounded figure. A value that rounds to zero is refused rather than treated as a fee of nothing.
Who pays the processing fee
Bachs charges a processing fee on every sale. On a destination charge the sale is yours, so that fee is yours to pay, and it is taken in this order.- Your cut of the sale. Whatever you kept, whether you stated it as
platform_feeor left it as the remainder after atransfer_data.amount. - Your platform’s available balance. If your cut on this sale is too small to cover the fee, the rest comes out of your balance: money from your other sales, not this one. Your balance has to cover the whole remainder for this step to apply. If it cannot cover all of it, none of it is taken from there.
- The account’s share. Only when your cut and your balance together cannot pay the fee does the remainder come out of what the account receives.
transfer_data.amount, that is also the only case where the account receives
less than the amount you named.
When you receive it
On a direct charge, your cut becomes available on the account’s settlement schedule, never sooner. Settlement is what turns a charge into money either party can actually move; crediting your fee before that point would let you spend money that hasn’t cleared yet. On a destination charge, whatever your platform keeps is never transferred at all: it is already part of your platform’s own balance when the charge settles, since the charge was yours to begin with. Only the account’s share moves, as a transfer to the account.Reading it back
The checkout and payment objects echo back whichever term you sent:platform_fee on a fee-first split, destination_amount on a share-first one. The one you didn’t send is null.
A fee-first split also mints a platform fee record: a standalone object naming the charge it came from, the amount, and both parties. This is the queryable, permanent record of your cut. A share-first split never mints one, since you never stated a cut, only a payout. There, read what the account was paid straight off the transfer or the payment’s destination_amount.
Get one by id, or list every fee your platform was a party to:
GET /v1/platform_fees · GET /v1/platform_fees/{fee_id} · scope transfers:read
collected_from is the account the cut came out of, on either shape. earned_by is your platform. Both the account and your platform can read a fee they were party to; filter the list with charge to see the one fee tied to a specific sale.
This replaces reading a direct charge’s fee as a kind=platform_fee transfer. That transfer kind is retired: platform_fee is not a movement between two balances, it is money your platform keeps, and GET /v1/transfers?kind=platform_fee now returns 400.

