Errors
Every error code the v2 API can return, with its HTTP status, meaning and what to do.
Every error has the same shape:
{
"success": false,
"error": {
"code": "AMOUNT_OUT_OF_RANGE",
"message": "Deposits in Kenya must be a whole number between 10 and 250000 KES.",
"details": { "min": 10, "max": 250000, "currency": "KES" },
"request_id": "req_9f1c2b3a4d5e6f70"
}
}- Branch on
code. Never branch onmessageordetailstext. messageis safe to log and usually safe to show.- Quote
request_id(also in theX-Request-Idheader) when you contact support. - When a
Retry-Afterheader is present, wait that many seconds before trying again.
How to react
| Kind of error | Retry? |
|---|---|
400 validation (INVALID_REQUEST, AMOUNT_OUT_OF_RANGE, …) |
No. Fix the request. |
401 / 403 key or scope errors |
No. Fix the key. |
Reconnect errors (DERIV_RECONNECT_REQUIRED, …) |
No. Send the customer through Deriv OAuth again, or ask for a new personal access token. |
409 state conflicts |
No. Fetch the transaction and follow its state. |
429 |
Yes, after Retry-After. |
502, 503, 504 |
Yes, with backoff. For deposits and withdrawals use the same idempotency key. |
Never start a new deposit or withdrawal because a request timed out. Retry with the same key, or fetch the transaction.
Gateway errors
These come from the Deripay v2 API itself.
| Code | Status | Meaning | What to do |
|---|---|---|---|
AUTH_MISSING_API_KEY |
401 | No API key on the request. | Send Authorization: Bearer <secret key> from your server. |
AUTH_INVALID_API_KEY |
401 | The key is unknown, revoked or malformed. | Check the key in the dashboard; rotate it if it leaked. |
AUTH_INSUFFICIENT_SCOPE |
403 | The key lacks the scope this endpoint needs. | Create a key with the required scope. |
AUTH_IP_NOT_ALLOWED |
403 | The request came from an IP outside the key's allowlist. | Add your server IP to the key or clear the allowlist. |
ACCOUNT_SUSPENDED |
403 | Your Deripay developer account is suspended. | Contact support. |
RATE_LIMITED |
429 | Too many requests for this key. | Back off for Retry-After seconds. |
INVALID_REQUEST |
400 | The body or query failed validation. | Fix the fields listed in error.details. |
IDEMPOTENCY_KEY_REQUIRED |
400 | A money-moving request was sent without Idempotency-Key. |
Send a unique key per logical attempt and reuse it on retries. |
IDEMPOTENCY_KEY_REUSED |
409 | The idempotency key was already used for a different request. | Use a new key for a new attempt. |
DERIV_APP_NOT_REGISTERED |
400 | client_id is not one of your registered Deriv apps. |
Add the Deriv OAuth client ID under Deriv apps in the dashboard. |
CONNECTION_NOT_FOUND |
404 | No connection with this ID belongs to your account. | Use the con_… ID returned when the customer connected. |
TRANSACTION_NOT_FOUND |
404 | No transaction with this ID belongs to your account. | Use the txn_… ID returned when the transaction was created. |
MARKET_NOT_AVAILABLE |
400 | The country is not an open market. | Call GET /markets and offer only the countries it returns. |
INVALID_PHONE_NUMBER |
400 | The phone number is not a valid mobile number for the market. | Collect a number in the market's own country. |
OPERATOR_REQUIRED |
400 | The market has several mobile money networks and none was chosen. Also returned when operator is not one of the market's codes. |
Send operator using a code from GET /markets. |
AMOUNT_OUT_OF_RANGE |
400 | The amount is outside the market's current limits. details has the limits. |
Read limits from GET /rates?country= before submitting. |
DEPOSITS_PAUSED |
503 | Deposits in this market are temporarily unavailable. | Disable the deposit button and try again later. |
UPSTREAM_UNAVAILABLE |
502 | Deripay's payment network did not answer in time. | Retry with the same idempotency key, with backoff. |
INTERNAL_ERROR |
500 | Unexpected gateway error. | Retry; contact support with the request_id if it persists. |
TRANSACTION_NOT_READY |
409 | An OTP, resend or confirm was sent before the transaction reached the payment network. | Fetch the transaction again shortly, then retry. |
UPSTREAM_ERROR |
502 | The payment network failed without a specific code. | Retry with backoff; contact support if it persists. |
API_VERSION_RETIRED |
410 | A request was sent to /api/v1. |
Move to /api/v2 with a new key. See Changelog. |
Payment network errors
These come from Deripay's payment network and are passed through unchanged, with their own HTTP status. Transaction errors carry the prefix of the operation: DEPOSIT_ or WITHDRAWAL_. Connection errors are not prefixed. The same base code can appear in either form, for example DERIV_RECONNECT_REQUIRED and DEPOSIT_DERIV_RECONNECT_REQUIRED. Handle both.
Connections and Deriv authorization
| Code | Status | Meaning | What to do |
|---|---|---|---|
DERIV_AUTH_FAILED |
401 or 403 (varies) | Deriv did not accept the access token, or it lacks a required scope such as payment. |
Send the customer through Deriv OAuth again, or ask for a new personal access token, with trade account_manage payment. |
DERIV_RECONNECT_REQUIRED (or prefixed) |
401 | Deriv has rejected the stored token. | Reconnect the customer, then retry. |
DERIV_SESSION_EXPIRED (or prefixed) |
401 | Older form of DERIV_RECONNECT_REQUIRED. |
Treat it the same way. |
DERIV_REFRESH_UNAVAILABLE (or prefixed) |
503 | Token state is temporarily unavailable. | Retry with backoff. Do not reconnect. |
DERIV_ACCOUNT_MISMATCH |
400 | preferred_account_id was not among the customer's Deriv accounts. |
Fix the account id and connect again. |
DERIV_ACCOUNT_NOT_ELIGIBLE (or prefixed) |
400 or 412 | No real USD Deriv account is available. | Ask the customer to connect with a real USD account. |
NICKNAME_NOT_FOUND, DEPOSIT_NICKNAME_NOT_FOUND |
412 | Deriv has not given the customer a nickname, which the payment agent transfer needs. | Ask the customer to set up their nickname in Deriv, then retry. |
DEPOSIT_SESSION_APP_MISMATCH, WITHDRAWAL_SESSION_APP_MISMATCH |
401 | The stored connection cannot be used for this operation. | Reconnect the customer. |
DERIV_CONNECTION_NOT_FOUND (or prefixed) |
404 | The payment network no longer knows this connection. | Reconnect the customer with POST /connections. |
Deposits
| Code | Status | Meaning | What to do |
|---|---|---|---|
DEPOSIT_INVALID_DEPOSIT_AMOUNT |
400 | The amount was refused by the payment network. | Refresh limits from GET /rates and ask again. |
DEPOSIT_INVALID_REQUEST |
400 | Required data was missing or malformed. | Fix the request. Do not retry blindly. |
DEPOSIT_OTP_INVALID |
400 | The OTP is badly formed. | Ask the customer for the code again. |
DEPOSIT_OTP_INVALID |
409 | The deposit is not waiting for an OTP, or the transaction is a withdrawal. | Fetch the transaction and follow customer_action. |
DEPOSIT_OTP_INVALID |
429 | Too many OTP attempts (at most five per deposit). | Stop. Fetch the transaction; it will end in failed or move on. |
Other provider failures on a deposit can use other DEPOSIT_… codes. Fetch the transaction before deciding what to tell the customer.
Withdrawals
| Code | Status | Meaning | What to do |
|---|---|---|---|
WITHDRAWAL_ALREADY_ACTIVE |
409 | The customer, Deriv login or phone already has a withdrawal in progress. | Fetch the existing withdrawal and wait for it to finish. |
WITHDRAWAL_COOLDOWN |
429 | A cooldown applies after a recent withdrawal. | Wait for Retry-After, then retry the same request. |
WITHDRAWAL_VELOCITY_LIMIT |
429 | Too many withdrawals in a rolling window. | Wait for Retry-After. Do not switch phone or account to get round it. |
WITHDRAWAL_INVALID_WITHDRAWAL_AMOUNT |
400 | The amount was refused by the payment network, for example above the main wallet balance. | Check GET /connections/{id}/balance and GET /rates. |
WITHDRAWAL_INVALID_OTP |
varies | The Deriv verification code is wrong or expired. | Fetch the transaction. If it is awaiting_code, let the customer re-enter or resend the code. |
WITHDRAWAL_INVALID_TRANSACTION_STATE |
409 | The withdrawal cannot do that from its current state, or the transaction is a deposit. | Fetch the transaction and update your screen. |
WITHDRAWAL_INVALID_REQUEST |
400 | Required data was missing or malformed. | Fix the request. Do not retry blindly. |
WITHDRAWAL_FORBIDDEN |
403 | The transaction does not belong to this connection. | Check the ids you stored. |
Errors in the event stream
GET /transactions/{id}/events can send an error event inside a 200 stream:
event: error
data: {"code":"MONITOR_UPSTREAM_ERROR","message":"Live status is unavailable. Poll GET /api/v2/transactions/{id}."}Stop streaming and poll GET /transactions/{id}. See Transactions.
Asynchronous failures
A transaction can fail after the API has answered 201. That is not an error response: the transaction moves to state: "failed" with a status_message, and you receive transaction.failed. Show status_message to the customer.