---
title: "Lock a USDT/KES rate"
method: POST
path: "/v1/quotes"
tags: ["Partner API"]
---

# Lock a USDT/KES rate

`POST /v1/quotes`

## Request

- Base URL: `https://sandbox.onlink.africa`
- URL: `https://sandbox.onlink.africa/v1/quotes`
- Auth: API key in header `X-OnLink-Key`

Returns a single-use, time-limited quote. Quotes are only issued off a live rate — a degraded rate source returns 503 rather than a locked rate we could not honour.

## Request body

- CreateQuoteDto
  - `side` 'buy' | 'sell', required: buy: you pay KES and receive USDT (on-ramp). sell: you send USDT and receive KES (off-ramp).
  - `kesAmount` string: KES amount as a decimal string, max 2 decimal places. Provide exactly one of kesAmount or usdtAmount.
  - `usdtAmount` string: USDT amount as a decimal string, max 6 decimal places (Tron USDT precision). Provide exactly one of kesAmount or usdtAmount.

## Response `201`

Quote locked

- QuoteResponseDto
  - `quoteId` string, uuid, required: The quote id. Single-use: pass it to POST /v1/orders/sell, which consumes it.
  - `side` 'buy' | 'sell', required: Echoes the side you asked for. `buy` prices the on-ramp, `sell` the off-ramp — a sell order will not consume a buy quote (409).
  - `kesAmount` string, required: The trade PRINCIPAL in KES, decimal string, always 2 decimal places (KES cents). Never a JSON number. Whichever leg you did not supply is the derived one, and it rounds in OnLink’s favour. `kesAmount × rate ↔ usdtAmount` holds exactly — **the fee is not folded into either.** The one exception: a buy named by `kesAmount` whose USDT is delivered to your own wallet has its `usdtAmount` rounded down to 2 decimal places (by less than 0.01 USDT; `kesAmount` is unchanged). What moves is `kesTotalAmount`.
  - `usdtAmount` string, required: USDT leg, decimal string, always 6 decimal places (Tron USDT precision). Never a JSON number.
  - `rate` string, required: KES per USDT at 2 decimal places, the rate this quote is locked at. Quotes are only ever issued off a live rate source.
  - `kesTotalAmount` string, required: The KES that actually moves, decimal string, 2 decimal places. **buy:** `kesAmount + fee.kesAmount` — this is what is debited from your KES balance. **sell:** `kesAmount − fee.kesAmount` — this is what we pay you. This is the figure that appears on a bank statement, so it is the one to reconcile against.
  - `fee` QuoteFeeDto, required
    - `kesAmount` string, required: The fee charged on this trade, KES, always 2 decimal places. On a **buy** it is ADDED to what you pay; on a **sell** it is DEDUCTED from what you receive. `"0.00"` means no fee. Never a JSON number.
    - `currency` 'KES', required: Always `KES`. The fee is charged in KES on both sides.
    - `percentBps` integer, required: The percentage applied, in basis points — `100` is 1.00%. Applied to the `kesAmount` PRINCIPAL, not to the total, and rounded UP to the cent. An integer, not a string: unlike the amounts, it is not money.
    - `flatKesAmount` string, required: The flat component applied, KES, 2 decimal places. Charged on every order regardless of size. `"0.00"` means no flat component.
    - `capKesAmount` string, nullable, required: The ceiling applied to the whole fee, KES, 2 decimal places — or `null` when the fee is UNCAPPED. **`null` and `"0.00"` are different**: `null` means there is no ceiling, `"0.00"` means the fee is capped at nothing and this trade is free.
  - `expiresAt` string, date-time, required: When the locked rate stops being executable, ISO 8601 UTC. Short by design — a long-lived locked rate is a free option against us.
  - `settlementEstimateSeconds` 1800 | 21600 | 86400, required: How long settlement is expected to take once your funds are confirmed, in whole seconds. **A different clock from `expiresAt`** — that is how long you have to send, this is how long settlement then takes. One of a small set of published bands rather than a continuously varying figure, so treat it as a band and not as a precise duration. An integer, not a string: unlike the amounts, it is not money.

## Other responses

- `400`: Both or neither amount supplied, or an amount is malformed
- `401`: Signature missing or invalid
- `422`: FEE_EXCEEDS_PROCEEDS — the fee equals or exceeds what a sell would pay out, so the trade is not viable at this size. Distinct from the 400 above on purpose: the amount is well-formed and the fix is to trade more, not to correct the request. For a buy delivered to your own wallet only: USDT_SEND_BELOW_MINIMUM (the USDT amount, after the 2 decimal place rounding of a kesAmount quote, is under 5 USDT) or USDT_SEND_AMOUNT_PRECISION (a usdtAmount has more than 2 decimal places).
- `503`: No live rate available, or FEE_PRICING_INVALID — this partner’s stored pricing cannot be applied. Either way no quote is issued, and neither is yours to fix.

---

[API](https://skmtc.dev/onlink/apis/onlink-partner-api.md) - [All operations](https://skmtc.dev/onlink/apis/onlink-partner-api/llms.txt) - [OpenAPI document](https://skmtc.dev/onlink/apis/onlink-partner-api/revisions/1d1c6a3671f2?raw)
