buyAmount, sellAmount, withdrawalAmount, depositAmount, withdrawableBalance, 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 JavaScript and in most languages whose numbers are IEEE-754 floats, which is how JSON numbers are typically parsed. For monetary amounts that’s unacceptable: a $0.000000000004 discrepancy compounds across millions of trades.
Strings preserve every digit the API returns. FX responses can contain up to 16 fractional digits; parse them with a decimal representation such as Decimal, BigDecimal, or bigint*scale.
Format
An FX request body with a zero amount (
"0" or "0.00") or a negative amount (for example, "-1.00") returns 400 VALIDATION_BODY_FAILED. The FX request schemas reject zero and negative amounts before business-rule checks.
Prefix vocabulary
Amount fields are always prefixed to describe what they refer to. There is no bare top-levelamount field on any resource 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 withdrawableBalance / totalBalance. The one exception is a nested object that’s already qualified by its parent key: the exposure summary’s usageBreakdown.settlableNow.amount and .settlableLater.amount.
Every monetary field carries a qualifier and ends in
Amount or Balance. There’s no bare amount field to infer meaning from context.
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.Withdrawal precision rules per currency type
For withdrawal requests, the server enforces:- Fiat: max 2 decimal places.
"100.00"✓,"100.123"→422WITHDRAWAL_INVALID_AMOUNT_PRECISION withdetails: { 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, buyCurrency, sellCurrency).
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 returns400VALIDATION_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 with400VALIDATION_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 too many decimals on a withdrawal. Withdrawal requests allow 2 decimals for fiat and 6 for crypto; over-precision returns
422WITHDRAWAL_INVALID_AMOUNT_PRECISION withdetails.maxDecimalsanddetails.currency. Round (don’t truncate) to the right scale before sending. FX amount schemas allow up to 16 fractional digits. - Sending a bare
amountfield. No request body accepts a bareamountfield. 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 in a request is rejected (unknown field on the request body).
Balance lifecycle: available vs total
EveryBalance row carries two amounts. Treat them as a state machine, not a snapshot.
withdrawableBalance: spendable now. Never negative.totalBalance: total held in the currency, including any amount earmarked against PENDING operations (submitted withdrawals, in-flight settlements). OrdinarilywithdrawableBalance≤totalBalance, but it can go negative when the org owes a settlement in that currency;withdrawableBalanceis then0rather than a negative-adjacent figure.
totalBalance − withdrawableBalance (when totalBalance is non-negative). 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.
Net equity aggregate
GET /v3/fx/balances also returns an aggregate block (nested under data.aggregate) alongside the per-currency data.balances array:
aggregate.withdrawableBalance is the org’s net equity across every currency, converted to USD. It’s the ceiling on what can actually be withdrawn overall, as distinct from any single currency’s withdrawableBalance. It follows the same clamping rule: never negative, 0 when the org’s overall net position is negative.
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
withdrawableBalance 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 cancellation. While PENDING, funds are out of
withdrawableBalance but still counted in totalBalance.
Which view to read
What’s next
Quickstart
A worked quote-then-trade flow that uses these amount fields.
Errors
WITHDRAWAL_INVALID_AMOUNT_PRECISION and other amount-related errors.
FX quotes
Where
buyAmount, sellAmount, and quoteAmount come from.