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

# Changelog

> Every change to the TetraFi API that a client can see, newest first, with what to do about it.

The API is in preview until launch, so a change here can still remove or rename something. From launch on, changes follow the [compatibility policy](https://tetrafi.mintlify.site/compatibility.md): additive only, with removals announced at least six months ahead.

**2026-11-04**

Amount precision

* An amount with more fractional digits than its asset has is refused before any quote or funding check, with `400 AMOUNT_PRECISION_EXCEEDED`; `details` names the `field`, the `decimals` sent and the `allowed` precision ([Conventions](https://tetrafi.mintlify.site/reference/conventions.md)).

**2026-11-03**

Firm only, and repeated signed reads

* The search filter `filters.executableOnly` is renamed `filters.firmOnly`, and it now keeps only firm quotes (`firm: true`): executable estimates held to `minOut`, such as order-book and AMM quotes, are left out too, as are indicative quotes. A request still sending `filters.executableOnly` is refused with `400 INVALID_REQUEST` ([Firm only](https://tetrafi.mintlify.site/search/firm-only.md)).
* A signed read (`GET` or `HEAD`) sent again unchanged inside the 300-second window is accepted, so polling needs no nonce. A signed write is still accepted once ([Sign requests](https://tetrafi.mintlify.site/authentication.md#sign-requests)).
* An API key placing an order without an `Idempotency-Key` header is refused with `400 IDEMPOTENCY_KEY_REQUIRED` ([Idempotency](https://tetrafi.mintlify.site/trade/idempotency.md)).
* The source refusal `BINDING_NOT_ON_CHAIN` is renamed `WALLET_NOT_ON_CHAIN`, with the same meaning ([Wallets and accounts](https://tetrafi.mintlify.site/trade/wallets-and-accounts.md)).
* Every path that names a workspace calls its parameter `workspaceId`, for example `GET /api/v1/workspaces/{workspaceId}/venue-enablements`. The URLs themselves are unchanged.
* A new source reason, `venueEnablementRechecking`: the venue's readiness for your workspace is being re-checked, so it wasn't asked. Request quotes again shortly ([Error codes](https://tetrafi.mintlify.site/reference/error-codes.md#venueEnablementRechecking)).

**2026-11-02**

What an order did, one trade history, and exchange account balances

* Every order, Basic and Advanced, carries what it actually did: `fills`, `quotedBuyAmount` and `settledBuyAmount`, the fees actually charged in `fees` (each with `includedInPrice`), the kinds whose amount was not recorded in `unrecordedFees`, and `settlesBy` ([What the order did](https://tetrafi.mintlify.site/trade/order-status.md#what-the-order-did)). Show an unrecorded fee as "not recorded", never as 0.
* The search filter `filters.commitment` is replaced by `filters.executableOnly`, a boolean: send `true` to leave indicative quotes out ([Firm only](https://tetrafi.mintlify.site/search/firm-only.md)). A request still sending `filters.commitment` is refused with `400 INVALID_REQUEST`, `details.field` `filters.commitment`.
* `GET /api/v1/prices` rows no longer carry `commitment`: they carry `firm` and `lastLook`, both always `false` ([Prices before you quote](https://tetrafi.mintlify.site/search/prices.md)).
* `GET /api/v1/workspaces/{workspaceId}/trades` lists the workspace's orders and exchange-account trades in one history, with cursor paging and CSV export. The key needs **Read trades** ([Trade history](https://tetrafi.mintlify.site/trade/trade-history.md)).
* `GET /api/v1/workspaces/{workspaceId}/trades/{tradeId}` reads one trade. A failed or refunded order's `settlement.failureReason` says why ([Read one trade](https://tetrafi.mintlify.site/trade/trade-history.md#read-one-trade)). A trade read with an API key no longer carries the compliance records attached to it: read those in the app.
* An API key with **Read treasury** reads an exchange account's balances, deposit instructions and deposit history at `…/venue-accounts/{venueAccountId}/balances`, `/deposit-instructions` and `/deposit-history`; these used to answer `403 JWT_REQUIRED`. A read whose authority is withdrawn while it runs answers `403 VENUE_READ_AUTHORITY_REFUSED` ([Exchange account balances](https://tetrafi.mintlify.site/trade/venue-balances.md)).
* `GET /api/v1/workspaces/{workspaceId}/venue-enablements` lists your exchange accounts with the same key, with cursor paging: each item's `accountId` is the `{venueAccountId}` of those reads ([List your exchange accounts](https://tetrafi.mintlify.site/trade/venue-balances.md#list-your-exchange-accounts)). The deposit history is one bounded window, `lookbackDays` 1 to 30, with no cursor; more than 1,000 records answers `DEPOSIT_HISTORY_WINDOW_TOO_LARGE`.

**2026-11-01**

Service-account tokens, key rules and response headers

* A service account's API key can be exchanged for a bearer token of at most 15 minutes, with the OAuth 2.0 client-credentials grant at `POST /api/v1/oauth/token`. `GET /api/v1/me` with the token answers for the key it was exchanged from: `auth` `oauthClientCredentials`, `keyId`, `workspaceId`, its live `scopes` and `organisationWallets` ([Service accounts and tokens](https://tetrafi.mintlify.site/service-accounts.md)).
* A key that requires request signing or holds venue trading is refused by the exchange with `400 unauthorized_client`, `error_uri` ending `#SIGNED_KEY_NOT_EXCHANGEABLE`: sign each request with it instead.
* Turning signing off on a key that can trade venues answers `409 VENUE_TRADE_KEY_SIGNING_REQUIRED`; the key is unchanged.
* A service account's organisation-wallet trades stop with `409 SERVICE_ACCOUNT_SPONSOR_NOT_CURRENT` when its sponsor is no longer a current member ([Its sponsor](https://tetrafi.mintlify.site/service-accounts.md#its-sponsor)).
* Every answer to a key with a per-minute limit carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, refusals included. A deprecated operation's answers will carry `Deprecation`, `Sunset` and `Link`; none is deprecated ([Conventions](https://tetrafi.mintlify.site/reference/conventions.md#versioning)).
* Reading a quote set with `GET /api/v1/quotes/requests/{quoteRequestId}` moments after it was returned answers `503 QUOTE_REQUEST_NOT_READY` instead of `404`: read it again after about a second.
* A venue-trading key that is revoked, expired or no longer current for its workspace is refused with `VENUE_TRADE_KEY_NOT_CURRENT`, and nothing is placed ([Error codes](https://tetrafi.mintlify.site/reference/error-codes.md#VENUE_TRADE_KEY_NOT_CURRENT)).
* Every quote carries `firm` (the venue is bound to the price until `expiresAt`) and `lastLook`, always `false`. Read firmness from `firm` and `expiresAt`: `commitment` said the same ([How search works](https://tetrafi.mintlify.site/search/how-it-works.md#executable-and-indicative-quotes)).
* Quote cards no longer carry `commitment`, in `POST /quotes` and requotes alike: read `firm` and `expiresAt`.
* Past a quote's `retainUntil`, a requote or an order answers `410 REQUOTE_WINDOW_CLOSED` with `next.kind: "search"`: search again. `410 QUOTE_EXPIRED` now means only that the quote lapsed and can still be requoted ([Requotes and expiry](https://tetrafi.mintlify.site/trade/requotes-and-expiry.md)).

**2026-10-31**

A key reads its own organisation wallets; fuller filtered order pages

* `GET /api/v1/me` with a workspace key answers `organisationWallets`: `venueTradeScope`, `venueTradeEnabled`, `requestSigningRequired`, the `grants` with each wallet's `financialAccountId`, and `blockedBy`, the code an organisation-wallet order would be refused with right now ([Check what a key can trade from](https://tetrafi.mintlify.site/authentication.md#check-what-a-key-can-trade-from)).
* `GET /api/v1/orders` with a `status` filter fills its page before answering, up to a scan limit. A short page with a `nextCursor` says `scanLimitReached: true`; a filter that matches nothing answers one page with no `nextCursor`.

**2026-10-31**

The last pay and receive words become sell and buy

What to change:

* Match the source reason `buyAmountUnsupported` where you matched `receiveExactUnsupported`, on quote sources, quote cards, price rows and `netReceived`.
* Match `sellChainInvalid` and `buyChainInvalid` where you matched `payChainInvalid` and `receiveChainInvalid`.
* Read `sizeLimits.fixedAmount` (`sellAmount` or `buyAmount`, the request field the limits bound) where you read `sizeLimits.amountSide` (`payExact` or `receiveExact`).

**2026-10-31**

Refused orders, and on-chain orders that answer before signing

What to change:

* Handle the order `status` `refused`, which is final (`terminal: true`). A pre-trade check refused the order before anything was sent, and `operation.complianceRefusal` names why. Such an order used to read `pending`. It cannot be confirmed, continued or cancelled: once the cause is cleared, request a new quote and place a new order. `GET /api/v1/orders/by-quote/{quoteId}` still answers `ordered` for its quote.
* A Basic order on an on-chain venue now answers `working` as soon as it is recorded, before its wallet signs. A wallet that then refuses to sign ends the order `unfilled`, with `operation.dispatch.notSentCause`, instead of answering `409`. Follow the order or its webhook to `terminal`.
* Read `outcomeUnknown` only as a lost outcome. An on-chain trade waiting for its transaction to confirm reads `working`, not `outcomeUnknown`.

**2026-10-31**

Advanced order detail in sell and buy

An Advanced order's detail, the `order` of a `kind` `advancedTrade` answer, states its amounts as sell and buy, in decimals.

What to change:

* Read `order.route.sell` and `order.route.buy`, where you read the route's pay and receive sides. Each carries its asset, chain and `decimalAmount`. When TetraFi cannot work out the decimals, the side carries `unresolved` (`assetNotInRegistry` or `amountUnavailable`) and no `decimalAmount`: decimals are never guessed.
* Stop reading `order.inputAmounts` and `order.outputAmounts`. Outputs that pay a fee are listed in `order.feeOutputs`, in decimals.
* Read each of `order.legs[]` as `sell`, `buy` (the leg's estimate) and `measuredBuy`, with `measuredBuySource`, where you read `inputAsset`, `inputAmount`, `outputAsset`, `outputAmount` and `measuredOutputAmount`.
* Read what was delivered from `order.deliveredBuy`, with `deliveredBuyProvenance`.

**2026-10-31**

Prices in sell and buy

`GET /api/v1/prices`, `GET /api/v1/prices/stream` and every `referenceMid` use sell and buy too. The old names are gone, with no aliases.

What to change:

* Query prices with `sell`, `buy` and exactly one of `sellAmount`, `buyAmount` or `sizes`, where you sent `pay`, `receive`, `amount` and `side`. Pin chains with `sell.chain` and `buy.chain`, and filter variants with `variants.sell` and `variants.buy`. An old parameter is refused with `400 INVALID_REQUEST` naming it.
* Read the price response's `sell`, `buy` and `sellBalances`, each row's `sellVariant` and `buyVariant`, and each size's `sell` and `buy`, where you read the pay and receive names.
* Read `referenceMid` as `sellUsd`, `buyUsd` and `buyPerSell`, on prices and on quote sets, where you read `payUsd`, `receiveUsd` and `receivePerPay`.

[Prices](https://tetrafi.mintlify.site/search/prices.md) covers the request.

**2026-10-31**

Company details before trading

A company, broker or liquidity provider workspace trades once its company details are on file: legal name, country of incorporation, registration number and registered address. TetraFi's business verification and members' identity verification no longer hold such a workspace back.

What to change:

* Expect `403 CAPABILITY_BLOCKED` on quotes, orders and new counterparty connections while company details are missing. Its `details.capabilityBlockers` holds `BASE_PROFILE_INCOMPLETE`, with the missing fields in `currentlyDue`. Add them in the TetraFi app under **Company profile**.
* Retry `503 CAPABILITY_UNAVAILABLE` with the blocker `BASE_PROFILE_UNREADABLE`: the details could not be read just now.
* `KYC_NOT_VERIFIED` now blocks only a direct user's own workspace.

[A workspace that cannot trade yet](https://tetrafi.mintlify.site/trade/errors.md#a-workspace-that-cannot-trade-yet) covers the refusal.

**2026-10-31**

Venue account fields renamed

What to change:

* On a venue account order (`kind` `basicTrade`), read `operation.label` where you read `operation.accountLabel`.
* On a venue connection, read `label` and `venueEnablementId` where you read `accountLabel` and `accountBindingId`.

**2026-10-31**

Sell and buy

A quote request names what you sell and what you buy, and fixes exactly one amount. The old `pay`, `receive`, `amount` and `side` fields are gone, with no aliases.

What to change:

* Send `sell` where you sent `pay`, and `buy` where you sent `receive`. Each is still `{asset, chain?}`.
* Send `sellAmount` where you sent `amount` with `side: "payExact"`, and `buyAmount` where you sent `amount` with `side: "receiveExact"`. Both are decimal strings in Asset units.
* Send exactly one of `sellAmount` and `buyAmount`. A body with both, or with neither, is refused with `400 INVALID_REQUEST` naming them.
* Send `filters.variants` as `{"sell": …, "buy": …}`, where it was `{"pay": …, "receive": …}`.
* Read each quote's `sell` and `buy` (`asset`, `chain`, `variant`, `amount`) where you read `pay` and `receive`. `buy.amount` is what lands at `deliverTo`.
* Stop sending `pay`, `receive`, `amount` and `side` on `POST /api/v1/quotes`: they are unknown fields now, refused with `400 INVALID_REQUEST`.

Requote and order bodies, and order answers, do not change. [Sell exact or buy exact](https://tetrafi.mintlify.site/search/sell-or-buy-exact.md) covers the new fields.

**2026-10-30**

List and cancel orders

* List your orders with `GET /api/v1/orders`: newest first, in the order envelope, filtered by `status`, `kind`, `sellAsset`, `buyAsset`, `from` and `to`, and paged with `cursor`. A page can hold fewer than `limit` orders, even none: follow `nextCursor` until it is `null`.
* Cancel an order before it is sent to a venue, or a pay-in waiting for funds, with `POST /api/v1/orders/{id}/cancel` and an `Idempotency-Key`. Afterwards it answers `409 ORDER_NOT_CANCELLABLE` with `details.status`. TetraFi never cancels at the venue or refunds.
* `GET /api/v1/workspaces/{workspaceId}/trades` is no longer part of the integrator API: use the order list.

**2026-10-30**

orderId on every order answer; minReceiveRequired on quotes

What to change:

* Read an order's id from the top-level `orderId` of the order answer, the status read and the recovery answer. It is the id `GET /api/v1/orders/{id}` takes, for Basic and Advanced orders alike.
* Send `minReceive` when the quote's `minReceiveRequired` is `true`, instead of working it out from `commitment`. A missing one is refused with `400 MIN_RECEIVE_REQUIRED`, with `details.field` `minReceive`.

**2026-10-30**

One order read, one 401 code, and renamed refusals

An order has one read, and every refusal has one code with one status.

What to change:

* Read every order at the `statusUrl` its answer gives: `GET /api/v1/orders/{id}`. It answers the same envelope as `POST /api/v1/orders`, for Basic and Advanced orders alike: `kind`, `status`, `terminal`, `statusUrl`, `next` when there is something to do, and the trade's own detail under `operation` or `order`. The recovery answer of `GET /api/v1/orders/by-quote/{quoteId}` also carries `statusUrl`.
* Read `status` from one camelCase list: `pending`, `working`, `filled`, `settled`, `unfilled`, `cancelled`, `failed`, `refunded`, `reviewRequired` and `outcomeUnknown`. Stop polling on `terminal`. While an order is not final, `next` says `wait` (read again after `retryAfter`) or `contact` (contact support with `reference`).
* Stop building the Basic trade's address from your workspace, connection and operation ids.
* Match `401 AUTH_REQUIRED` for every authentication failure, and read the cause in `details.reason`, for example `MISSING_CREDENTIALS`, `INVALID_API_KEY` or `SIGNATURE_INVALID`.
* Match the renamed codes: `QUOTE_PRICE_MOVED` (was `PRICE_MOVED_BEYOND_PROTECTION`), `VENUE_FIRM_QUOTE_LIMITED` (was `FIRM_QUOTE_LIMITED`), `QUOTE_ALREADY_ORDERED`, `QUOTE_ID_INVALID` for a malformed quote id, and `WALLET_SIGNATURE_REQUIRED` for a missing wallet signature on an order.
* Read `docUrl` on any refusal: it links the code's entry in the [error-code reference](https://tetrafi.mintlify.site/reference/error-codes.md).
* Deliver trade output to an address or one of your own accounts: no trade can deliver to a payee.

**2026-10-30**

An API key implies its workspace

An API key is bound to the workspace it was created in, and every request it sends now works in that workspace without `X-Workspace-Id`.

What to change:

* Stop sending `X-Workspace-Id` with an API key, or keep sending it: when present it must be the key's own workspace, or it is refused with `403 WORKSPACE_SCOPE_MISMATCH`.
* A signed-in member's session still sends `X-Workspace-Id`. Without it, the request is refused with `400 MISSING_WORKSPACE`.
* Read a placed order's status at the `statusUrl` its `POST /api/v1/orders` answer returns, instead of building the address yourself.
* Filter `GET /api/v1/workspaces/{workspaceId}/trades` with its camelCase parameter names only, for example `nextActor`, `dateFrom`, `amountMin` and `sortBy`. The snake\_case names are refused with `400`.

**2026-10-29**

Trading source and funding replace bind and binding

A quote request names what a trade executes with in `source`, and where its output goes in a top-level `deliverTo`. Each quote states its trading source and its funding. The old `bind`, `binding` and `bindingOptions` fields are gone, with no aliases.

What to change:

* Read `sourceOptions` where you read `bindingOptions`. It is listed on the quote set and on each quote. Candidates carry `financialAccountId`, `kind`, `displayName` and `isDefault` instead of `id` and `label`.

* Send `source.tradingWallets` (CAIP-2 chain id to `financialAccountId`) where you sent `bind.wallets`, `source.venueAccountId` where you sent `bind.account`, and `source.fundingAccountId` where you sent `bind.bankAccount`.

* Send `deliverTo` at the top level of the request or requote, not under `bind`. Its kinds are `financialAccount`, `payee` and `address`.

* Read each quote's `tradingSource`, `fundingModel`, `fundingAccount`, `settlement` and `deliverTo` where you read `binding`. A quote with no usable source carries `sourceRefusal`.

* Read a quote's `location` as `{ "kind": … }` only: `singleChain`, `crossChain`, `offChain` or `mixed`.

* On a `walletRefused` source, read `recovery.defaultWallets` and send it as `source.tradingWallets`, where you sent `recovery.defaultBinding` as `bind`.

* Match `SOURCE_NOT_OWNED` (403) where you matched `BINDING_NOT_OWNED`. It now refuses a `deliverTo` or `source.fundingAccountId` naming a wallet or account you cannot use.

* A trading wallet in `source.tradingWallets` that a venue account has not linked is no longer refused. That venue reads `unavailable` with the reason `tradingSourceNotLinked`, and the others still quote.

The other source refusal codes keep their names, for example `BINDING_NOT_ON_CHAIN`. [Wallets and accounts](https://tetrafi.mintlify.site/trade/wallets-and-accounts.md) covers the new fields.

**2026-10-27**

Consistent refusals, order status and source reasons

Every refusal of a quote, price, order, preflight, asset or order-status request, at every status, is now one JSON object:

| Field | What it is |
| - | - |
| `error` | The stable code. Match on it. |
| `message` | A sentence for people. It can change. |
| `requestId` | The same value as the `X-Request-Id` response header. Quote it to support. |
| `timestamp` | When the refusal was made, in Unix seconds. |
| `next` | The way forward, when there is one. |
| `details` | The code's detail, when there is any. Keys are camelCase and can be added. |

What to change:

* Read `requestId` where you read `traceId` before.
* Read waits from `details.retryAfter`, an ISO 8601 duration such as `PT2S`. The `Retry-After` header still carries the same wait in seconds.
* Fields a refusal used to send at its top level now sit under `details`.
* A request body that is too large now answers JSON, `413 REQUEST_BODY_TOO_LARGE`, instead of plain text.
* A missing or empty `X-Workspace-Id` answers `400 MISSING_WORKSPACE` everywhere. A malformed one answers `400 INVALID_WORKSPACE_ID`.
* Send credentials on `GET /api/v1/orders/{orderId}`. Without them, you get `401 MISSING_CREDENTIALS` before any order lookup. This one is rolling out: until it reaches a deployment, a read without credentials answers `404 ORDER_NOT_FOUND`.
* Read order `status` as a string, including `"failed"`. Failure details are in `failure.phase`, `failure.code` and `failure.message`, not inside `status`.
* Source reasons on quotes and prices are camelCase tokens, for example `pairNotServed` instead of `pair_unsupported` and `apiKeyOrganisationWalletScopeRequired` instead of `API_KEY_ORGANISATION_WALLET_SCOPE_REQUIRED`.
* Match `receiveExactUnsupported` when a source cannot quote a fixed receive amount. Quotes and prices now use the same token. Read it from `reason`, or from `detail` on a `filteredOut` row.

The [error-code reference](https://tetrafi.mintlify.site/reference/error-codes.md) lists every declared refusal and source reason. The `Idempotency-Key` rules for `POST /api/v1/orders` are now documented in full: see [Idempotency](https://tetrafi.mintlify.site/trade/idempotency.md).

**2026-10-27**

New developer docs

These docs now describe one API for search, quotes, requotes and orders, with a runnable sample on every page. They start from the [Quickstart](https://tetrafi.mintlify.site/quickstart.md).

The separate RFQ API, Router API, Price API and Trade History API pages are gone. Their old addresses redirect to the pages that replace them.

**2026-10-26**

Venue identity on quotes

Each quote leg and each entry in `sources` names the venue it comes from with `venueId` and `venueName`. Group sources and quotes by `venueId` for display. The existing `id` still identifies the route that executes, and it is the id `filters.venues` takes ([Venue types and venues](https://tetrafi.mintlify.site/search/venues.md#specific-venues)). Pools and solvers that are not a named venue leave both fields out.

**2026-10-17**

Quotes by asset pair, requotes, and orders by quote id

A quote request names the asset you pay and the asset you receive, not chains or wallets: `POST /api/v1/quotes` with `pay`, `receive`, `amount` and `side`. Every quote states where it executes and how firm it is.

* Changing a quote's wallet, account or route, or turning an indicative price executable, is a requote: `POST /api/v1/quotes` with `requote` set to the quote id. See [Requotes and expiry](https://tetrafi.mintlify.site/trade/requotes-and-expiry.md).
* `POST /api/v1/orders` takes the quote id. The server holds the quote's terms, so you never send them back. See [Orders](https://tetrafi.mintlify.site/trade/orders.md).
* `GET /api/v1/assets` lists what your workspace can trade, and `GET /api/v1/prices` returns indicative prices without asking any venue for a quote.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.