Skip to content

Money for platforms

You earn an application fee on your accounts’ payments, on top of ours. Your fees collect in your Connect balance, which is swept into your own balance daily and can fund transfers to your accounts. You also bear your accounts’ losses.

PayerPaymentas acct_…, 4500Tuppence feepayment's feeAccount's balancenetIts bankits payout scheduleYour Connect balanceapplication feesYour balance→ your bankpaysnetapplication fee05:30 UTC sweeptransferspayout

A £45.00 payment on a UK consumer card (Tuppence’s fee at 0.79%, rounded up), with your application_fee_amount: 100:

Account pays our fee (default)You pay our fee (controller.fees.payer: application)
Payment amount45004500
Payment fee (Tuppence’s)360 (fee_payer: platform)
application_fee_amount (yours)100100
Taken from the payment, as one fee136100
Account’s net43644400
Into your Connect balance10064 (the fee’s tuppence_fee is 36; net is 64)

A $45.00 payment on a US card (Tuppence’s fee at 2.49% rounded up, plus 20¢), with your application_fee_amount: 100:

Account pays our fee (default)You pay our fee (controller.fees.payer: application)
Payment amount45004500
Payment fee (Tuppence’s)1330 (fee_payer: platform)
application_fee_amount (yours)100100
Taken from the payment, as one fee233100
Account’s net42674400
Into your Connect balance100-33 (the fee’s tuppence_fee is 133; net is -33)

fees_payer is set when the account is made and can’t change. If you pay our fee and take none of your own, your Connect balance goes below zero. Whatever is below zero at month end is debited from your own balance on the 1st.

application_fee_amount (minor units) on POST /v1/payments, POST /v1/payments/charge_saved or POST /v1/bookings/:id/payments, made as the account.

Terminal window
curl https://api.tuppence.ai/v1/payments \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Tuppence-Account: acct_1…" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 4500, "currency": "gbp", "application_fee_amount": 100}'
Terminal window
curl https://api.tuppence.ai/v1/payments \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Tuppence-Account: acct_1…" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 4500, "currency": "usd", "application_fee_amount": 100}'
Terminal window
curl https://api.tuppence.ai/v1/payments \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Tuppence-Account: acct_1…" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 4500, "currency": "eur", "application_fee_amount": 100}'

Or set it up ahead, as the account, so it’s taken without your platform in the loop:

WhereField
Payment link with a fixed amountapplication_fee_amount
Payment link where the payer choosesapplication_fee_percent ("2.5")
Plan (POST /v1/plans)application_fee_percent: every charge of a subscription, including its link’s first payment
The account’s default (POST /v1/accounts/:id)settings.application_fees: percent (string, "1.5") of the amount rounded up, plus fixed_amount. Applies wherever a payment names no fee

A store you run for an account (with Tuppence-Account on the store routes) takes your fee on every store payment: the web checkout, the Storefront API, hand-offs and agents’ own checkouts.

WhereField
Your default for every account’s store (POST /v1/account)settings.connect.defaults.store_application_fee: percent and fixed_amount, as above
One account’s store (POST /v1/accounts/:id)settings.store.application_fee. Wins over your store default

A store payment takes the account’s store fee, else your store default, else the account’s general fee. Other payments never read the store fees. Both are held to the cap. A store refund leaves your fee with you unless you give it back.

An order a sales channel took (TikTok Shop, eBay) was paid there, so it carries no fee: store.order_placed says application_fee: null with application_fee_reason: "paid_on_channel".

How a new account’s store starts: settings.connect.defaults.store on POST /v1/account.

FieldDefaultWhen false
enabledtrueThe store is made with its checkout paused (checkouts_paused.code: "platform"). Open it with POST /v1/sites/:id/resume_checkouts, as the account
agent_native_checkouttrueAI agents hand the shopper to the store’s checkout instead of paying themselves

GET /v1/accounts/:id/store reads an account’s store: its address and status (none, not_open, open, closed), and this month’s store orders, revenue and your fees on them. The account’s page in the dashboard shows the same under Store.

Your accounts’ stores at your own domain

Section titled “Your accounts’ stores at your own domain”

Each account’s store can live under a domain of yours, such as shops.yourplatform.com, instead of its Tuppence address:

  1. Set settings.connect.store_domain on POST /v1/account to the domain.
  2. Add the two records settings.connect.store_domain.records lists at your DNS host: a TXT record at _tuppence.<domain>, which proves the domain is yours, and one wildcard CNAME from *.<domain> to us.
  3. Tuppence looks for the TXT record every ten minutes, or at once when you send the same domain again. Until it is seen, status is pending and error names the record to add; after seven days it is failed.

Once it is verified, every account’s store gets <its slug>.<domain> (a store made later gets its own as it is made). Each address is a platform domain in the store’s domains: verified while its certificate is issued, then active. From then on it is the store’s address: its Tuppence address redirects there, Apple Pay and Google Pay work there, and the footer reads Powered by your platform’s name. Each business is still the seller on every page. An account’s own domain, if it adds one, wins over yours. A new slug moves its address with it, and store_domain: null takes every address away again.

RuleDetail
Cap: 20% of the payment, rounded down (on £45.01$45.01€45.01, 900)Over it: 400 application_fee_too_high with max_fee_bps (2000), max_fee_amount and fix_url. Ask for a higher cap in Settings → Platform. A raised cap applies to every account on your platform
Cap on fees set up aheadRefused when you set them, not when a payer pays: a link’s fee or percent, a plan’s percent, an account’s default percent, your platform defaults. A default fixed_amount over the cap on a small payment is taken up to the cap
Account keeps at least 1A fee of the whole amount or more is refused when the payment is made
Room for our feeOur fee is known only once the card is, so a fee that leaves no room for it (and that 1) is refused at confirm with 400 application_fee_too_large. Make a new payment with a smaller fee
Partial captureTakes the same part of your fee, rounded up
Your own paymentA fee on a payment not made as a connected account: 400 platform_account_required

Each fee is an application_fee (fee_…), announced with application_fee.created. GET /v1/application_fees lists them (?account=, ?payment=, ?status=). A fee is pending until the payment’s money becomes available (available_on), then available, then swept. A connected account’s balance transactions show both fees in fee_details.

Give it back:

HowReturns
refund_application_fee: true on POST /v1/refunds, as the accountYour fee in proportion to the refund, rounded down; the refund that empties the payment returns the rest
POST /v1/application_fees/:id/refunds, your own keySome or all (the rest, by default)

Each is a fee_refund (GET /v1/application_fees/:id/refunds), announced with application_fee.refunded, and goes back to the account’s balance. Only your fee is returned, never ours.

See connect on GET /v1/balance, its movements at GET /v1/balance/transactions?balance=connect, or the dashboard’s Balance page.

  • Daily sweep at 05:30 UTC: if the available amount is £1.00$1.00€1.00 or more, it moves into your own balance in one transfer and those fees become swept. It’s then paid out with the rest of your money. connect.next_sweep says when the next sweep runs.
  • Below zero: a fee given back after it was swept takes the balance below zero. Your next fees make it up before anything more is swept.
  • Test mode: a fee on a payment made with pm_card_bypassPending is available at once. With an ordinary test card it stays pending for the card’s clearing days. Advancing your test clock runs the sweeps for the skipped days but doesn’t shorten the card’s pending period.

POST /v1/transfers, with your own key, sends from the available part of your Connect balance. It reaches the account’s balance at once, and you both hear transfer.created.

Terminal window
curl https://api.tuppence.ai/v1/transfers \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 500, "currency": "gbp", "destination": "acct_1…", "transfer_group": "referrals"}'
Terminal window
curl https://api.tuppence.ai/v1/transfers \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 500, "currency": "usd", "destination": "acct_1…", "transfer_group": "referrals"}'
Terminal window
curl https://api.tuppence.ai/v1/transfers \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 500, "currency": "eur", "destination": "acct_1…", "transfer_group": "referrals"}'

A transfer is money leaving your platform, so the same checks as your payouts apply. Transfers and payouts share one daily limit and one exposure cap: what you transfer today you can’t pay out, and a reversal gives it back.

RefusalWhen
402 balance_insufficient (with available)More than is available. Pending fees fund nothing
403 payouts_not_approvedUntil we’ve reviewed your account
403 new_account_floor (with available_from)Your first 168 hours
403 payouts_on_holdWhile your payouts are held
403 payout_limit (with fix_url)Above your payout limits
  • The account can pay transferred money out 7 days after it arrives. Until then it’s in its balance but not cleared.
  • Take it back: POST /v1/transfers/:id/reversals (the rest, by default) while the account still holds it; otherwise 402 balance_insufficient, naming the account.
  • List: GET /v1/transfers (?destination=, ?transfer_group=) and GET /v1/transfers/:id/reversals.
  • Account to account: a reversal, then a transfer.

Your connected accounts top up their card balance as any business does: as them, with Tuppence-Account (or Open as in the dashboard), or on a hosted page you send them. You can also top one up from your own Connect balance, and set what your accounts may do.

From your Connect balance: POST /v1/issuing/topups with method: platform, as the account. It is a transfer from the available part of your Connect balance (the same checks and refusals as POST /v1/transfers) and it’s free.

Terminal window
curl https://api.tuppence.ai/v1/issuing/topups \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Tuppence-Account: acct_1…" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"method": "platform", "amount": 5000}'
RegionWhat happens
USavailable at once: the account’s cards spend it straight away. Like any transfer, the account can pay it out only 7 days after it arrives
UKIt lands in the account’s payments balance, held for the cards, and waits there processing (delay.code: moving_to_card_balance) until the move onto the card balance opens. It is never paid out

Only you can make it, acting as the account: anyone else gets 400 platform_account_required. GET /v1/issuing/topups/options lists platform only for you, with what your Connect balance holds.

A hosted page: POST /v1/issuing/topup_links (as the account, with return_url and an optional amount) answers a url, shown only then. The page shows your name, logo and colour (Settings → Branding), takes a card or shows the card balance’s bank details, works once and lasts 24 hours. In a browser, tuppence.topups.redirect({ url }) from @tuppence/js sends the person there.

Your settings: settings.connect.card_topups on POST /v1/account, or Platform settings → Card top-ups.

FieldWhat it sets
enabledfalse switches top-ups off for every account (yours from your balance still work). Default true
methodsThe ways your accounts may top up: any of bank_transfer, card, balance, platform. null (the default): all
platform_feeYour fee on their card top-ups: rate_bps of what arrives (at most 500, 5%) plus fixed, on top, never more than 5% of the top-up. Always shown to them, and paid into your Connect balance as an application fee with source: issuing_topup
limitsper_topup and per_30_days, in minor units: lower than Tuppence’s only
auto_topup_allowedWhether they may switch on auto top-up. Default true

Who pays Tuppence’s fee follows the account’s fees.payer, as on its payments. With application, your Connect balance pays it and the account sees none: a card top-up of 10000 with a 1% fee of yours charges the account 10100, and your Connect balance gets 100 less our fee. The top-up’s fee says so: paid_by and platform_fee.

Daily by default: whatever has cleared under the account’s settlement tier. Change it with settings.payouts.schedule on POST /v1/accounts/:id.

FieldValues
intervaldaily; weekly with weekly_anchor (monday…); monthly with monthly_anchor 1–31 (the last day in a shorter month); manual (never paid automatically: you pay it with POST /v1/payouts, as the account)
delay_days"minimum" (its settlement tier’s; the default), or a longer number. Shorter than the tier is 400

GET /v1/payout_settings, as the account, shows its schedule and next payout. Instant payouts work as the account too, on the same terms as yours.

A refund or lost dispute can take an account below zero. We first try to recover it from the account’s bank. If it’s still below zero three days later, you cover it in this order. You hear each step as account.negative_balance; the account has no dashboard and is told nothing.

  1. Your Connect balance: what’s available goes to the account.
  2. Your own balance: the rest is debited from it, as your platform agreement allows.
  3. Neither covers it: your payouts and your accounts’ wait. Tuppence writes the rest off and charges it to your Connect balance, collected on the 1st.

Your accounts’ cards earn cashback on eligible spend: 0.89% in the UK (1% in the US). You earn it and choose how much to pass on.

Your sharecashback_share: a percentage of card spend, "0" to under "5", at most two decimals
Your defaultsettings.connect.defaults.cashback_share on POST /v1/account, or Settings → Platform
One businesssettings.cashback_share on POST /v1/accounts/:id; null returns to your default
Nothing setPasses on nothing: you keep it all. You can pass on all of it ("0.89") or more; any excess comes from your Connect balance
Which share appliesRead at statement time: the business’s own, else your default, else 0. A change applies from the next statement
RefundsShares are worked out on the month’s total spend, rounded down. A refund in a later month is taken back from both sides on the next statement
Balance can’t cover a shareThe share waits and you get an alert. Once covered, the daily sweep sends it first and holds it back from what it sweeps. Your balance never goes below zero for a share

Payment. Each business gets a monthly statement, made early the next month and checked by our team. When it’s paid:

  1. what you earned (net) goes into your Connect balance;
  2. the business’s share (share_net) goes from there to its payments balance, as a transfer with transfer_group: cashback_<month>.

The statement, read as the business (GET /v1/cashback/statements), names you as beneficiary with share_bps, share_gross, share_reversals, share_net and share_transfer_id. GET /v1/cashback/connected_accounts?period=2026-10 lists each business’s month: card spend, what you earn, pass on and keep. The dashboard shows the same on Cashback and each business’s Cards and cashback.

Your monthly statement (GET /v1/statements/:month, or Reports → Monthly statements):

StatementLines
Yours, connect sectionOpening and closing balance; your fees and fees given back; our fees you paid; transfers to accounts and reversals; swept into your balance; accounts’ losses; fees per account (by_account)
Yours, own balanceconnect_swept (swept in), connect_losses (losses debited from your own balance)
Each account’s (read as it)application_fees (your fee), transfers_in, and processing_fees_paid_by_platform if you pay our fee (it never touched the account’s balance)

Docs assistant

Open as a page
Loading the assistant…