Caps and limits
Request rate, order ceilings, and the 429 contract.
Request rate
120 requests per minute, per partner — across all your credentials, so extra key pairs do not buy extra throughput. A second 120-per-minute counter applies per source IP, so separate integrations sharing one egress IP share that budget too; the tighter of the two applies.
Exceeding it returns 429:
{
"statusCode": 429,
"message": "Too Many Requests",
"error": "Too Many Requests",
"requestId": "req-1756512184903-7c1d5a0"
}The wait, in seconds, is in the Retry-After-partner header. The limit is
enforced by a throttler named partner, and the header carries that name; the
body has no retryAfterSeconds on this refusal.
Honour the interval
A retry loop that ignores Retry-After-partner stays refused and consumes the
budget it is waiting for. Back off for the interval given, then continue.
Polling is the usual reason integrations approach this limit. Use webhooks as the completion signal and polling as a fallback, and the limit stops being a consideration.
Confirmation resends
A resend requested before the shared resend ladder allows one is refused with
429 CONFIRMATION_RESEND_TOO_SOON, carrying retryAfterSeconds in the body
and mirrored into the standard Retry-After header:
{
"statusCode": 429,
"message": "CONFIRMATION_RESEND_TOO_SOON",
"error": "Too Many Requests",
"requestId": "req-1756512201044-2f7b9c1",
"retryAfterSeconds": 45
}Wait the stated seconds, then request another code.
Order, transfer and send ceilings
Two ceilings apply to order creation, transfer creation and USDT send creation:
- a per-instruction maximum, and
- a rolling 24-hour maximum that orders, transfers and USDT sends share — one ceiling across all three, not three independent ones. An order, a transfer and a send created within the same rolling day all draw on the same total.
Both are set per partner. The values are in your onboarding, not on this page — printing a number here would be wrong for most readers and would go stale for the rest.
A buy order counts its principal plus fee — the total that actually
leaves your KES balance — against both ceilings, never the principal alone; a
fee must never create headroom under a cap. A USDT send counts its KES
equivalent, valued at the mid-rate at the moment the check runs, so the same
send can consume slightly different headroom depending on when you make it. A
USD transfer counts at its KES equivalent too, at the mid-rate when you
create it, rounded up: its amount against the per-instruction cap, and its
amount plus fee against the rolling total, which keeps that figure for the rest
of the rolling day. Both ceilings stay in KES: USD has no ceiling of its own. If
no usable mid-rate is available when you create a USD transfer or batch, it is
refused 503 TRANSFER_DAILY_CAP_RATE_UNAVAILABLE, nothing is written, and you
can retry shortly.
Breaching either ceiling is a 422:
| Code | Meaning |
|---|---|
ORDER_EXCEEDS_PER_ORDER_CAP | This single order is above your per-order cap. |
ORDER_EXCEEDS_DAILY_CAP | It would breach your shared rolling 24-hour total. |
TRANSFER_EXCEEDS_PER_ORDER_CAP | This single transfer (a USD one at its KES equivalent) is above your per-instruction cap. |
TRANSFER_EXCEEDS_DAILY_CAP | It would breach your shared rolling 24-hour total. |
USDT_SEND_EXCEEDS_PER_ORDER_CAP | This single send, at its KES equivalent, is above your per-order cap. |
USDT_SEND_EXCEEDS_DAILY_CAP | It would breach your shared rolling 24-hour total. |
None of these is retryable as-is. Split the instruction, wait for the window to roll, or ask us to review your limits.
Reconciling an unexpected refusal
If a refusal surprises you, add your committed orders, transfers and USDT sends from the last rolling 24 hours together before concluding the cap itself is wrong — the shared total is what the check compares against, not any one of the three alone.
Conversions
A USD to KES conversion counts against none of the ceilings above. It is not an order, a transfer or a USDT send, so it neither consumes the per-instruction or rolling 24-hour totals nor is refused by them, and a conversion made today does not reduce the room you have for a transfer.
The only limits on a conversion are a minimum of USD 1.00
(CONVERSION_AMOUNT_BELOW_MINIMUM) and one maximum per conversion
(CONVERSION_AMOUNT_ABOVE_MAXIMUM). That maximum is the same for every partner
and is not set on your account, so it is not one of the per-partner values in
your onboarding. Contact us for the current figure; to convert more, send several
conversions.
Minimums
Transfers have no minimum beyond an amount greater than zero, and nor does a
sell order, provided the derived amount does not round to zero and, on a sell,
the fee leaves something to pay out. Moving USDT on-chain does: a USDT send, and a buy order whose
USDT is delivered to your own wallet, must carry at least 5.000000 USDT and at
most 2 decimal places. A USDT send below the floor is refused with
USDT_SEND_BELOW_MINIMUM — see
Send USDT from your wallet. A buy
order whose USDT is credited to your managed USDT balance (the default) has no
such minimum and no precision limit beyond the quote's 6 decimal places. For
precision, KES carries at most 2 decimal places and USDT at most 6 on a quote.
See Money.
A buy to your own wallet is judged at quote and at create
If your account delivers buys on-chain to your own wallet, the quote and POST /v1/orders/buy apply the floor and precision to the USDT amount before any
KES moves: below 5 USDT is 422 USDT_SEND_BELOW_MINIMUM, and more than 2
decimal places is 422 USDT_SEND_AMOUNT_PRECISION. A quote named by
usdtAmount is refused at quote time if it breaks either limit. A quote named
by kesAmount has its usdtAmount rounded down to 2 decimal places (by
less than 0.01 USDT, and kesAmount stays exactly as you named it), and is
refused at quote time only if the rounded amount is under 5 USDT. If your
account is credited to a managed USDT balance, none of this applies: buys of
any size and precision are accepted and the quote keeps all 6 decimal places.
Payment windows
Only one kind of sell order has a window: a sell collected from your own wallet
has 24 hours to have its collection submitted, or it expires. The deadline is
expiresAt on the create response — read it rather than assuming it. Once we
have recorded the collection the order no longer expires: it stays
awaiting_usdt until the settlement confirmation arrives or we resolve it.
A sell paid from your USDT balance is debited when you create it and normally
leaves awaiting_usdt at once. If the create returned LEDGER_POST_FAILED, or
the order is still awaiting_usdt afterwards, read it and contact us with the
orderId; do not wait on expiresAt. An order whose USDT debit was posted never
expires — it is held until we advance it or reverse the debit — while one whose
debit never posted expires with the rest.
The buy leg has none. A buy order debits your own KES balance the instant
you create it — there is no separate payment step to complete, so
expiresAt on a buy order is informational rather than a deadline you must
beat. See Buy USDT with KES for what
kesDebit.status tells you instead.
References and attribution
Which reference identifies which order, per leg — and why neither leg needs you to attribute a payment any more.
Products and scopes
Why an endpoint documented on this site answers 403 for your credential: every key carries an explicit list of what it may call, and there is no wildcard.