OnLink
Concepts

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:

CodeMeaning
ORDER_EXCEEDS_PER_ORDER_CAPThis single order is above your per-order cap.
ORDER_EXCEEDS_DAILY_CAPIt would breach your shared rolling 24-hour total.
TRANSFER_EXCEEDS_PER_ORDER_CAPThis single transfer (a USD one at its KES equivalent) is above your per-instruction cap.
TRANSFER_EXCEEDS_DAILY_CAPIt would breach your shared rolling 24-hour total.
USDT_SEND_EXCEEDS_PER_ORDER_CAPThis single send, at its KES equivalent, is above your per-order cap.
USDT_SEND_EXCEEDS_DAILY_CAPIt 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.

On this page