Money representation
Amounts are decimal strings on the wire and integer minor units internally. Never a floating-point number, in either direction.
Every amount this API accepts or returns is a string. Not a JSON number.
{ "kesAmount": "10000.00", "usdtAmount": "100.000000" }Never parse an amount into a float
JSON.parse converts a JSON number to an IEEE-754 double before any code of
yours runs. By the time you could check it, the rounding has already
happened. That is why these fields are strings on the wire: it makes the
unsafe operation something you have to choose, rather than something the
parser does to you.
The two units
Internally, and in any field named Minor or Micro, money is an integer
count of the smallest unit. There are exactly two:
| Currency | Unit | Factor | 1 unit is |
|---|---|---|---|
| KES | cents (minor) | 100 | KES 0.01 |
| USDT | micro | 1,000,000 | 0.000001 USDT |
USDT's factor is 1e6 because Tron USDT has six decimal places. It is not 1e18 — that is a different chain's convention and using it would misstate every amount by twelve orders of magnitude.
So:
KES 10,000.00is1000000minor units.100.000000 USDTis100000000micro units.
Decimal places are enforced, not rounded
The API validates the shape of the string and refuses anything it cannot represent exactly. It does not round for you.
| Field | Maximum decimal places | Rejected examples |
|---|---|---|
kesAmount | 2 | 10.001, 1e4, +10, 10,000, 0x64, "" |
usdtAmount | 6 | 1.0000001, 1e2, -1, 1 with a trailing space |
The patterns are deliberately strict — no sign, no exponent, no separators, no
whitespace. That strictness is load-bearing rather than fussy: BigInt('0x64')
is 100 and BigInt('') is 0n, so a permissive parser silently produces an
amount nobody typed.
A value with too many decimal places is a 400. Round it yourself, in the
direction your own accounting requires, before you send it.
Working with amounts correctly
Read the string. Do not coerce it.
// Wrong — the rounding already happened.
const kes = Number(order.kesAmount) * 100;
// Right — exact, and it throws rather than silently truncating.
const kesMinor = BigInt(order.kesAmount.replace('.', '').padEnd(/* … */));In practice, use a decimal library (decimal.js, big.js) or your language's
native decimal type, and convert to an integer minor-unit count once at the
boundary.
Do arithmetic in minor units, as integers.
Add, subtract and compare integers. BigInt in JavaScript, int64/bigint
elsewhere. A KES total assembled from cents is exact; one assembled from doubles
is approximately exact, which in a ledger means wrong.
Convert back to a decimal string only to display or to send.
Format from the integer, with a fixed number of decimal places — 2 for KES, 6 for USDT. Format once, at the very last step, so no rounded value ever feeds another calculation.
Compare exchange rates as decimals, never as floats.
rate is also a string. Two rates that differ in the last place are different
rates, and a float comparison will sometimes tell you they are the same.
Amount limits
Your account carries a per-order cap and a rolling 24-hour cap, both denominated in KES minor units. The daily cap is rolling rather than calendar-based: it does not reset at midnight, precisely so that an order cannot be doubled by splitting it across the boundary.
Exceeding either returns 422 with ORDER_EXCEEDS_PER_ORDER_CAP or
ORDER_EXCEEDS_DAILY_CAP. Your specific caps are set on your account — we do not
publish a default here, because the number that matters is yours and — is more
honest than a figure that is not.
Summary
- Amounts are strings on the wire, in both directions.
- KES has 2 decimal places, USDT has 6. Over-precision is a
400, not a rounding. - Convert to integer minor units at your boundary, do all arithmetic there, and format back to a string only to display or send.
- Never let an amount pass through a floating-point number at any point.