OnLink
Guides

Convert USD to KES

Turn the USD in your USD account into KES in your KES account with one call, and know exactly what rate you were paid.

A conversion takes USD from your USD account and pays KES into your KES account. It is available on a Collections account and needs the conversions:write scope to create and conversions:read to quote and read back — see Products and scopes.

Only USD to KES is supported. A conversion needs no confirmation code: it moves money between two accounts that are both yours.

GET /v1/balances shows both accounts on a Collections credential: the USD you can convert under usd, and the KES a conversion pays into under kes. If usd.available is null, your USD account cannot be read yet and usd.unavailableReason says why — see Balances.

1. Ask for a quote (optional)

Send from, to and amount as query parameters, for example ?from=USD&to=KES&amount=100.00:

GET /v1/conversions/quote

The answer:

{
  "rate": "128.35",
  "source": "100.00",
  "target": "12835.00",
  "indicative": true,
  "asOf": "2026-10-24T10:00:00.000Z"
}
  • rate is the KES you would be paid per 1 USD, to 2 decimal places.
  • target is source × rate, rounded down to the cent.
  • A quote is indicative. It does not hold a rate and it does not reserve money. The rate is fixed when the conversion executes.

2. Create the conversion

POST /v1/conversions
Content-Type: application/json
{
  "from": "USD",
  "to": "KES",
  "amount": "100.00",
  "partnerReference": "payout-run-2026-10-24",
  "idempotencyKey": "cnv-2026-10-24-0001"
}

The answer is a 202. The conversion has been accepted and is running:

{
  "id": "c0a8012e-7b1a-4c39-9d3e-2f5c8a1b6d40",
  "status": "processing"
}
  • idempotencyKey makes the create safe to retry. The same key with the same body returns the same conversion, and never converts twice. The same key with a different body is refused with IDEMPOTENCY_KEY_REUSED.
  • Before accepting, we check that your USD account holds at least amount. If it does not, the create is refused with CONVERSION_INSUFFICIENT_BALANCE and nothing moves. usd.available on GET /v1/balances is the figure to size amount from, but it is a reading up to thirty seconds old, not a reservation.
  • The minimum is USD 1.00. There is also one maximum per conversion; above it the create is refused with CONVERSION_AMOUNT_ABOVE_MAXIMUM. It is the same for every partner and is not set on your account. Contact us for the current figure. See Limits.

3. Wait for the result

A conversion ends in one of two states, and each sends a webhook:

statusWebhookWhat it means
processingnoneStill running. Wait for the webhook or read it back again later. If it is still processing 30 minutes after you created it, contact us and quote the conversion id.
settledconversion.settledThe USD left your USD account and the KES is in your KES account.
failedconversion.failedNo KES was paid. Any USD it took has been returned to your USD account.

Read it back with GET /v1/conversions/{id}:

GET /v1/conversions/{id}
{
  "id": "c0a8012e-7b1a-4c39-9d3e-2f5c8a1b6d40",
  "status": "settled",
  "from": "USD",
  "to": "KES",
  "source": "100.00",
  "target": "12835.00",
  "rate": "128.35",
  "partnerReference": "payout-run-2026-10-24",
  "failureReason": null,
  "createdAt": "2026-10-24T10:00:00.000Z",
  "settledAt": "2026-10-24T10:00:41.000Z",
  "failedAt": null
}

rate and target are null until the rate is fixed. Once they are set they never change: target is exactly source × rate, rounded down to the cent, and it is the KES paid into your account.

Why a conversion fails

failureReason (and reason on conversion.failed) is one of:

ReasonWhat happenedYour USD
insufficient_balanceYour USD account no longer held amount when the conversion ran.Never taken
debit_refusedThe USD could not be taken from your account.Never taken
fx_unavailableThe exchange could not be made.Taken and returned
provider_otp_not_receivedThe exchange could not be authorised in time.Taken and returned
rate_moved_beyond_toleranceThe rate moved too far between your create and the exchange, so we did not trade.Taken and returned

In every case no KES was paid, and you can create a new conversion with a new idempotencyKey. A conversion is reported failed only once any USD it took is back in your USD account.

Conversions and credit.received

A conversion moves money into one of your accounts, and a credit to one of your accounts usually also sends a credit.received event. So a conversion usually produces more events than its own two:

  • A settled conversion sends conversion.settled, and usually also a credit.received in KES on your KES account for the KES that landed.
  • A failed conversion that had taken your USD sends conversion.failed, and usually also a credit.received in USD on your USD account for the USD returned.

Do not rely on either credit.received: a conversion's legs do not always produce one, and when one arrives it carries no conversion id. Count a conversion once, on conversion.settled's target. Never count a credit.received for a conversion's funds as well, and never count the USD returned by a conversion.failed as income: it is your own USD coming back, and your USD balance is what it was before the conversion.

Limits

Conversions count against none of your caps. The per-order, per-instruction and rolling 24-hour ceilings in Caps and limits apply to orders, transfers and USDT sends, and a conversion draws on none of them, nor adds to what they have used.

The only limits on a conversion are these two, both applied to the USD amount when you create it:

  • a minimum of USD 1.00 (CONVERSION_AMOUNT_BELOW_MINIMUM), and
  • one maximum per conversion, the same for every partner and not set per account (CONVERSION_AMOUNT_ABOVE_MAXIMUM). To convert more, send several conversions.

Testing in sandbox

In sandbox a conversion runs on your simulated balances: it takes simulated USD from your USD account and pays simulated KES into your KES account, and sends the same webhooks as in production. Fund your USD account first with a simulated collection deposit, then watch usd.available go down and kes.available go up on GET /v1/balances once the conversion settles (a balance can be up to thirty seconds behind) — see Testing in sandbox.

Errors worth handling

Every code on these routes is catalogued in Errors — Conversions.

StatusMeaning
422A pair other than USD to KES, an invalid or out-of-range amount, or not enough USD. Nothing moved.
503Conversions are not enabled, a rate or your balance could not be read right now, or (CONVERSIONS_UNAVAILABLE) conversions are temporarily unavailable. Nothing moved; retry with the same idempotencyKey.
404No conversion with that id belongs to you.

On this page