Fees
The fee is a named component beside the rate, not folded into it. The formula, the rounding direction, and which figure lands on your bank statement. Mechanics, never numbers: your rate is in your agreement, not on this page.
This page describes how pricing behaves on this API. It publishes no percentage, no minimum, no ceiling and no rate card, and it never will: your commercial terms are in your agreement with us, they differ between partners, and a number printed here would be wrong for most readers and stale for the rest.
What does belong here is the arithmetic, because your reconciliation has to agree with ours to the minor unit.
Three figures, and which one moves
Every quote and every order carries three KES figures. Reading the wrong one is the mistake this section exists to prevent.
| Field | What it is |
|---|---|
kesAmount | the trade principal — the amount the rate converts |
fee.kesAmount | the fee, disclosed as its own line |
kesTotalAmount | the figure that moves, and the only one on a bank statement |
The relationship has a direction:
- buy —
kesTotalAmount = kesAmount + fee.kesAmount. You pay the total, and the payment instructions on the order quote the total. - sell —
kesTotalAmount = kesAmount − fee.kesAmount. We pay you the total.
Reconcile against the total, not the principal
A client that adds the fee on both sides over-collects from its own users on
every sell. Read kesTotalAmount and let it carry the sign — that is what it
is for.
The relationship between kesAmount, rate and usdtAmount is unchanged and
still exact. The fee is never folded into the rate or into the USDT leg:
quoting the same trade with and without a fee returns byte-identical rate and
usdtAmount. That is what makes the fee a component you can see and check,
rather than something absorbed into the conversion.
The formula
In KES minor units (cents), on the principal:
fee = min(cap, ceil(principal × percentBps ÷ 10 000) + flat)
Three properties of that expression are worth stating, because each of them is a decision rather than an accident:
- The percentage applies to the principal, not to the total. A fee-inclusive base would be a fixed point, and solving it needs a second rounding rule.
- The percentage rounds UP to the cent, and that rounding favours OnLink. Said plainly because you should not have to discover it from a reconciliation: you owe the fee on both sides of the trade, so one converter serves both. Rounding down would also let a trade split into dust orders pay no percentage at all.
- The cap is applied LAST, after the flat. It is the one clamp here that favours you. Capping before adding the flat would charge the flat on top of the ceiling.
A worked buy
A principal of KES 1,000.00 (100 000 cents) at 1.90% + KES 0.30, uncapped:
| Step | Cents | KES |
|---|---|---|
| principal | 100000 | 1000.00 |
| percentage | 1900 | 19.00 |
| plus flat | 1930 | 19.30 |
| you pay | 101930 | 1019.30 |
kesAmount is "1000.00", fee.kesAmount is "19.30", kesTotalAmount is
"1019.30".
The same terms on a sell
| Step | Cents | KES |
|---|---|---|
| principal | 100000 | 1000.00 |
| fee | 1930 | 19.30 |
| we pay you | 98070 | 980.70 |
Same fee, opposite direction. kesTotalAmount is "980.70".
Where the rounding shows
A principal of KES 1.01 (101 cents) at 1.90% is 1.919 cents, which becomes
2 cents — not 1. Every fee you receive is already rounded; do not re-round
it, and do not recompute it from the percentage. Read fee.kesAmount.
Uncapped is not the same as free
fee.capKesAmount | Means |
|---|---|
null | uncapped — the formula's result stands, whatever it is |
"0.00" | free — the fee is capped at nothing on this trade |
| an amount | the fee never exceeds that amount on one order |
Treating null as free would understate what you are charged, which is the
expensive direction of that mistake. Branch on === null, never on falsiness.
When a fee makes a trade impossible
On a sell, a fee cannot exceed what the trade would pay out — there would be
nothing left to send you. That case is refused with
FEE_EXCEEDS_PROCEEDS (422), and the fix is to trade a larger
amount rather than to correct the request. The asymmetry is deliberate: a buy of
the same size still prices, because on a buy you can always pay the fee on top.
If your stored pricing cannot be applied at all, the quote is refused with
FEE_PRICING_INVALID (503) rather than being quoted at zero. A fee that silently
vanished would be indistinguishable from a fee we had chosen not to charge, and
nothing would ever report it.
The rate is not itemised further
rate is the whole price of the conversion, and it is not broken into
components — here or anywhere on the API. What you can see is the rate you
trade at: compare it against a mid-market reference and you know exactly what
the conversion costs you. The derived leg rounds in OnLink's favour, which the
quotes page also states.
So there are exactly two things to reconcile, and both are on every quote and every order: the two legs, and the fee.
What to ask us
- Your percentage, your flat component, and whether either is capped.
- What your invoice looks like, and on what cycle.
Email info@onlink.africa. Those are commercial questions with a per-partner answer, which is exactly why they are not documented here.