> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zet.money/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> HTTP status codes and error codes returned by the Zet API

# Error Codes

All error responses follow a consistent format:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description."
  }
}
```

## HTTP status codes

| Code  | Meaning                                                         |
| ----- | --------------------------------------------------------------- |
| `200` | Success                                                         |
| `201` | Created                                                         |
| `400` | Bad Request — invalid parameters                                |
| `401` | Unauthorized — missing or invalid API key                       |
| `404` | Not Found — resource doesn't exist                              |
| `409` | Conflict — duplicate reference or resource                      |
| `422` | Unprocessable Entity — valid format but business logic rejected |
| `429` | Too Many Requests — rate limit exceeded                         |
| `500` | Internal Server Error                                           |

## Error codes

### Authentication

| Code              | HTTP | Description                                                                                                        |
| ----------------- | ---- | ------------------------------------------------------------------------------------------------------------------ |
| `UNAUTHORIZED`    | 401  | Missing or invalid `x-api-key` header.                                                                             |
| `API_KEY_REVOKED` | 401  | The API key has been revoked. Contact [zetdotmoney@gmail.com](mailto:zetdotmoney@gmail.com) to generate a new one. |
| `IP_NOT_ALLOWED`  | 401  | Request from an IP address not in the allowlist.                                                                   |

### Validation

| Code                     | HTTP | Description                                                        |
| ------------------------ | ---- | ------------------------------------------------------------------ |
| `INVALID_REQUEST`        | 400  | Request body failed validation. Check the `message` for specifics. |
| `INVALID_AMOUNT`         | 400  | Amount must be a positive number.                                  |
| `INVALID_TOKEN`          | 400  | Token symbol not supported on the specified chain.                 |
| `INVALID_CHAIN`          | 400  | Chain identifier not supported.                                    |
| `INVALID_ADDRESS`        | 400  | Wallet or contract address is not a valid Ethereum/EVM address.    |
| `INVALID_BANK_CODE`      | 400  | Bank code not recognized. Use `/onramp/banks` to get valid codes.  |
| `INVALID_ACCOUNT_NUMBER` | 400  | Account number format is invalid. Must be 10 digits.               |

### Quotes

| Code              | HTTP | Description                                                                         |
| ----------------- | ---- | ----------------------------------------------------------------------------------- |
| `QUOTE_EXPIRED`   | 422  | The quote has expired. Request a new quote.                                         |
| `QUOTE_NOT_FOUND` | 404  | Quote ID not found or belongs to another API key.                                   |
| `NO_ROUTE_FOUND`  | 422  | No swap/bridge route available for this token pair. Try a different pair or amount. |
| `AMOUNT_TOO_LOW`  | 422  | Amount is below the minimum for this operation.                                     |
| `AMOUNT_TOO_HIGH` | 422  | Amount exceeds the maximum for this operation.                                      |

### Wallets

| Code                      | HTTP | Description                                              |
| ------------------------- | ---- | -------------------------------------------------------- |
| `WALLET_NOT_FOUND`        | 404  | Wallet ID not found or belongs to another API key.       |
| `DUPLICATE_EXTERNAL_USER` | 409  | A wallet already exists for this `externalUserId`.       |
| `INSUFFICIENT_BALANCE`    | 422  | Wallet doesn't have enough balance for this transaction. |

### Transactions

| Code                    | HTTP | Description                                                          |
| ----------------------- | ---- | -------------------------------------------------------------------- |
| `TRANSACTION_NOT_FOUND` | 404  | Transaction ID not found or belongs to another API key.              |
| `DUPLICATE_REFERENCE`   | 409  | A transaction with this `reference` already exists.                  |
| `TRANSACTION_FAILED`    | 422  | The on-chain transaction reverted. Check `errorMessage` for details. |

### Ramp

| Code                       | HTTP | Description                                                                |
| -------------------------- | ---- | -------------------------------------------------------------------------- |
| `BANK_VERIFICATION_FAILED` | 422  | Could not verify the bank account. Check the bank code and account number. |
| `RAMP_PROVIDER_ERROR`      | 500  | The ramp provider returned an error. Retry or contact support.             |
| `DEPOSIT_EXPIRED`          | 422  | The bank deposit window has expired. Initiate a new on-ramp.               |

### Webhooks

| Code                   | HTTP | Description                                                  |
| ---------------------- | ---- | ------------------------------------------------------------ |
| `WEBHOOK_NOT_FOUND`    | 404  | Webhook ID not found.                                        |
| `INVALID_WEBHOOK_URL`  | 400  | URL must be HTTPS and reachable.                             |
| `MAX_WEBHOOKS_REACHED` | 422  | Maximum number of webhooks (10) reached. Delete unused ones. |

### Rate limiting

| Code           | HTTP | Description                                                |
| -------------- | ---- | ---------------------------------------------------------- |
| `RATE_LIMITED` | 429  | Too many requests. Respect the `X-RateLimit-Reset` header. |

### Server

| Code                  | HTTP | Description                                                                              |
| --------------------- | ---- | ---------------------------------------------------------------------------------------- |
| `INTERNAL_ERROR`      | 500  | Unexpected server error. Retry with exponential backoff. If persistent, contact support. |
| `SERVICE_UNAVAILABLE` | 503  | Temporary maintenance. Retry after a few minutes.                                        |

## Handling errors

### Recommended approach

```javascript theme={null}
async function zetApiCall(endpoint, body) {
  const response = await fetch(`https://api.zet.money/v1${endpoint}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': process.env.ZET_API_KEY,
    },
    body: JSON.stringify(body),
  });

  const result = await response.json();

  if (!result.success) {
    const { code, message } = result.error;

    switch (code) {
      case 'RATE_LIMITED':
        // Retry after delay
        const retryAfter = response.headers.get('X-RateLimit-Reset');
        await sleep(retryAfter * 1000 - Date.now());
        return zetApiCall(endpoint, body);

      case 'QUOTE_EXPIRED':
        // Get a new quote
        throw new Error('Quote expired, please request a new one');

      case 'INSUFFICIENT_BALANCE':
        // Show user-friendly message
        throw new Error('Not enough balance for this transaction');

      default:
        throw new Error(`Zet API Error [${code}]: ${message}`);
    }
  }

  return result.data;
}
```
