Tokenised deposits, step by step
Run the vault deposit and withdrawal one step at a time — fiat to tcbUSD, tcbUSD into the vault and back out, through the vault's queue when it cannot pay at once.
Concepts: Intents and the money lifecycle · Vault sub-accounts
A vault deposit runs two steps in one call: it mints tcbUSD from your fiat, then puts that tcbUSD into the vault for shares. A vault withdrawal runs the same two steps in reverse. This page gives you each step as a call of its own.
| Step | Call | Moves | Scope |
|---|---|---|---|
| Mint | POST /token-mints | Fiat → the same amount of tcbUSD | vault:deposit |
| Subscribe | POST /share-subscriptions | tcbUSD → vault shares | vault:deposit |
| Redeem at once | POST /share-redemptions | Vault shares → tcbUSD, paid now | vault:withdraw |
| Redeem through the queue | POST /share-redemption-requests | Vault shares → tcbUSD, paid when the vault can | vault:withdraw |
| Redeem tcbUSD | POST /token-redemptions | tcbUSD → the same amount of fiat | vault:withdraw |
tcbUSD earns nothing on its own. Only vault shares carry the yield, so tcbUSD you hold between a mint and a subscription earns nothing while it waits.
Every route on this page acts as you, never as a sub-client, and refuses x-on-behalf-of with 400 ON_BEHALF_OF_NOT_ALLOWED_HERE.
Choose the steps or the bundled calls
POST /deposits and POST /withdrawals are unchanged, and they remain the simplest way in and out: one call, one idempotency key, one event, and no tcbUSD left booked to your sub-accounts.
Use the steps when you need one of these:
- To hold tcbUSD between the steps. Mint now and subscribe later, or subscribe part of what you minted.
- To redeem when the vault cannot pay at once. A bundled withdrawal is refused with
409 INSUFFICIENT_VAULT_LIQUIDITY. A queued redemption waits in the vault's queue and is paid when the vault can. - To keep the redeemed money as tcbUSD. A vault redemption pays tcbUSD, not fiat. Turning it into fiat is a separate
POST /token-redemptions, which you call when you choose.
Running the steps means one call, one key and one outcome per step. A failure in one step leaves the earlier steps settled: if a subscription fails after a mint settled, you hold the tcbUSD the mint produced, and nothing unwinds it for you.
The shares a subscription books are the same shares a deposit buys. Both appear on GET /positions.
Where your tcbUSD and shares are held
TCB holds your tcbUSD and your vault shares in the one TCB wallet assigned to you. You hold no keys. What each sub-account holds is a book entry against that wallet, and a sub-account can spend only what is booked to it: a step that names a sub-account is authorised against that sub-account alone, never against the whole wallet.
tcbUSD and shares move only through the steps on this page. No route sends tcbUSD or shares to a wallet outside TCB, and no route moves them from one sub-account to another.
Every step response carries custody.mode TCB_SUBACCOUNT and custody.walletAddress, the address of that wallet.
Every step needs an idempotency key, and a key is not reusable
Send it as the Idempotency-Key header or as the idempotencyKey body field, and replay it on every retry of the same step. Never mint a new key on a retry path: a new key is a second mint, a second subscription or a second redemption. A replay returns the original body with replayed: true. A queued redemption's replay returns the request as it stands now, because the vault moves it on after the first answer. The same key with different parameters is refused with 409 IDEMPOTENCY_KEY_REUSED, and a second call while the first is still running answers 409 IDEMPOTENT_REQUEST_IN_PROGRESS. The key is remembered permanently. The full rule is in Conventions.
Every step answers the same shape
All five steps, their fetches and their lists return one object. Read type to know which step it is.
| Field | Meaning |
|---|---|
id | The step id. Fetch it again at the same path plus /{id}. |
type | TOKEN_MINT, TOKEN_REDEMPTION, SHARE_SUBSCRIPTION, SHARE_REDEMPTION or SHARE_REDEMPTION_REQUEST. |
status | Where the step stands. See the state ladder. |
idempotencyKey | The key you sent, echoed back. |
replayed | true when this body is the stored result of an earlier call under the same key. Money moved once. |
subAccountId | The sub-account the step is booked to. |
currency | The currency of the step. |
amount | What you asked to move, normalised: a submitted "1000.00" comes back as "1000". Fiat on a mint, tcbUSD on every other step. |
token | symbol, address and chain of the token the step moves. |
vault | address and chain of the vault on the vault steps. null on a mint and a tcbUSD redemption. |
custody | mode and walletAddress: where the tcbUSD and the shares are held for you. |
fiatAccountId | The fiat account a mint debited or a tcbUSD redemption credited. null on the vault steps. |
tokens.moved | The tcbUSD the step moved — minted, burned, put into the vault or paid out of it. On a queued redemption, what has been paid so far. null until the step has moved anything. |
shares.moved | The vault shares the step moved, at the vault's own share scale — received by a subscription, burned by a redemption. null on the token steps, and until a vault step has settled. |
queue | The queue position and fills of a queued redemption. null on every other step. See the queue. |
onchain.txHash | The step's transaction: the mint, the burn, the vault deposit or the vault withdrawal. null until it lands, and on a queued redemption, whose transactions are in queue. |
settledAt | When the step reached a final state. null until it does. |
createdAt | When you submitted the step. |
disclosure | The disclosure printed at the top of this page. It is abbreviated in the examples. |
Follow the state ladder
GET on the step's /{id} reports the current state.
| Status | What it means | What to do |
|---|---|---|
PENDING | Accepted. Nothing irreversible has happened. | Wait or poll. |
EXECUTING_ONCHAIN | The step is in flight. | Wait or poll. |
QUEUED | Queued redemptions only. The shares wait in the vault's queue. Parts the vault has paid are already booked to you as tcbUSD. | Wait, or cancel. |
SETTLED | Terminal. The step is done. On a queued redemption, the request closed with something paid — all of it, or the part paid before you cancelled. | Done. |
CANCELLED | Terminal. Queued redemptions only. You cancelled before anything was paid, and the shares are available to the sub-account again. | Done. |
FAILED | Terminal. Nothing moved. Any tcbUSD or shares the step reserved are available again. | Safe to retry under a new key. |
FAILED_PENDING_COMPENSATION | Money may have moved and the outcome is not confirmed. | TCB resolves it — automatically where the outcome can be proven, by an operator otherwise. Do not retry. |
COMPENSATED | Terminal. TCB resolved the compensation. | Read the object for the outcome. |
A queued redemption never reaches FAILED_PENDING_COMPENSATION. It goes QUEUED once the vault's queue holds it, or FAILED if it never got there.
Never re-key a compensable failure
Every *_FAILED_PENDING_COMPENSATION and *_SETTLEMENT_NOT_RECORDED code, and LEDGER_ENTRY_NOT_RECORDED, means the same thing for your integration: money may have moved, or did. Retrying with a fresh idempotency key starts a second mint of your fiat, or a second irreversible redemption.
TCB resolves these, and nothing resolves them on a timer. Poll the original key or the step id. Quote the step id when you contact TCB.
Mint tcbUSD from fiat
POST /token-mints · scope vault:deposit · idempotency key required
Debits a fiat account you control and mints the same amount of tcbUSD into the TCB wallet assigned to you, booked to the sub-account you name. The mint charges no fee and reserves nothing. Your first mint creates that wallet before anything is charged.
| Field | Type | Required | Notes |
|---|---|---|---|
currency | string, 3 letters | yes | The fiat currency. Selects the token: USD mints tcbUSD. |
amount | decimal string | yes | The fiat to turn into tcbUSD. Never a JSON number. No more fractional digits than the currency settles at — USD settles at 2. |
sourceAccountId | UUID | no | The fiat account to debit. Omitted, your single active account in that currency is used; once you hold more than one, name it — see naming the right account. |
subAccountId | UUID | no | The vault sub-account to book the tcbUSD to. Omitted, your default sub-account is used. |
idempotencyKey | string, up to 100 chars | no | Alternative to the Idempotency-Key header. |
curl -X POST https://api.threecommabank.com/api/partner/v1/token-mints \
-H "Authorization: Bearer $TCB_API_KEY" \
-H "Idempotency-Key: acme-mint-2026-10-12-0001" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"amount": "1000.00",
"sourceAccountId": "1b9d4f07-6c23-4a8e-b510-9f2c7d3e5a68",
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395"
}'{
"success": true,
"data": {
"id": "7c1e5a90-2b4d-4f86-9e13-5a0d7b2c4e68",
"type": "TOKEN_MINT",
"status": "SETTLED",
"idempotencyKey": "acme-mint-2026-10-12-0001",
"replayed": false,
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395",
"currency": "USD",
"amount": "1000",
"token": {
"symbol": "tcbUSD",
"address": "0x5c9E0a7d4F3b21C68e0A9b7D1f2E34c5A6b7C8d9",
"chain": "ARBITRUM"
},
"vault": null,
"custody": {
"mode": "TCB_SUBACCOUNT",
"walletAddress": "0x3B9aC4f27D18E056b1C4a9F30d2E85c7A6b1F094"
},
"fiatAccountId": "1b9d4f07-6c23-4a8e-b510-9f2c7d3e5a68",
"tokens": { "moved": "1000" },
"shares": { "moved": null },
"queue": null,
"onchain": {
"txHash": "0x9d20b7e548c1f36a0742e9bd35c108af62d497130ba85e6c2f9d40718ac35be2"
},
"settledAt": "2026-10-12T10:15:34.000Z",
"createdAt": "2026-10-12T10:15:28.000Z",
"disclosure": { "version": "2026-10-12", "summary": "…", "items": ["…"] }
},
"timestamp": "2026-10-12T10:15:34.271Z"
}When TCB has paused issuance of the token, a mint is refused with 409 TOKEN_ISSUANCE_PAUSED and nothing is charged. details.idempotencyKeySpent is false when it was refused up front (the same key stays usable) and true when the pause landed mid-execution (that mint is terminal FAILED; the next attempt needs a new key). Redemptions are unaffected.
Put tcbUSD into the vault
POST /share-subscriptions · scope vault:deposit · idempotency key required
Moves tcbUSD booked to the sub-account into the currency's vault and books the shares it receives to the same sub-account.
| Field | Type | Required | Notes |
|---|---|---|---|
currency | string, 3 letters | yes | Selects the vault and its token: USD puts tcbUSD into the USD vault. |
amount | decimal string | yes | The tcbUSD to put in, up to 18 fractional digits. |
subAccountId | UUID | no | The sub-account whose tcbUSD is used and to which the shares are booked. Omitted, your default sub-account is used. |
idempotencyKey | string, up to 100 chars | no | Alternative to the Idempotency-Key header. |
The subscription is authorised against the sub-account's available tcbUSD alone — held minus reserved, as GET /positions/tokens reports it. The check, the claim on your key and a reserve on that tcbUSD are one atomic step, so two concurrent steps can never spend the same tcbUSD. The reserve is released when the subscription settles or fails. A subscription that ends compensable keeps its reserve held until TCB resolves it, and nothing releases a reserve because time passed.
curl -X POST https://api.threecommabank.com/api/partner/v1/share-subscriptions \
-H "Authorization: Bearer $TCB_API_KEY" \
-H "Idempotency-Key: acme-subscription-2026-10-12-0001" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"amount": "1000",
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395"
}'A settled subscription answers with type SHARE_SUBSCRIPTION, vault set, fiatAccountId null, tokens.moved the tcbUSD put in and shares.moved the shares received, at the vault's own share scale.
Redeem shares for tcbUSD at once
POST /share-redemptions · scope vault:withdraw · idempotency key required
Redeems vault shares booked to the sub-account for the tcbUSD amount you name. The vault burns the shares that amount is worth and pays the tcbUSD at once into the TCB wallet assigned to you, booked to the same sub-account. It pays tcbUSD, not fiat: to receive fiat, follow it with POST /token-redemptions.
| Field | Type | Required | Notes |
|---|---|---|---|
currency | string, 3 letters | yes | Selects the vault and its token. |
amount | decimal string | yes | The tcbUSD to take out, up to 18 fractional digits. You never name a number of shares. |
subAccountId | UUID | no | The sub-account whose shares are redeemed and to which the tcbUSD is booked. Omitted, your default sub-account is used. |
idempotencyKey | string, up to 100 chars | no | Alternative to the Idempotency-Key header. |
The redemption is authorised against the sub-account's available shares, the same way a vault withdrawal is, and reserves them while it is in flight.
When the vault cannot pay the amount right now it is refused with 409 INSUFFICIENT_VAULT_LIQUIDITY, and nothing moves. Redeem less, or queue it.
curl -X POST https://api.threecommabank.com/api/partner/v1/share-redemptions \
-H "Authorization: Bearer $TCB_API_KEY" \
-H "Idempotency-Key: acme-redemption-2026-10-12-0001" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"amount": "400",
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395"
}'A settled redemption answers with type SHARE_REDEMPTION, tokens.moved the tcbUSD paid to you and shares.moved the shares the vault burned.
Queue a redemption when the vault cannot pay now
POST /share-redemption-requests · scope vault:withdraw · idempotency key required
Places shares booked to the sub-account in the vault's redemption queue for the tcbUSD amount you name. The body is the same as an instant redemption: currency, amount (tcbUSD, up to 18 fractional digits), subAccountId and idempotencyKey. A queued redemption ends in tcbUSD exactly as an instant one does: each part the vault pays is booked to the sub-account as tcbUSD, and fiat is a separate POST /token-redemptions.
curl -X POST https://api.threecommabank.com/api/partner/v1/share-redemption-requests \
-H "Authorization: Bearer $TCB_API_KEY" \
-H "Idempotency-Key: acme-queued-2026-10-12-0001" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"amount": "400",
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395"
}'A request read back after the vault has paid part of it:
{
"success": true,
"data": {
"id": "e2a94c07-5d18-4b3f-8a60-1c7f9e2d4b53",
"type": "SHARE_REDEMPTION_REQUEST",
"status": "QUEUED",
"idempotencyKey": "acme-queued-2026-10-12-0001",
"replayed": false,
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395",
"currency": "USD",
"amount": "400",
"token": {
"symbol": "tcbUSD",
"address": "0x5c9E0a7d4F3b21C68e0A9b7D1f2E34c5A6b7C8d9",
"chain": "ARBITRUM"
},
"vault": {
"address": "0x7a2fD3c8e91B4A5d06E7c1F2b38A9d4C5e6f0B12",
"chain": "ARBITRUM"
},
"custody": {
"mode": "TCB_SUBACCOUNT",
"walletAddress": "0x3B9aC4f27D18E056b1C4a9F30d2E85c7A6b1F094"
},
"fiatAccountId": null,
"tokens": { "moved": "150" },
"shares": { "moved": null },
"queue": {
"onchainRequestId": "42",
"claimableAt": "2026-10-13T10:20:00.000Z",
"sharesEscrowed": "398.120000000000000000000000",
"requestTxHash": "0x2b7e90c4d15a3f86e0721c9bd48a5f3e60d1972c8b4af05e3d6c19a7f8e2b041",
"cancelTxHash": null,
"fills": [
{
"txHash": "0x8c41f7a20d93b65e1c08f4a2d79e3b5c60a1d84f2b97c3e05a6d1f8b4c2e7a90",
"tokens": "150",
"bookedAt": "2026-10-13T11:04:12.000Z"
}
]
},
"onchain": { "txHash": null },
"settledAt": null,
"createdAt": "2026-10-12T10:20:00.000Z",
"disclosure": { "version": "2026-10-12", "summary": "…", "items": ["…"] }
},
"timestamp": "2026-10-13T11:10:00.118Z"
}queue field | Meaning |
|---|---|
onchainRequestId | The request's id in the vault's queue. null until the vault confirms the request. |
claimableAt | The earliest time the vault may pay the request: its notice period. null until the vault confirms the request. The vault pays when it has the liquidity, which can be later than this. |
sharesEscrowed | The shares held in the vault's queue for this request, at the vault's own share scale. |
requestTxHash | The transaction that placed the request in the queue. |
cancelTxHash | The transaction that cancelled it, when you cancelled it. |
fills | What the vault has paid, part by part: txHash, tokens (the tcbUSD that part paid) and bookedAt (when it was booked to your sub-account, null while it is being booked). |
How a request moves:
- While it is
QUEUED, the shares it covers stay reserved on the sub-account, soGET /positionsshows them underreservedand no other step can spend them. Each part the vault pays is booked to the sub-account as tcbUSD by a TCB process that runs every few minutes, so a paid part appears infillsand onGET /positions/tokenswithin minutes of being paid.tokens.movedis what has been paid so far. - It ends
SETTLEDonce the vault has paid all of it, or once you cancel after part of it was paid. The paid part leaves the sub-account's shares, and any unpaid shares are released back to it. - It ends
CANCELLEDwhen you cancel before anything was paid. The reserve is released and the shares are available again. - It ends
FAILEDwhen the request never reached the vault's queue. Nothing moved and the shares are available again. The create call answers500 SHARE_REDEMPTION_REQUEST_FAILED; a new key is safe.
A request waits in QUEUED for as long as the vault takes. Nothing closes it because time passed.
Cancel a queued redemption
POST /share-redemption-requests/{id}/cancel · scope vault:withdraw · no body, no idempotency key
Takes the request out of the vault's queue. Shares not yet paid return to your sub-account; anything already paid stays paid. It answers 200 with the request in its new state: CANCELLED when nothing had been paid, SETTLED for the part that was. If the vault has taken the cancel but the request is not closed yet, the answer is QUEUED with queue.cancelTxHash set. The request closes on its own within minutes, and calling cancel again in the meantime gives the same answer.
curl -X POST https://api.threecommabank.com/api/partner/v1/share-redemption-requests/e2a94c07-5d18-4b3f-8a60-1c7f9e2d4b53/cancel \
-H "Authorization: Bearer $TCB_API_KEY"| Status | Code | What to do |
|---|---|---|
| 404 | SHARE_REDEMPTION_REQUEST_NOT_FOUND | No queued redemption with that id in your scope. |
| 409 | SHARE_REDEMPTION_REQUEST_NOT_CONFIRMED | The request is not on the vault's queue yet. Nothing was cancelled. Retry shortly. |
| 409 | SHARE_REDEMPTION_REQUEST_NOT_CANCELLABLE | The request is no longer waiting: it was paid, cancelled or failed. Read it for its outcome. |
| 503 | GAS_PREFLIGHT_FAILED | Nothing was cancelled. Retry. |
Redeem tcbUSD to fiat
POST /token-redemptions · scope vault:withdraw · idempotency key required
Burns tcbUSD booked to the sub-account you name and credits the same amount of fiat to a fiat account you control.
| Field | Type | Required | Notes |
|---|---|---|---|
currency | string, 3 letters | yes | The fiat currency to credit. Selects the token. |
amount | decimal string | yes | The tcbUSD to burn; the same amount of fiat is credited. No more fractional digits than the currency settles at — USD settles at 2 — because an account cannot be credited a fraction of a cent. |
destinationAccountId | UUID | no | The fiat account to credit. Omitted, your single active account in that currency is used; once you hold more than one, name it. |
subAccountId | UUID | no | The sub-account whose tcbUSD is burned. Omitted, your default sub-account is used. |
idempotencyKey | string, up to 100 chars | no | Alternative to the Idempotency-Key header. |
The redemption is authorised against the sub-account's available tcbUSD alone, and the tcbUSD is reserved while it is in flight. If the burn fails with nothing burned, the reserve is released and the tcbUSD is available again. If its outcome is not proven, the tcbUSD stays reserved until TCB resolves it.
curl -X POST https://api.threecommabank.com/api/partner/v1/token-redemptions \
-H "Authorization: Bearer $TCB_API_KEY" \
-H "Idempotency-Key: acme-token-redemption-2026-10-12-0001" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"amount": "250.00",
"destinationAccountId": "1b9d4f07-6c23-4a8e-b510-9f2c7d3e5a68",
"subAccountId": "5d3b8e10-2f47-4c9a-b6e8-0a71d4c2f395"
}'A settled redemption answers with type TOKEN_REDEMPTION, vault null, fiatAccountId the account credited and tokens.moved the tcbUSD burned.
Fetch and list the steps
Scope transaction:read on every route below.
| Fetch one | List |
|---|---|
GET /token-mints/{id} | GET /token-mints |
GET /token-redemptions/{id} | GET /token-redemptions |
GET /share-subscriptions/{id} | GET /share-subscriptions |
GET /share-redemptions/{id} | GET /share-redemptions |
GET /share-redemption-requests/{id} | GET /share-redemption-requests |
A fetch returns the step in its current state, including a terminal failure, which is reported as the object rather than as an error. It is the poll surface the compensable errors point at, and the fetch half of the thin webhook payloads. A queued redemption is read with its place in the queue and what has been paid, from the vault. An id that is not yours, or that belongs to a different step, answers 404 INTENT_NOT_FOUND — 404 SHARE_REDEMPTION_REQUEST_NOT_FOUND on the queued-redemption routes.
A list returns your steps of that kind, newest first, one page at a time, each row in the same shape as a fetch.
| Query | Required | Notes |
|---|---|---|
status | no | Only steps in this status: PENDING, EXECUTING_ONCHAIN, QUEUED, SETTLED, CANCELLED, FAILED, FAILED_PENDING_COMPENSATION or COMPENSATED. |
cursor | no | The nextCursor from the previous page. Omit it for the first page. Treat it as opaque. |
limit | no | Rows per page, 1-100. Defaults to 25. |
curl -G https://api.threecommabank.com/api/partner/v1/share-redemption-requests \
-H "Authorization: Bearer $TCB_API_KEY" \
--data-urlencode "status=QUEUED" \
--data-urlencode "limit=25"The page is items and nextCursor, which is null on the last page. A malformed cursor, limit or status is refused with 400 INVALID_AMOUNT.
Read your tcbUSD
GET /positions/tokens · scope position:read
The tcbUSD booked to each sub-account — held, reserved by steps in flight, and available — with the totals and the balance of the TCB wallet assigned to you. The totals can show walletTokens above held: a part of a queued redemption the vault has paid but that is not yet booked, and fractions of a cent left by a fiat withdrawal, sit in the wallet until they are booked. Every response carries yieldNote: tcbUSD earns nothing on its own, and only vault shares carry the yield. The fields and an example are in Positions. Your vault shares are on GET /positions.
Money caps
TCB may set ceilings on your account's money movement, per currency and per flow, the same four kinds as on vault deposits and withdrawals. The steps count against them like this:
| Step | Counted against | Refused with |
|---|---|---|
POST /token-mints | Its own tcbUSD-mint cap | 400 TOKEN_MINT_CAP_EXCEEDED |
POST /token-redemptions | Its own tcbUSD-redemption cap | 400 TOKEN_REDEMPTION_CAP_EXCEEDED |
POST /share-subscriptions | Your deposit cap | 400 DEPOSIT_CAP_EXCEEDED |
POST /share-redemptions | Your withdrawal cap | 400 WITHDRAWAL_CAP_EXCEEDED |
POST /share-redemption-requests | Your withdrawal cap | 400 WITHDRAWAL_CAP_EXCEEDED |
A vault subscription moves money into the vault exactly as a deposit does, and a vault redemption takes it out exactly as a withdrawal does, so they share those caps: a deposit cap bounds POST /deposits and POST /share-subscriptions together. The maxOutstanding ceiling counts subscriptions as money in and settled vault redemptions as money out.
No ceiling applies unless TCB has configured one for your account; there is no API to read or change them. A refused request moved nothing: details.cap names the ceiling, details.limit its value, and details.attempted what the figure would have become. The idempotency key stays usable — resubmit the same key when the window rolls over, or with a smaller amount. The daily and monthly figures count requests as soon as they are accepted; only a step that failed or was cancelled with nothing moved stops counting.
Handle the errors
Nothing has moved on any 4xx, or on a 503, unless the row says otherwise.
| Status | Code | Steps | What to do |
|---|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED / IDEMPOTENCY_KEY_INVALID | all | Send a key, in one place, within the allowed characters and length. |
| 400 | INVALID_AMOUNT | all | amount was not a positive decimal string, or a list query was malformed. |
| 400 | AMOUNT_PRECISION_EXCEEDED | mint, tcbUSD redemption | Round to details.allowedDecimals and resubmit. |
| 400 | INSUFFICIENT_BALANCE | mint | The source fiat account does not hold the amount. |
| 400 | INSUFFICIENT_SUB_ACCOUNT_TOKENS | tcbUSD redemption, subscription | The named sub-account's available tcbUSD does not cover it. Name the sub-account that holds it, wait for the in-flight step, or move less. |
| 400 | INSUFFICIENT_SUB_ACCOUNT_POSITION | both vault redemptions | The named sub-account's available shares do not cover it. |
| 400 | INSUFFICIENT_VAULT_POSITION | both vault redemptions | Your wallet does not hold enough shares to redeem that amount. |
| 400 | TOKEN_MINT_CAP_EXCEEDED / TOKEN_REDEMPTION_CAP_EXCEEDED / DEPOSIT_CAP_EXCEEDED / WITHDRAWAL_CAP_EXCEEDED | see Money caps | Resubmit the same key when it clears, or send less. |
| 400 or 403 | SOURCE_ACCOUNT_NOT_USABLE / DESTINATION_ACCOUNT_NOT_USABLE | mint, tcbUSD redemption | The named account is not a fiat account in that currency (400) or is not active (403). Name another account. |
| 403 | WALLET_NOT_ALLOWLISTED | subscription | Your wallet is not yet cleared for this vault. Contact TCB. |
| 403 | INSUFFICIENT_SCOPE | all | The key lacks vault:deposit, vault:withdraw, transaction:read or position:read. |
| 404 | VAULT_NOT_AVAILABLE | vault steps | No active vault for that currency. |
| 404 | SOURCE_ACCOUNT_NOT_FOUND / DESTINATION_ACCOUNT_NOT_FOUND | mint, tcbUSD redemption | The named account is not one of yours. |
| 404 | SUB_ACCOUNT_NOT_FOUND | all | The subAccountId is not one of yours. |
| 404 | INTENT_NOT_FOUND / SHARE_REDEMPTION_REQUEST_NOT_FOUND | fetches, cancel | No step with that id in your scope. |
| 409 | SOURCE_ACCOUNT_AMBIGUOUS / DESTINATION_ACCOUNT_AMBIGUOUS | mint, tcbUSD redemption | Name the account explicitly. |
| 409 | FIAT_ACCOUNT_NOT_PROVISIONED | mint, tcbUSD redemption | The named (or implied) fiat account is not yet set up to be debited or credited by this step. Name another account, or contact TCB. |
| 409 | TOKEN_CONFIG_NOT_AVAILABLE | all | The currency is not provisioned for this flow. Contact TCB. |
| 409 | TOKEN_ISSUANCE_PAUSED | mint | TCB has paused issuance of the token. Nothing was charged. Read details.idempotencyKeySpent — see the mint. |
| 409 | VAULT_SUBSCRIPTIONS_CLOSED | subscription | The vault is not accepting subscriptions right now. |
| 409 | VAULT_TOKEN_MISMATCH | vault steps | The vault is built on a different token than the active tcbUSD. Contact TCB. |
| 409 | INSUFFICIENT_VAULT_LIQUIDITY | instant vault redemption | The vault cannot pay that much now. Redeem less, or queue it. |
| 409 | STEP_AUTHORIZATION_CONFLICT | redemptions, subscription | Another step on the same sub-account was authorised at the same moment. Nothing moved — retry with the same key. |
| 409 | PARTNER_RAIL_FROZEN | tcbUSD redemption, vault steps | Reconciliation found an unexplained difference on your tcbUSD or your vault position, and the steps that draw on it are suspended. A tcbUSD freeze does not suspend payouts. Nothing moved. A person lifts it; contact TCB. |
| 409 | IDEMPOTENCY_KEY_REUSED | all | Same key, different parameters. Use a new key. |
| 409 | IDEMPOTENT_REQUEST_IN_PROGRESS | all | The first call under this key is still running. Retry the same key. |
| 409 | SHARE_REDEMPTION_REQUEST_NOT_CONFIRMED / SHARE_REDEMPTION_REQUEST_NOT_CANCELLABLE | cancel | See Cancel a queued redemption. |
| 500 | TOKEN_MINT_FAILED | mint | Nothing was charged. Safe to retry under a new key. |
| 500 | TOKEN_REDEMPTION_FAILED | tcbUSD redemption | Nothing was burned and the tcbUSD is available again. Safe to retry under a new key. |
| 500 | SHARE_SUBSCRIPTION_FAILED / SHARE_REDEMPTION_FAILED | subscription, instant redemption | The vault call was refused before anything reached the vault. The tcbUSD or shares are available again. Safe to retry under a new key. |
| 500 | SHARE_REDEMPTION_REQUEST_FAILED | queued redemption | The request never reached the vault's queue and the shares are available again. Safe to retry under a new key. |
| 500 | TOKEN_MINT_FAILED_PENDING_COMPENSATION / TOKEN_REDEMPTION_FAILED_PENDING_COMPENSATION / SHARE_SUBSCRIPTION_FAILED_PENDING_COMPENSATION / SHARE_REDEMPTION_FAILED_PENDING_COMPENSATION | the matching step | Money may have moved. Any reserve stays held until TCB resolves it. Do not re-key — see the callout above. |
| 500 | TOKEN_MINT_SETTLEMENT_NOT_RECORDED / TOKEN_REDEMPTION_SETTLEMENT_NOT_RECORDED / SHARE_SUBSCRIPTION_SETTLEMENT_NOT_RECORDED / SHARE_REDEMPTION_SETTLEMENT_NOT_RECORDED | the matching step | The step completed and only its record failed. Do not re-key. |
| 500 | LEDGER_ENTRY_NOT_RECORDED | subscription, instant vault redemption | The money completed but the exact share movement could not be read back, so no ledger entry was posted. Do not re-key; TCB retries the read and posts the entry, or an operator resolves it. |
| 503 | CHAIN_UNAVAILABLE / GAS_PREFLIGHT_FAILED / TOKEN_CONFIG_CHECK_FAILED | all | Nothing moved. Retry. |
| 503 | PARTNER_WALLET_PROVISIONING_FAILED | mint | Your first mint creates your custody wallet, and that could not complete just now. Nothing was charged and your idempotency key was not claimed: resend with the same key or a new one, with the same or changed parameters. |
The full catalogue is in Errors.
Receive the events
Subscribe at POST /webhooks to any of these. Each payload is thin: it carries the event type and a resource pointer, and you fetch the step to read its state.
| Event | Fires when | Fetch |
|---|---|---|
token_mint.settled / token_mint.failed | A mint settled, or failed with nothing charged | GET /token-mints/{id} |
token_redemption.settled / token_redemption.failed | A tcbUSD redemption settled, or failed with nothing burned | GET /token-redemptions/{id} |
share_subscription.settled / share_subscription.failed | A subscription settled, or failed with nothing sent to the vault | GET /share-subscriptions/{id} |
share_redemption.settled / share_redemption.failed | An instant vault redemption settled, or failed with nothing redeemed | GET /share-redemptions/{id} |
share_redemption_request.queued | A queued redemption is in the vault's queue | GET /share-redemption-requests/{id} |
share_redemption_request.settled | A queued redemption closed with something paid | GET /share-redemption-requests/{id} |
share_redemption_request.cancelled | A queued redemption was cancelled before anything was paid | GET /share-redemption-requests/{id} |
share_redemption_request.failed | A queued redemption never reached the vault's queue | GET /share-redemption-requests/{id} |
resource.type is token_mint, token_redemption, share_subscription, share_redemption or share_redemption_request. A part the vault pays on a queued redemption fires no event of its own; read queue.fills. A compensable outcome fires no event — see which webhook fires when.
The machine-readable specification for these endpoints is openapi.partner.v1.json. Where it and this page disagree, the specification is authoritative.