Using the APIAmounts & Precision

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. Decimal in Python, BigDecimal in 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

AssetDecimals
USDC, USDT, EURC6
EURe18
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.

ValueMeans
"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_rate carries 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_bps is a decimal string and may be fractional ("7.5").
  • partner_fee_bps takes up to 3 decimal places (0 to 1000), and partner_fee_flat up 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.