Errors
One error envelope for every refusal, the codes you branch on, and what is worth retrying.
Every partner-plane failure returns the same JSON shape.
{
"statusCode": 409,
"message": "QUOTE_EXPIRED_OR_CONSUMED",
"error": "Conflict",
"requestId": "req-1756512000000-a1b2c3d"
}Two fields are appended when they apply:
errorCode— present on some refusals, absent on most.retryAfterSeconds— present on a429, mirrored into the standardRetry-Afterheader.
Branch on message, not on errorCode
The stable short codes arrive in message. errorCode is only set on
some refusals, so a client that switches on it silently falls through to a
default branch for most of the catalogue. Validation failures are the one
exception to the short-code shape: they read Validation failed on: <fields>.
requestId is on every error body and is also returned as the X-Request-Id
header. It is the only handle that resolves to your request in our logs — log it
on every non-2xx response and quote it when you
contact us.
Status codes
| Status | Meaning | Retry? |
|---|---|---|
202 | Accepted. The order exists; it has not settled. | — |
400 | The request body failed validation. | No. Fix the request. |
401 | Authentication failed. Uniform, with no reason given. | No. See below. |
404 | No such route, or no such order for your partner account. | No. |
409 | A conflict with existing state — a spent quote, a reused reference, a hash already attached. | Only after changing something. |
422 | The order breaches one of your caps. | No. Split it or wait for the window. |
429 | Rate limited. | Yes, after Retry-After seconds. |
401 is deliberately uninformative
A bad signature, an expired timestamp, a replayed nonce and an unknown key all return the same body:
{
"statusCode": 401,
"message": "An error occurred",
"error": "unauthorized",
"requestId": "req-1756512061412-9f4e2b7"
}Distinguishing them would tell an attacker which half of a guess was right. Debug against the worked example on the authentication page instead.
429 carries its own backoff
{
"statusCode": 429,
"message": "Too Many Requests",
"error": "Too Many Requests",
"requestId": "req-1756512184903-7c1d5a0",
"retryAfterSeconds": 12
}Honour retryAfterSeconds, or the Retry-After header that mirrors it. A tight
retry loop stays refused. The limit is 120 requests per minute per partner
across all your credentials — see Caps and limits.
The code catalogue
These are the values that arrive in message. Branch on them.
Quotes
| Code | Status | Meaning | What to do |
|---|---|---|---|
QUOTE_EXPIRED_OR_CONSUMED | 409 | The quote has expired or was already used to create an order. | Take a fresh quote. Do not retry this one. |
QUOTE_SIDE_MISMATCH | 409 | A buy quote was presented to the sell route, or the reverse. | Quote the side you are creating. |
Orders
| Code | Status | Meaning | What to do |
|---|---|---|---|
ORDER_EXCEEDS_PER_ORDER_CAP | 422 | The order is larger than your per-order ceiling. | Split it. |
ORDER_EXCEEDS_DAILY_CAP | 422 | The order would breach your rolling 24-hour ceiling. | Wait for the window to roll, or talk to us. |
ORDER_NOT_BUY_SIDE | 409 | A buy-only action was taken against a sell order. | Check the orderId. |
ORDER_NOT_SELL_SIDE | 409 | A sell-only action was taken against a buy order. | Check the orderId. |
ORDER_NOT_AWAITING_USDT | 409 | A transaction hash was attached to an order that is no longer awaiting a deposit. | Read the order; it has already moved on. |
EXPECTED_AMOUNT_MISMATCH | 409 | The amount presented does not match what the order expects. | Do not adjust and retry — read the order first. |
PARTNER_SUSPENDED | 409 | Your partner account cannot create orders. | Contact us. Retrying will not clear it. |
Deposits and payouts
| Code | Status | Meaning | What to do |
|---|---|---|---|
TX_HASH_ALREADY_ATTACHED | 409 | This order already has a transaction hash. | One hash per order. Read the order. |
TX_HASH_ALREADY_USED | 409 | That hash is already attributed to another order. | One deposit funds one order. Send a separate deposit. |
WALLET_NOT_DELIVERABLE | 409 | The withdrawal address cannot receive — usually not confirmed. | Confirm the address, then retry. |
PAYOUT_ACCOUNT_NOT_PAYABLE | 409 | The payout account cannot receive the KES. | Use another registered account, or contact us. |
Ignore codes you do not recognise
We may add codes. Treat an unknown message on a 4xx as a non-retryable
refusal, log it with its requestId, and surface it rather than retrying —
the safe default when you do not know why something was refused.
What is worth retrying
429— yes, after the interval it gives you.5xxand network timeouts — yes, with backoff, and reuse the samepartnerReference. That is what makes a retry safe: the same reference returns the existing order rather than creating a second.- Everything else — no. A
4xxmeans the request will be refused the same way until something changes.