---
url: /en/guide/errors.md
---
# Errors

On failure you get a non-200 status and a unified error body:

```json
{
  "code": 400,
  "reason": "AMOUNT_BELOW_MIN",
  "message": "amount below minimum",
  "metadata": { "minAmount": "10" }
}
```

| Field | Description |
|---|---|
| `code` | HTTP status code |
| `reason` | Stable error identifier — **branch on this** |
| `message` | English text for developers; may change, don't parse it |
| `metadata` | Dynamic parameters (string → string), see below |

## Reasons

| reason | HTTP | Meaning | metadata |
|---|---|---|---|
| `BAD_REQUEST` | 400 | Invalid parameters (missing field, bad format, …) | |
| `VALIDATOR` | 400 | Field validation failed; `message` names the field | |
| `TOO_MANY_REQUESTS` | 429 | Too many requests; retry later | |
| `NOT_FOUND` | 404 | Resource not found | |
| `MERCHANT_AUTH_FAILED` | 401 | Invalid / disabled API key, disabled merchant, or missing signature headers | |
| `SIGNATURE_INVALID` | 401 | Signature mismatch | |
| `REQUEST_EXPIRED` | 401 | `X-Timestamp` missing or outside ±30 s | |
| `NONCE_REPLAYED` | 401 | `X-Nonce` reused or of invalid length | |
| `IP_NOT_ALLOWED` | 403 | Source IP not in the allowlist | |
| `PERMISSION_DENIED` | 403 | The API key lacks the required scope | |
| `CHAIN_NOT_SUPPORTED` | 400 | Chain unknown or disabled | |
| `TOKEN_NOT_SUPPORTED` | 400 | Token unknown or not configured on this chain | |
| `ADDRESS_INVALID` | 400 | Malformed address | |
| `USER_NOT_FOUND` | 404 | User wallet does not exist (no address requested yet) | |
| `INSUFFICIENT_BALANCE` | 400 | Insufficient user balance (internal transfer) | |
| `TX_NOT_CREDITABLE` | 400 | Rescan: transaction not found, not yet on-chain, failed, or not paying a platform address | |
| `RECREDIT_REJECTED` | 403 | Rescan: the receiving address does not belong to this user | |
| `WITHDRAW_DISABLED` | 403 | Withdrawals are not enabled for the merchant | |
| `WITHDRAW_NOT_ALLOWED` | 400 | Withdrawals of this token on this chain are not allowed | |
| `AMOUNT_BELOW_MIN` | 400 | Below the minimum withdrawal amount | `minAmount` |
| `DUPLICATE_REQUEST` | 409 | `biz_id` already exists | `withdrawId`, `status` |
| `INVALID_STATE` | 409 | The current state does not allow this operation | |
| `HOT_WALLET_INSUFFICIENT` | 409 | Platform outbound wallet balance is insufficient; retry later or contact us | |
| `SIGNER_UNAVAILABLE` | 503 | Signing service temporarily unavailable; retry later | |
| `CHAIN_RPC_UNAVAILABLE` | 503 | Chain node temporarily unavailable; retry later | |
| `INTERNAL` | 500 | Internal server error | |

## Retrying

* **5xx and network errors**: safe to retry. For `POST /v1/withdraws` always retry with the **same `biz_id`**.
* **4xx**: fix the request first; an identical retry will fail again. `DUPLICATE_REQUEST` means an earlier attempt succeeded — continue with `metadata.withdrawId`.
* New reasons may be added; fall back to the HTTP status for unknown ones.
