Amounts & Precision
Money is decimal strings, rates are basis points, and every rounding decision is made where you can see it.
Money is a decimal string
Every monetary value in a request or a response is a string in the asset's major units:
{"amount": "100.5", "currency": "usd"}
Never a float, and never a smallest-unit integer. Three consequences for your code:
- Parse into a decimal type, not a double.
Decimalin Python,BigDecimalin Java, a decimal library in JavaScript. Parsing"0.1"into a float and adding it three times is how reconciliation jobs start disagreeing with the ledger. - Send strings. A JSON number with a fractional part is refused with
422 invalid_parameter("send amounts as strings"), because a JSON parser has already turned it into a float and lost digits. A JSON integer is exact and accepted. A request amount must be positive, with at most 38 digits and 18 decimal places. - Amounts come back in plain notation with no trailing zeros. 7 USDC
reads
"7", half a dollar"0.5", zero"0". The same value reads the same in every response and webhook. Trailing zeros (or their absence) say nothing about the asset's precision: take that from the table below. - Compare values, not strings.
"10.50"in your request reads back as"10.5".
Precision per asset
| Asset | Decimals |
|---|---|
| USDC, USDT, EURC | 6 |
| EURe | 18 |
| Fiat amounts (USD) | 2 |
Decimals are the issuer's, and identical on every chain we deliver the
asset on. A wallet-send amount, or a quote estimate's target_amount,
finer than the asset's decimals returns 400 invalid_amount rather than
being rounded, so we never move a different number than the one you asked
for.
Rates are basis points
Every rate is in basis points, never percent.
| Value | Means |
|---|---|
"100" | 1% |
"30" | 0.3% |
"1000" | 10%, the maximum for partner_fee_bps |
"0" | No fee |
This applies to partner_fee_bps, corridor bps and spread_bps, and
every plan-level rate. No fee or spread field is in percent, so a value of
1 always means 0.01%, never 1%.
Exchange rates
exchange_rate on a conversion is target per source, before fees,
frozen at execution:
delivered = (source_amount - netted_fees) x exchange_rate
Only fee lines with "collection": "realtime" are netted. Lines with
"collection": "monthly" are metering for your invoice and did not reduce
the delivery. On the standard plan our conversion fee is monthly, so
deliveries go out whole.
The rate on a quote estimate is indicative. The rate on the conversion is the fact of record.
Rounding
Every rounding is explicit, and each one leans the same way for the same reason:
- Fee amounts are rounded half up to the cent.
- Delivered amounts are rounded down to the target asset's decimals, so we never deliver more than the rate and fees allow.
exchange_ratecarries 12 decimal places, rounded half up, and the delivery is priced with exactly the rate you see.- On a target-based quote estimate, the source amount needed is rounded up, so the requested delivery is always achievable.
total_cost_bpsis a decimal string and may be fractional ("7.5").partner_fee_bpstakes up to 3 decimal places (0 to 1000), andpartner_fee_flatup to 2 (0 to 1000, in the account's currency).- The combined partner fee is capped so a delivered amount can never go negative.
- Gas amounts on wallet sends are priced at the native-token rate noted when the send executed, and never re-priced. A token price move before month end is never your FX gain or loss.