Pilot environment: testnet, simulated yield, mocked fiat rails — read the disclosure

Changelog

Dated, additive changes to the TCB API surface.

Changes within v1 are additive: new endpoints, new optional request fields, new response fields. A breaking change would be a new path segment. See Versioning.

2026-10-30 — Quote a payout or a conversion before you make it

You can now see what a payout or a conversion will cost before you commit. Quote it first, then name the quote on the call that moves the money: the payout is charged the quoted Sending fee, and the conversion executes at the quoted rate and commission. Quoting is optional. See Quote a payout first and Quote a conversion first.

Added

  • POST /payouts/quote: the payout's Sending fee, its total and expiresAt (60 seconds), under a quoteId.
  • POST /conversions/quote: the conversion's rate, its commission, what it would credit (receiveAmount) and expiresAt (the venue's own quote window, never more than 60 seconds), under a quoteId.
  • quoteId, optional, on POST /payouts and POST /conversions. A quote executes once, before expiresAt, for the request it priced.
  • commission on the conversion object: { amount, currency }, the commission the conversion was charged. null until it settles, and on a conversion charged none.
  • networkFee on the conversion object and the conversion quote. Always null: TCB covers any network cost of a conversion and never charges it.
  • requiresApproval on the payout quote: true for a first payment to a destination above your organisation's own first-send cap, which the payout itself refuses. A quote records nothing against the destination.
  • A quoted conversion fills at the quoted rate or better and is charged at most the quoted commission; when the market has moved against the quote it is refused with QUOTE_TERMS_CHANGED instead.
  • Error codes QUOTE_NOT_FOUND, QUOTE_EXPIRED, QUOTE_ALREADY_USED, QUOTE_MISMATCH, QUOTE_TERMS_CHANGED and CONVERSION_RESULT_UNREADABLE. See Errors.

2026-10-30 — How a sub-client is held

Each sub-client holds its own account with TCB, in its own name. You collect the identity information and TCB accepts it. You instruct on the sub-client's behalf. The pages that describe sub-clients now say so. No endpoint, field or behaviour changed.

2026-10-27 — A payout pays the Sending fee for its rail

A payout now pays the Sending fee for the rail it travels, the same per-rail fee a payment sent from the TCB app pays: on top of amount, in the payout's currency, from the same source account. It is refunded with the payout. See The Sending fee.

Added

  • fee on the payout object: { amount, currency }, the Sending fee charged with the payout. null only on a payout refused before anything was debited.
  • SENDING_FEE_UNPRICEABLE (409): no Sending fee is configured for the rail the payout would travel. Nothing was charged and the key stays usable.

Changed

  • INSUFFICIENT_BALANCE on POST /payouts now means the source account does not hold the amount plus its Sending fee; details.required and details.fee say what the payout needs.

2026-10-27 — Fiat amounts are held to the currency's minor unit

Changed

  • Settlements now refuse a fiat amount finer than the currency's minor unit with 400 AMOUNT_PRECISION_EXCEEDED and details.allowedDecimals, the same refusal deposits, withdrawals, payouts and conversions give.
  • The minor unit is the currency's own, not a flat 2, on withdrawals and payouts too: JPY and KRW take whole units only (0 decimal places), and BHD, KWD, OMR, JOD and TND take 3. See Amounts and scales.

2026-10-22 — A crypto account's balance shows what is in flight

Changed

  • balance.reserved on a crypto account now carries what a conversion or an outgoing payment from it holds back until it settles, and available is total minus that. The shape is unchanged; a fiat account still answers reserved: "0".

2026-10-20 — Your custody wallet is created on your first deposit

Your first POST /deposits or POST /token-mints creates the TCB custody wallet that holds your position, before anything is charged.

Added

  • PARTNER_WALLET_PROVISIONING_FAILED (503): the wallet could not be created just now. Nothing was charged and your idempotency key was not claimed: resend with the same key or a new one, parameters unchanged or not.

2026-10-12 — The tokenised deposit chain, one step at a time

A vault deposit mints tcbUSD from your fiat and puts it into the vault; a vault withdrawal takes it back out and redeems it to fiat. Each of those steps is now a call of its own, so you can hold tcbUSD between them and queue a redemption the vault cannot pay right now. See Tokenised deposits, step by step.

Added

  • POST /token-mints (scope vault:deposit): debit a fiat account and mint the same amount of tcbUSD, booked to a sub-account.
  • POST /token-redemptions (scope vault:withdraw): burn tcbUSD booked to a sub-account and credit the same amount of fiat.
  • POST /share-subscriptions (scope vault:deposit): put tcbUSD booked to a sub-account into the vault; the shares are booked to the same sub-account.
  • POST /share-redemptions (scope vault:withdraw): redeem shares for tcbUSD, paid at once and booked to the sub-account.
  • POST /share-redemption-requests and POST /share-redemption-requests/{id}/cancel (scope vault:withdraw): queue a redemption in the vault's queue, and cancel what has not been paid.
  • A fetch and a paginated list for each step (scope transaction:read): GET /token-mints, GET /token-redemptions, GET /share-subscriptions, GET /share-redemptions, GET /share-redemption-requests, each with its /{id}.
  • GET /positions/tokens (scope position:read): your tcbUSD per sub-account — held, reserved and available — with the balance of the wallet that holds it.
  • Twelve webhook event types — token_mint.settled / .failed, token_redemption.settled / .failed, share_subscription.settled / .failed, share_redemption.settled / .failed, and share_redemption_request.queued / .settled / .cancelled / .failed — and the five resource types they point at.
  • The step error codes, and two new money caps, TOKEN_MINT_CAP_EXCEEDED and TOKEN_REDEMPTION_CAP_EXCEEDED. A vault subscription counts against your deposit cap and a vault redemption against your withdrawal cap. See Errors.

Changed

  • The disclosure carried by every response and page is now version 2026-10-12. Its custody item now covers tcbUSD as well as vault shares: both are held in the one TCB wallet assigned to you, you hold no keys and move them only through this API, and each sub-account's shares and tcbUSD are book entries against what that wallet holds.

Unchanged, and worth saying

  • POST /deposits and POST /withdrawals behave exactly as before, and remain the way to move fiat into and out of a vault in one call.

2026-10-10 — A payout may be held for review

TCB reviews some payments before they go out, from the app and from the API alike. A payout that is flagged is debited and held: it reports the new status HELD_FOR_REVIEW until TCB releases it (it continues to PROCESSING) or declines it (it ends FAILED, the debit refunded). See A payout may be held for review.

Added

  • HELD_FOR_REVIEW in the payout status enum.
  • payout.held and payout.released webhook event types.

2026-10-10 — A deposit says when TCB has paused issuing the token

TCB can now stop issuing a token without a deployment — for a problem on the chain, in the mint path, or with the fiat behind the tokens. While it is stopped, a deposit that would mint that token is refused and says so. Nothing is charged, and withdrawals and redemptions are not affected. See Errors and Handle the errors.

Added

  • 409 TOKEN_ISSUANCE_PAUSED on POST /deposits. details.idempotencyKeySpent is false when the deposit was refused up front, nothing was recorded and the same key stays usable; true when the pause landed while the deposit was executing, which leaves that deposit terminal FAILED and needs a new key for the next attempt. Submitting again while the pause stands is refused again, so do not retry in a loop — contact TCB.

2026-10-09 — The position carries the rate the vault pays

GET /positions now returns the annual rate the vault pays, read on-chain, in place of a rate derived from the observed share price. See The share price and the rate.

Added

  • rate on GET /positions: apyBps (net of fees), asOf, stale, effectiveFrom, pendingChange, nextChange and unavailableReason.

Removed

  • netApyBps and basis are gone from GET /positions. To derive the rate a position has realised, use GET /positions/share-price-history, which is unchanged. This is a one-off exception to the additive-only rule for v1, made while the API is a testnet pilot.

2026-10-09 — A same-day deposit no longer moves what a position has earned

Fixed

  • currentValue on GET /positions (and the earnedLifetime and earnedUnrealised figures built on it): a deposit settled after the latest share-price observation was valued at that earlier price, so the position showed a small gain or loss the moment it settled. currentValue is now the position at the latest observed share price, plus movements since that observation at their own amounts. A withdrawal since the observation leaves the remaining shares at the observed price. No field was added or removed. See Positions & accrual.

2026-10-08 — What your vault position has earned

The position now says what each sub-account has earned, valued at an observed share price you can verify. You can also list your vault deposits and withdrawals, and read the share price day by day. See Positions & accrual and List your deposits and withdrawals.

Added

  • shares.minted on a deposit (POST /deposits, its replay and GET /deposits/{id}): the vault shares the deposit received, the same figure as shares on the deposit list. null unless the deposit has settled.
  • GET /deposits and GET /withdrawals: paginated lists, each row on one status set (QUEUED, PROCESSING, CREDIT_PENDING, COMPLETED, FAILED, REFUNDED, CANCELLED). Withdrawals carry requestedAmount, creditedAmount and estimatedAvailableAt (set only for a queued withdrawal; vault withdrawals execute immediately, so it is null).
  • GET /positions/share-price-history: the vault's share price for each day, with the chain block and time it was observed. A day that was not observed is null.
  • On GET /positions:
    • sharePrice (with asOfBlock and asOfAt), netApyBps (net of fees) and its basis.
    • In every accrual: deposited, withdrawn, earnedLifetime, earnedUnrealised, earnedRealised and notObserved. earnedPeriod is filled when you send the new optional periodFrom and periodTo.

Changed

  • accrual is always present. A figure that cannot be observed is null with its reason in notObserved. A failed read now fails the request with 503 CHAIN_UNAVAILABLE. It is no longer reported as an accrual of null.
  • currentValue is the settled position at the latest observed share price shown in sharePrice. When that observation is more than two days old the figures stay and sharePrice.stale is true.
  • A sub-account whose records disagree (EVIDENCE_INCONSISTENT), or that has more than 5,000 ledger entries (HISTORY_TOO_LONG), has its earned figures withheld; the rest of the report is served.

Removed

  • gross, net and resellCut are gone from accrual on GET /positions. earnedLifetime replaces gross and net, which it always equalled; resellCut was always "0". This is a one-off exception to the additive-only rule for v1, made while the API is a testnet pilot.

2026-09-24 — Crypto capabilities for sub-clients

Two new capability flags say whether an owner may hold and send crypto. A sub-client follows you by default, and TCB can set either flag for one sub-client. See Read what a sub-client may do.

Added

  • holdCrypto and sendCrypto in every capabilities object — on a sub-client's verification.capabilities and on every account. On your own accounts they are true when you hold crypto (your KYC is approved and you have an active crypto account). On a sub-client they take the same value by default and, like every flag, are false until it is VERIFIED.

Changed

  • Opening a crypto network while acting as a sub-client now needs its holdCrypto; without it the request is refused with 409 SUB_CLIENT_CAPABILITY_UNAVAILABLE.

2026-09-24 — Crypto networks for sub-clients

A sub-client holds one crypto account. A network is opened in it from the TCB app while acting as the sub-client, and gets the sub-client's own deposit address. Every EVM network the sub-client opens shows the same address. Deposits to it follow the rules of your own crypto deposit addresses. See Crypto deposits.

Added

  • crypto_deposit.credited now also fires for a credit on a sub-client's network. It is delivered to your endpoint and names the sub-client, whose x-on-behalf-of the fetch needs.
  • SUB_CLIENT_HAS_CRYPTO_BALANCE (409) on POST /sub-clients/{id}/close while any of the sub-client's crypto accounts holds an asset. On a successful close the sub-client's crypto deposit addresses retire with its IBANs. See Errors.

2026-09-24 — Search, filters and totals on the sub-client list

See Read your registry.

Added

  • q, country and type query parameters on GET /sub-clients: case-insensitive search across legal name, external ref and email; filter by ISO 3166-1 alpha-2 country; filter by sub-client kind.
  • totalHeldUsd, lastActivityAt and country on every row of GET /sub-clients — the sum of the sub-client's accounts priced in USD, its latest transaction, and its derived country. Present only on that call; GET /sub-clients/{id} and every write response are unchanged.

2026-09-24 — One IBAN per account, plus local details

Every currency account receives on the one IBAN of the account it belongs to, and carries local details for its currency where the currency has them. See The account tree.

Added

  • localRails on every account: details on the currency's domestic scheme (SEPA for EUR, Fedwire / ACH for USD, Faster Payments / CHAPS for GBP, SIC for CHF), each with its bank, numbers, paymentReference and active. Empty for JPY and BHD, for a crypto account, and for accounts opened before these details existed.

Changed

  • iban on a currency account is its account's IBAN, the same on every currency in that account; a payment on it lands in the currency account for the payment's currency.
  • Existing per-currency IBANs are retired. A currency account that had an IBAN of its own moves onto its account's IBAN, and its old IBAN is retired. Money that arrives on a retired IBAN is not credited to any account: TCB holds it, and an operator releases it by hand. Give your payers the IBAN iban now shows. The move sends account.iban.issued for the account's IBAN and no event for the retired one.
  • POST /accounts/{id}/iban returns the account's IBAN every time. ACCOUNT_ALREADY_HAS_IBAN is no longer returned.
  • account.iban.issued is sent once per IBAN, for the first account that shows it, not for every currency added.

2026-09-23 — One account per currency

An owner holds one account per currency. To hold the same currency separately, open another account: ask TCB to open one for you. See Open an account in a currency.

Changed

  • POST /accounts no longer opens a SUB beside the MAIN. It opens the owner's account in currency (201, role MAIN, with its IBAN) or, when the owner already holds that currency, returns that account unchanged (200). label and externalRef apply only to an account the call opens; requestIban is accepted and ignored.
  • OWNER_HAS_NO_MAIN_FOR_CURRENCY is no longer returned.
  • Existing SUB accounts are folded into the MAIN in the same currency: the balance moves by an internal transfer with reference FOLD-…, and the SUB is closed with its history kept. The fold sends no webhook; read the SUB's transfers or GET /accounts?role=SUB to see it.

2026-09-16 — Net with a counterparty

Two members that pay each other often can now offset instead of posting each movement gross. See Net with a counterparty.

Added

  • /netting/agreements — propose (one currency, your account under it), accept (naming yours), decline, end, list and read. position on every read carries what you owe, what you are owed, the net, who owes it and whether you may cut.
  • /netting/agreements/{id}/obligations and /netting/obligations/{id} — owe into the set, list, read and withdraw. An obligation reserves its full amount the moment it is submitted: available on this rail is balance minus ACTIVE holds minus OPEN obligations. Netting never extends credit.
  • POST /netting/agreements/{id}/cut — the member that owes the net cuts. One ordinary settlement posts for the net (none when zero) and every open obligation is netted in the same transaction. 403 NETTING_CUT_NOT_NET_PAYER for the other side; 409 SETTLEMENT_CONFLICT if the set moved under the cut, with nothing written.
  • Webhooks settlement.netting.agreement.proposed|accepted|declined|ended, settlement.netting.obligation.created|withdrawn and settlement.netting.cut; resource types settlement_netting_agreement, settlement_netting_obligation, settlement_netting_cut.
  • Codes NETTING_AGREEMENT_NOT_FOUND, NETTING_AGREEMENT_NOT_OPEN, NETTING_AGREEMENT_EXISTS, NETTING_NOT_YOURS, NETTING_INVALID, NETTING_HAS_OPEN_OBLIGATIONS, NETTING_OBLIGATION_NOT_FOUND, NETTING_OBLIGATION_NOT_OPEN, NETTING_CUT_NOT_NET_PAYER, NETTING_NOTHING_TO_CUT.

Changed

  • INSUFFICIENT_BALANCE on this rail now folds OPEN netting obligations into reserved beside ACTIVE holds. A member with no netting agreement sees no difference.

2026-09-16 — Convert what you receive

A member can now ask for everything it receives over the network to land in one currency. The settlement is unchanged — same-currency, final in the currency the sender chose — and the conversion happens on the receiver's own accounts after it commits. See Convert what you receive.

Added

  • inboundConversionCurrency on GET/PUT /network/profile. Set it to a currency you hold an active fiat account in and every settlement paid to you in another currency is sold for it on arrival, priced as your conversions normally are. null clears it. Naming a currency you cannot hold answers 422 INBOUND_CONVERSION_CURRENCY_UNAVAILABLE.
  • receipt on every settlement read: { amount, currency, conversionOrderId } on the receiver's read once its rule has filled, null everywhere else — including always on the sender's read. What the receiver does with money that is final and theirs is not the sender's to see.

Unchanged, and worth saying

  • A conversion that cannot be priced is not retried on a clock. The funds stay in the currency they arrived in and the receiver is told.
  • A return of funds lands in the currency it was sent.

2026-09-16 — Transfers move fiat, and name the state of a blocked account

POST /transfers is a fiat rail: it moves fiat between two accounts in your tree, in one currency, on TCB's ledger. A crypto balance is not moved by a ledger entry — it moves on-chain — so naming a crypto account on either side is now refused instead of posting. A leg that is unusable for a reason other than closure now says so in its own code. See Move money inside your tree.

Changed

  • A crypto account on either side of POST /transfers is refused with 400 TRANSFER_ASSET_NOT_SUPPORTED. Before this date a crypto-to-crypto pair inside your tree was accepted and posted as a ledger entry, which is not how a chain balance moves: the ledger figure and the chain could then disagree. Fiat-to-fiat in one currency is unchanged — including every transfer between your fiat accounts and your sub-clients'. To move crypto, send it on-chain. details.accountId and details.accountType name the offending side.

Added

  • 409 ACCOUNT_NOT_ACTIVE answers a transfer naming an account that is neither active nor closed — today that means frozen. 409 ACCOUNT_CLOSED now means what it says: the account is closed, which is irreversible. Before this date both states answered ACCOUNT_CLOSED, which sent you to the wrong remedy for a state that is lifted rather than permanent. Both codes carry details.status, and that is the field to key on: a state added to the enum later reaches you in a field you already read rather than as a code you have never seen. See Errors.

2026-09-14 — Choose who may pay you

The CONNECTED_ONLY receive policy now works. A member on it accepts settlements only from the senders on its own connections list; everyone else is still refused with 422 RECEIVE_POLICY_REFUSED. See Choose who may pay you.

Added

  • GET /network/connections, POST /network/connections and DELETE /network/connections/{memberClientAccountId} list, add and remove the members allowed to pay you. Receiver-owned: nothing is asked of the member you add.
  • connected on GET /network/resolve: whether you are on that member's list, so a sender learns before paying whether CONNECTED_ONLY would refuse it.

Changed

  • RECEIVE_POLICY_REFUSED is no longer unconditional for a CONNECTED_ONLY receiver: a listed sender is accepted. Two new codes, 422 CONNECTION_INVALID and 404 CONNECTION_NOT_FOUND, belong to the list itself.

2026-09-14 — Settlements with other TCB customers

Instant, same-currency payments to another TCB customer, settled directly on TCB's own ledger with no external rail — final on the response, and free. See the Settlements guide.

Added

  • POST /settlements settles to another network member — name the account to debit and exactly one of resolveToken, instrumentId or toAccountId — and GET /settlements and GET /settlements/{id} list and fetch settlements, scoped to the acting node. See Settle and Read a settlement.
  • GET /network/resolve confirms who a handle or TCB IBAN belongs to before you pay them, with a verdict on the name you supplied, and GET /network/search is a bounded prefix search over members who opted into the directory. See Confirm who you are paying and Search the directory.
  • GET /network/profile and PUT /network/profile read and set how you are addressed on the network: your handle (set once), whether you appear in directory search, your receive policy and your trading-name aliases. See Choose how you are paid.
  • POST /settlement-requests asks another member to pay you, with GET /settlement-requests, GET /settlement-requests/{id}, POST /settlement-requests/{id}/approve, POST /settlement-requests/{id}/decline and POST /settlement-requests/{id}/cancel to list, fetch, pay, refuse and withdraw one. See Ask to be paid.
  • POST /settlement-holds reserves funds against a named member without moving anything, with GET /settlement-holds, GET /settlement-holds/{id}, POST /settlement-holds/{id}/release and POST /settlement-holds/{id}/cancel to list, fetch, settle and cancel one. See Reserve before you pay.
  • POST /settlements/{id}/return-request asks the receiver of a completed settlement to send the money back, with GET /settlement-returns, GET /settlement-returns/{id}, POST /settlement-returns/{id}/accept, POST /settlement-returns/{id}/decline and POST /settlement-returns/{id}/cancel to list, fetch, accept, decline and withdraw one; accepting books a linked reversing settlement. See Ask for money back.
  • Eleven new webhook events: settlement.settled, settlement.reversed, settlement.request.created, settlement.request.approved, settlement.request.declined, settlement.hold.created, settlement.hold.released, settlement.hold.cancelled, settlement.return.requested, settlement.return.accepted and settlement.return.declined.
  • Three new scopes are in use: settlement:read on every read, including resolve and directory search; settlement:manage on PUT /network/profile and on creating, declining or cancelling a request, placing or cancelling a hold, and raising, declining or cancelling a return; settlement:execute on the calls that move money — POST /settlements, approving a request, releasing a hold and accepting a return.
  • A settlement names what it is linked to. settlementRequestId is the request it paid, settlementHoldId is the hold whose release booked it, and settlementReturnId is the return whose acceptance booked the reversal — on both the reversed settlement and the reversing one — or, while the settlement is COMPLETED and not itself a reversal, the return request open on it. reversedById is the settlement that reversed this one, present whenever status is REVERSED. reversalOfId, set only on a reversing leg, is the settlement it reverses. Each is null when nothing is attached.
  • Raising a return can answer 409 SETTLEMENT_CONFLICT: a concurrent change to the same settlement; nothing was written, retry the request.

Documentation

2026-09-10 — Crypto accounts appear in GET /accounts

GET /accounts was silently excluding every account funded by a crypto deposit address: it was reachable and fully usable at GET /accounts/{id}, but never listed. See Accounts.

Fixed

  • GET /accounts now lists every account you hold, fiat and crypto alike. ?currency=USDC (or any other asset you hold) now returns it too — before this date it returned an empty page for a currency you actually held. Nothing else about the endpoint changed: pagination, role, status, and the response shape are the same for every account it returns.

Known

  • A multi-asset container account is not listed. Where your onboarding provisioned them, your tree also holds a pair of container accounts. They carry no currency of their own and hold nothing directly — each asset sits beneath them — so GET /accounts leaves them out rather than reporting them as an account with no currency and a zero balance. GET /accounts/{id} still returns one if you name it, and reporting what sits beneath it is a filed follow-up. The accounts a deposit address funds are listed in full, so what you reconcile against those is unaffected.

2026-09-08 — Statements

Monthly, immutable statements of your sub-account ledger. See the Statements guide.

Added

  • GET /statements issues (on first request) or lists the monthly statements for your sub-account ledger, keyed on a closed calendar month.
  • GET /statements/{id} fetches one statement as JSON, or as text/csv with ?format=csv.

2026-09-07 — Documentation

The pages are regrouped around the two halves of the API — banking as a service and yield as a service. No route, field, error code or webhook changed.

Documentation

  • Two new pages describe the halves in full: Banking as a service — accounts and IBANs, money in, money moved, money out — and Yield as a service — fiat placed into a curated vault and held as a position. Each carries its own capability table, flow diagram, step-by-step routes and money map.
  • The overview is lighter. It points at the two pillar pages, keeps the capability summary, and leaves the flows and the step tables to them.
  • Core concepts on the overview grows from three entries to seven, grouped by pillar: sub-clients and verification level, accounts and IBANs, transfers and conversions, destinations and payouts, vault positions and sub-accounts, intents and idempotency, and webhooks and scopes.
  • Pilot scope is now the Roadmap: what follows the pilot, in sequence, as a compact table of six capabilities with what each does today and what comes next — including settlement with other TCB customers, statements and exports, and approval policies over the API. What the API does today lives on the two pillar pages instead, and the stated-direction list is dropped.
  • The diagrams are redrawn. The object model in How it works now separates your account tree from your vault ledger, and the four state ladders in The money lifecycle read left to right with every arrow coloured by where it lands, so the differences between the flows are visible at a glance.
  • The vault guide is now Vault deposits and withdrawals, and the reference groups it documents read Vault deposits and Vault withdrawals, so the vault leg is not read as a deposit held at a bank. The routes are unchanged.
  • Where TCB describes client-money accounts, How it works now says which objects those are: the sub-client and the accounts under it. The API uses the word sub-client everywhere.
  • Payouts are added to the money map in How it works, and Environments now states what is real for crypto deposits and for payouts.

2026-09-06 — Payouts

The pay half of third-party payouts. See the Payouts guide.

Added

  • POST /payouts pays a registered destination from a fiat account you control, and GET /payouts/{id} fetches one in its current state.
  • Scope payment:execute is now in use, on POST /payouts.
  • Three new webhook events: payout.settled, payout.failed and payout.returned.
  • New error codes: PAYOUT_RAIL_NOT_AVAILABLE, PAYOUT_CAP_EXCEEDED, DESTINATION_NOT_USABLE, DESTINATION_BLOCKED, DESTINATION_KIND_NOT_SUPPORTED, DESTINATION_CURRENCY_MISMATCH, DESTINATION_INCOMPLETE, SCREENING_UNAVAILABLE, PAYOUT_FAILED and PAYOUT_FAILED_PENDING_COMPENSATION.
  • The pilot scope page is updated: destinations and payouts are now one two-step capability, and what's next is paying a wallet destination and paying on behalf of a sub-client.
  • As the standing disclosure above states, the fiat rail this call submits to is simulated: no real money moves.

2026-09-06 — Market-priced conversions

A conversion is now priced only by an external venue that quotes independently of TCB. When no such venue can price the pair, the request is refused and nothing is converted; TCB no longer prices a conversion from an internal table. See Conversions.

Changed

  • POST /conversions refuses with 503 PRICING_UNAVAILABLE when no external venue returns a usable quote, instead of filling at a simulated rate. details.pair names the pair, nothing is persisted and the idempotency key stays usable; retry later. This code replaces 503 CONVERSION_NO_VENUE, which is no longer returned.
  • rate.pricingModel is MARKET on every conversion settled from this date. SIMULATED remains on conversions settled earlier and still means what it said: not a market rate.
  • The disclosure block's item on conversion rates now states that a conversion is market-quoted or refused. The block's version is 2026-09-06.

2026-09-06 — Crypto-credited funds move onward

A balance credited by a crypto deposit can now be converted and, as fiat, deposited into a vault — two calls, each idempotent, each announced by its own webhook events. See Move credited funds onward.

Added

  • POST /conversions accepts the account your crypto deposit address credits as sourceAccountId, and selects it when it is your only active account in fromCurrency. Before this change the call answered that account with 404 CONVERSION_ACCOUNT_NOT_FOUND.
  • details.accountIds on 409 CONVERSION_ACCOUNT_AMBIGUOUS names every active account of yours in that currency when more than one matches an omitted sourceAccountId or targetAccountId, so you can name the one you mean. TCB never picks between them.
  • 400 SOURCE_ACCOUNT_NOT_USABLE on POST /deposits and 400 DESTINATION_ACCOUNT_NOT_USABLE on POST /withdrawals when the named account is the one your crypto deposit address credits: a vault takes and returns fiat, so convert first. details.accountId names the account. Both were 404 before this change. Nothing moves and the key stays usable.
  • The pilot scope page no longer lists onward movement of crypto-credited funds as a planned capability: it is live.

2026-09-05 — Money caps

TCB may set ceilings on your account's deposits, withdrawals and conversions, per currency and per flow. No ceiling applies unless TCB has configured one for your account, TCB tells you your ceilings when they are set, and there is no API to read or change them. See Money caps and, for conversions, Money caps.

Added

  • 400 DEPOSIT_CAP_EXCEEDED, 400 WITHDRAWAL_CAP_EXCEEDED and 400 CONVERSION_CAP_EXCEEDED on POST /deposits, POST /withdrawals and POST /conversions. details.cap names the ceiling (maxTicket, daily, monthly, or maxOutstanding on deposits), details.limit its value and details.attempted what the figure would have become. Nothing moves, and the idempotency key stays usable.

Fixed

  • POST /conversions returned a 500 on every request. It completes again.

2026-09-03 — Crypto deposits

Accept crypto at a TCB-issued deposit address. See the Crypto deposits guide.

  • POST /crypto-deposit-addresses issues your static deposit address for one asset on one chain — USDC on Arbitrum Sepolia in the pilot — and creates the account arrivals are credited to.
  • A confirmed on-chain transfer to it is credited to that account, with the same permanent, replay-safe identity as every other money movement: the transaction hash is the deposit's identity, and one transaction carrying several transfers credits each of them separately.
  • GET /crypto-deposits and GET /crypto-deposits/{id} read every recorded arrival, credited or not.
  • crypto_deposit.credited joins the webhook catalogue — thin payload, fetched via the read routes, fired exactly once per credit.
  • An arrival the pilot does not accept is recorded and held, never credited and never silently dropped.

2026-09-03 — Payout destinations

The register half of third-party payouts. You can now register the destinations a future payout will name; the payout call itself is the immediately following capability and is not part of the API yet.

  • POST /beneficiaries registers a destination — the payee and its bank account or wallet in one call. A destination is never usable at registration: it must clear screening, TCB approval, and a holding period first, and you have no way to shorten or skip any of them — only a TCB operator can release a hold early. See Destinations.
  • GET /beneficiaries, GET /beneficiaries/{id} and DELETE /beneficiaries/{id} list, fetch and remove destinations. Removal keeps the record readable; nothing can be sent to a removed destination.
  • Two new webhook events: beneficiary.activated (a destination became usable) and beneficiary.blocked (screening or a TCB operator blocked it).
  • Two new scopes are in use: payee:manage on the writes, payee:read on the reads.
  • New error codes: BENEFICIARY_NOT_FOUND, and DUPLICATE_INSTRUMENT when you register details you already hold live.
  • The pilot scope page's third-party-payout exclusion is updated: the control half is live, and money leaving the bank via the API remains out of scope today.

2026-09-02 — Sub-client activation model

How a sub-client is held, what TCB needs before it can be activated, and what it may do before then.

  • A sub-client is one of your end customers. Each sub-client holds its own account with TCB, in its own name. You collect the identity information and TCB accepts it. You instruct on the sub-client's behalf.
  • Which onboarding model your firm gets is decided by TCB at your own onboarding, on its assessment of your firm and a signed reliance agreement, and RELIANCE is granted per client account. Being regulated does not by itself qualify a firm for it. Where reliance is not available, end customers are onboarded directly by TCB, on a level that is not available yet.
  • An activation baseline is now published. For a person: legal name and aliases, permanent residential address, date of birth, nationality, passport number where applicable, national ID details, your own reference for the sub-client, the verification method, result, date and file reference, and the PEP, sanctions and risk results your screening produced. For a business: legal identity, existence, registration, ownership and control sufficient to identify every natural-person owner or controller at or above 20 %. TCB runs its own screening at activation as well. Supporting documents stay with you and must be producible without delay.
  • No funds before VERIFIED. Creating a sub-client reserves its identifier and its MAIN accounts and allocates each IBAN, but that coordinate is not active: it is not to be given out, is not entered into verification of payee, accepts no funds, holds no balance, and nothing can be paid from it or moved within your tree on the sub-client's behalf. NOT_STARTED and REQUIREMENTS_DUE carry no capabilities, and RESTRICTED withdraws them all — a restricted sub-client's balance is held, not returned.
  • iban.active on every coordinate says which state it is in: false until its owner is verified, true afterwards.
  • The attestation is evidence, not the activation switch. verificationMethod, verificationResult, verifierRef, pepStatus, screeningResult, riskRating and cddPerformedAt are all required on it. An attestation missing a required field, or sent for a sub-client whose mandatory identity data is outstanding, is refused with 400 ATTESTATION_INCOMPLETE and nothing is recorded. TCB samples and tests the files behind attestations and may request documents at any time.
  • A legalName change on a verified sub-client moves it back to REQUIREMENTS_DUE and deactivates its IBANs until it is verified again. The coordinates are not re-issued; they reactivate carrying the corrected payee name.
  • A vault sub-account attributes your own position to your funds, strategies or products. One that stands for one of your end customers must be linked to a registered sub-client; how that link is expressed in the API is being finalised.

2026-09-02 — v1 pilot

The first published version of the TCB API and of these docs.

Added

Identity and access

  • GET /me — identify the principal a key authenticates as, and read its scopes, environment tag and key prefix.
  • API keys with per-route scopes, and the x-on-behalf-of header for executing a request as one of your sub-clients.

Sub-clients

  • POST /sub-clients, GET /sub-clients, GET /sub-clients/{id}, PATCH /sub-clients/{id} — register and maintain your end customers.
  • GET /sub-clients/{id}/identity — read the identity record you supplied.
  • POST /sub-clients/{id}/attestations, GET /sub-clients/{id}/attestations — append and read the due diligence you attest to having performed.
  • POST /sub-clients/{id}/currencies — add a currency, with its MAIN account and IBAN.
  • POST /sub-clients/{id}/close.

Accounts

  • GET /accounts, GET /accounts/{id}, POST /accounts — accounts under you or under one of your sub-clients, in MAIN and SUB roles.
  • POST /accounts/{id}/iban — issue a payment coordinate.
  • POST /accounts/{id}/close.

Vault deposits, withdrawals and conversions

  • POST /deposits, GET /deposits/{id} — fiat into a curated vault.
  • POST /withdrawals, GET /withdrawals/{id} — fiat back out.
  • POST /conversions, GET /conversions/{id} — convert one currency you hold into another.
  • A mandatory client idempotency key on every one of them, and a six-state lifecycle that names what happened to the money rather than a pipeline stage.

Positions

  • POST /sub-accounts, GET /sub-accounts — partition your position into named buckets, alongside the tcb:default bucket every client starts with.
  • GET /positions — settled, reserved and available shares, in total and per sub-account, beside the share balance of the wallet TCB holds for you.

Transfers

  • POST /transfers, GET /transfers, GET /transfers/{id} — instant movement of fiat between accounts inside your own tree.

Webhooks

  • POST /webhooks, GET /webhooks, GET /webhooks/{id}, DELETE /webhooks/{id} — register and manage endpoints.
  • POST /webhooks/{id}/rotate-secret — rotate a signing secret with an overlap window during which both signatures verify.
  • Thin, signed payloads for the terminal money events and for sub-client, account and transfer changes.

Machine-readable specification

Documentation

  • These docs replace the previous single-page reference. Guides, concepts and a generated per-operation API reference are now separate. The material that lived on the old front page has moved to Authentication, Conventions and Errors.

Pilot disclosure

On this page

2026-10-30 — Quote a payout or a conversion before you make itAdded2026-10-30 — How a sub-client is held2026-10-27 — A payout pays the Sending fee for its railAddedChanged2026-10-27 — Fiat amounts are held to the currency's minor unitChanged2026-10-22 — A crypto account's balance shows what is in flightChanged2026-10-20 — Your custody wallet is created on your first depositAdded2026-10-12 — The tokenised deposit chain, one step at a timeAddedChangedUnchanged, and worth saying2026-10-10 — A payout may be held for reviewAdded2026-10-10 — A deposit says when TCB has paused issuing the tokenAdded2026-10-09 — The position carries the rate the vault paysAddedRemoved2026-10-09 — A same-day deposit no longer moves what a position has earnedFixed2026-10-08 — What your vault position has earnedAddedChangedRemoved2026-09-24 — Crypto capabilities for sub-clientsAddedChanged2026-09-24 — Crypto networks for sub-clientsAdded2026-09-24 — Search, filters and totals on the sub-client listAdded2026-09-24 — One IBAN per account, plus local detailsAddedChanged2026-09-23 — One account per currencyChanged2026-09-16 — Net with a counterpartyAddedChanged2026-09-16 — Convert what you receiveAddedUnchanged, and worth saying2026-09-16 — Transfers move fiat, and name the state of a blocked accountChangedAdded2026-09-14 — Choose who may pay youAddedChanged2026-09-14 — Settlements with other TCB customersAddedDocumentation2026-09-10 — Crypto accounts appear in GET /accountsFixedKnown2026-09-08 — StatementsAdded2026-09-07 — DocumentationDocumentation2026-09-06 — PayoutsAdded2026-09-06 — Market-priced conversionsChanged2026-09-06 — Crypto-credited funds move onwardAdded2026-09-05 — Money capsAddedFixed2026-09-03 — Crypto deposits2026-09-03 — Payout destinations2026-09-02 — Sub-client activation model2026-09-02 — v1 pilotAddedDocumentationPilot disclosure