buyAmount, sellAmount, withdrawalAmount, depositAmount, availableBalance, and so on. No floats; no JSON numbers like 1000.50. Sending a number instead of a string returns 400 VALIDATION_BODY_FAILED. Reading the response with a JSON parser that auto-converts to float re-introduces the bug v3 is designed to avoid.
This illustrative object combines amount fields from several resource schemas. It is not a standalone request or response and omits the other fields required by those resources.
Why strings
Floats lose precision.0.1 + 0.2 is 0.30000000000000004 in JSON (and most languages). For monetary amounts that’s unacceptable: a $0.000000000004 discrepancy compounds across millions of trades.
Strings preserve every digit. Each language can parse them into whatever precise representation it uses internally (Decimal, BigDecimal, bigint*scale, etc.).
v2 used floats, a regular source of reconciliation bugs.
Format
A request body with
"withdrawalAmount": 1000 (number, not string) returns 400 VALIDATION_BODY_FAILED. The same status is returned for a request body with a negative amount (e.g. "withdrawalAmount": "-1.00") — request fields are typed as PositiveAmount and the schema regex rejects the leading sign before any business-rule check runs.
Prefix vocabulary
Amount fields are always prefixed to describe what they refer to. There is no bareamount field anywhere in v3 — every monetary field carries a qualifier and ends in Amount or Balance. A Deposit carries depositAmount, a Withdrawal carries withdrawalAmount, a Quote and Trade carry buyAmount / sellAmount, and a Balance carries availableBalance / totalBalance.
This is the inverse of v2, where a
Trade had a single bare amount field and you had to infer from context what it referred to. v3 removes the bare-amount field entirely.
Parsing in clients
Sending amounts
Stringify the value before adding it to a JSON body. Most JSON serializers will turn a string into a JSON string with no special handling.Precision rules per currency type
The server enforces:- Fiat: max 2 decimal places.
"100.00"✓,"100.123"→422 WITHDRAWAL_INVALID_AMOUNT_PRECISIONwithdetails: { maxDecimals: 2 } - Crypto: max 6 decimal places.
"100.000001"✓,"100.0000001"→ same error withmaxDecimals: 6
Currency codes
Amounts always travel with a currency code (currency, buy, sell).
Pattern:
^[A-Z0-9]{2,15}$ (some tokens contain digits, e.g. 1INCH).
Common mistakes
- Sending
withdrawalAmount: 1000.50(number) instead of"1000.50"(string). The number-vs-string distinction is enforced server-side and returns400 VALIDATION_BODY_FAILED. JSON serializers in some languages quietly emit numbers if you give them aDecimalorBigDecimal; explicitly.toString()before serializing. - Parsing into a native float. Round-tripping
"1.10"throughJSON.parsefollowed byNumber()lands you back in IEEE-754 territory. Wrap inDecimal/BigDecimalimmediately on read. - Formatting with locale separators. Use the period (
.) decimal separator and no thousands separators. A locale-aware formatter that emits"1,000.50"or"1.000,50"fails with400 VALIDATION_BODY_FAILED. Serialize amounts with an invariant/US formatter, not the user’s locale. - Stripping trailing zeros for display before persisting.
"1.00"and"1"parse to the same value but are not byte-equivalent strings. If you compare amounts as strings (e.g. for idempotent retries), normalize first. - Sending more precision than the currency allows. Fiat tops out at 2 decimal places, crypto at 6. Over-precision returns
422 WITHDRAWAL_INVALID_AMOUNT_PRECISIONwithdetails.maxDecimalsanddetails.currency. Round (don’t truncate) to the right scale before sending. - Sending a bare
amountfield. There is noamountfield anywhere in v3. AQuotecarriesbuyAmount/sellAmount/quoteAmount; aTradecarriesbuyAmount/sellAmount/executedAmount; aWithdrawalrequest and response carrywithdrawalAmount; aDepositcarriesdepositAmount. There is noratefield on eitherQuoteorTrade—quoteAmount/executedAmountis the computed counter-leg amount, not a standalone exchange rate. A bareamountkey is rejected (unknown field on the request body, or simply absent on the response). (v2’sreference_amountis gone — see Migration from v2 → Quote.)
Balance lifecycle: available vs total
EveryBalance row carries two amounts. Treat them as a state machine, not a snapshot.
availableBalance— spendable nowtotalBalance— total held in the currency, including any amount earmarked against PENDING operations (submitted withdrawals, in-flight settlements);availableBalance≤totalBalance
totalBalance − availableBalance. It is not broken out as a separate field in v3; a richer per-state breakdown may be added later.
Quotes do not reserve balance. Only EXECUTED trades and submitted
withdrawals do. A quote that never fires is free.
Walkthrough — a trade through its lifecycle
Starting balance: USD5000.00 total, 5000.00 available.
Trades are single-hop atomic — they debit one currency and credit another in the same operation. There is no intermediate held state for trades themselves, so
availableBalance and totalBalance move together.
Walkthrough — a withdrawal through its lifecycle
Starting balance: USD4000.00 total, 4000.00 available.
Withdrawals earmark funds at POST and release them at completion or failure. While
PENDING or PROCESSING, funds are out of availableBalance but still counted in totalBalance.
Which view to read
Common mistakes (balance edition)
- Trading against
totalBalance. You might “have” USD 5000 on the books but only USD 2000 spendable — trading againsttotalwill hitTRADE_INSUFFICIENT_BALANCEmid-day. (Trades and withdrawals use distinct, domain-scoped codes for insufficient balance; see the Errors page for both.) - Assuming the held difference equals PENDING withdrawals. The gap between
totalBalanceandavailableBalanceincludes any in-flight operation that locks balance — PENDING settlements, holds, multi-step transfers all count. - Polling
GET /v3/fx/balancesafter every trade. Trades return their post-trade state on the response. Use the canonical balance endpoint for snapshots, not for per-operation confirmation.
What’s next
Quickstart
A worked quote-then-trade flow that uses these amount fields.
Migration from v2
The float-to-string switch and how to convert client code.
Errors
WITHDRAWAL_INVALID_AMOUNT_PRECISION and other amount-related errors.Trade
Where
buyAmount, sellAmount, and quoteAmount come from.