Buying and selling
Quote a fiat↔crypto order, let the user accept it, then watch it settle.
Trading is a two-step flow, deliberately. A quote is not an order, and an order is not a fill.
1. Quote
/order prices a buy, /sell prices a sale. Both take exactly one amount and derive the other:
Sending both, or neither, is a 400 Provide exactly one of fiatAmount or cryptoAmount.
The response is a trade in status open, with a tradeId and — under quote — the numbers you show the user:
Nothing is committed yet. A quote you never approve simply lapses at quote.expiresAt — roughly a minute out. You can drop it early with /trade/cancel, but you never have to.
2. Approve
/trade/approve is the committing step, and it is the one place a price can change:
⚠️ The quote is indicative. Approving prices the order again — holding the leg you fixed and moving the derived one — and that is the price that binds. Show the user the approval numbers before you tell them the order is done.
The confirmed figures come back on the top-level cryptoAmount / fiatAmount / price; quote still holds the original, so the difference is always yours to display.
Approving a quote that has already lapsed marks it expired and answers 409 Quote has expired — request a new one.
3. Settle
An approved order reaches completed when it fills. Depending on the asset it may settle inside the approve call or shortly after, so treat approved and executing as work in progress and poll /trade until the status is terminal.
Retrying safely
The trade itself is the idempotency key, so a repeated /trade/approve never trades twice:
- already
completed→ the original result is replayed unchanged. - still
executing→409 This trade is already being filled — poll /trade before retrying.
The one case that needs care is an ambiguous failure. A 504 — or a 502 that is not Order rejected: … — means the outcome is unknown, and the trade stays executing on purpose rather than being marked failed. Poll /trade to find out what happened. Re-approving is the only action that could double up, which is exactly why the service refuses it.
A 502 Order rejected: … is different: nothing traded, the trade is failed, and a fresh quote is the way forward.
Money and rounding
Amounts are decimal strings, never floats — fiat to 2 places, crypto to the asset’s own decimals. Prices are fiat per one whole unit of the asset.
Responses show every amount and price to at most 6 decimal places, with trailing zeros trimmed — a fiat leg of exactly one hundred comes back as "100", not "100.00", so parse these as decimals rather than matching on their text. The extra places are cut, never rounded, so a displayed figure is never larger than the one that settles — on an 18-decimal asset the amount you show can be a dust fraction below the amount actually traded.
Rounding is directional and always conservative: the fiat leg rounds against the user (up on a buy, down on a sell) and the crypto leg always rounds down. A rounded quote can therefore never be filled short. An amount so small that its counter-leg rounds to zero is refused with 400 Quoted amount rounds to zero — increase the amount.
What trading does not do
A completed trade records what was traded and at what price. It does not move coins. Crediting or debiting the user’s wallet is a separate step on your side — a /send, or your own ledger. This is deliberate: it means a trading failure can never leave custody half-moved.
Not every asset is tradable, and not every asset is enabled for every api client — an asset you cannot trade is a 403.