Bybit API Error Codes: Complete Reference for Traders
Master Bybit API error codes with this practical reference guide. Learn what each code means, how to handle errors in code, and build more resilient trading bots.
Master Bybit API error codes with this practical reference guide. Learn what each code means, how to handle errors in code, and build more resilient trading bots.
If you've ever built a trading bot on Bybit, you've hit error codes. Maybe it was a 10001 on a rate limit, or a 33004 when your API key permissions were off. These aren't random numbers — each one tells you exactly what went wrong, if you know how to read them. This guide breaks down the full Bybit API error code system, shows you how to handle them in real code, and gets your bot recovering from failures instead of crashing at 3am.
Bybit's V5 API (the current unified API as of 2024) returns errors in a consistent JSON envelope. Every response — success or failure — includes a retCode field. A retCode of 0 means success. Anything else is an error. The retMsg field gives you the human-readable description, and the result field may be empty or partial on failures.
import requests
import time
import hmac
import hashlib
API_KEY = 'your_api_key'
API_SECRET = 'your_api_secret'
BASE_URL = 'https://api.bybit.com'
def generate_signature(params: str, timestamp: str) -> str:
param_str = timestamp + API_KEY + '5000' + params
return hmac.new(
API_SECRET.encode('utf-8'),
param_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
def get_wallet_balance():
endpoint = '/v5/account/wallet-balance'
timestamp = str(int(time.time() * 1000))
params = 'accountType=UNIFIED'
sign = generate_signature(params, timestamp)
headers = {
'X-BAPI-API-KEY': API_KEY,
'X-BAPI-TIMESTAMP': timestamp,
'X-BAPI-RECV-WINDOW': '5000',
'X-BAPI-SIGN': sign
}
response = requests.get(
f'{BASE_URL}{endpoint}?{params}',
headers=headers
)
data = response.json()
if data['retCode'] != 0:
print(f"Error {data['retCode']}: {data['retMsg']}")
return None
return data['result']
balance = get_wallet_balance()
print(balance)
Notice the pattern: check retCode first, then handle the result. This is the foundation of every reliable Bybit integration. Now let's look at the specific codes you'll actually encounter.
Bybit groups error codes into logical ranges. Understanding the groupings helps you triage problems fast — authentication errors sit in one range, order-related errors in another, and system-level issues in a third.
| Error Code | Category | Meaning | Common Cause |
|---|---|---|---|
| 0 | Success | Request successful | N/A |
| 10001 | Common | Parameter error | Missing or wrong param type |
| 10002 | Common | Request expired | Timestamp drift > recv_window |
| 10003 | Auth | Invalid API key | Wrong key or key deleted |
| 10004 | Auth | Invalid signature | Wrong secret or signing method |
| 10006 | Rate limit | Too many requests | Exceeding API rate limits |
| 10007 | Auth | IP not whitelisted | Calling from non-whitelisted IP |
| 10010 | Auth | Expired API key | Key past expiration date |
| 33004 | Auth | Key lacks permission | API key missing required scope |
| 110001 | Order | Order not found | Wrong order ID or already filled |
| 110007 | Order | Insufficient balance | Not enough funds in account |
| 110012 | Order | Price too low | Order price below minimum |
| 110013 | Order | Qty too small | Order size below minimum |
| 110043 | Order | Set leverage first | Leverage not set for symbol |
| 131001 | Withdraw | Withdrawal amount too low | Below chain minimum |
| 3100196 | Position | Cross/isolated conflict | Margin mode mismatch |
Error 10002 (request expired) is one of the most misunderstood. It happens when your system clock drifts more than the recv_window value (default 5000ms) from Bybit's servers. Fix it by syncing your system time with NTP, or by fetching Bybit's server time at startup and calculating an offset.
Authentication errors (10003, 10004, 10007, 33004) account for probably 60% of issues developers hit when first integrating the Bybit API. They look similar but have very different root causes. Compared to Binance, which surfaces authentication errors with slightly different structure, Bybit's V5 API is quite explicit — the retMsg usually tells you exactly what's wrong.
# Signature debugging helper — compare your string to what Bybit expects
import time
import hmac
import hashlib
def debug_signature(api_key: str, api_secret: str, params: str):
timestamp = str(int(time.time() * 1000))
recv_window = '5000'
# This is the EXACT string Bybit V5 expects you to sign
raw_string = timestamp + api_key + recv_window + params
print(f'String to sign: {raw_string}')
signature = hmac.new(
api_secret.encode('utf-8'),
raw_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
print(f'Generated signature: {signature}')
print(f'Timestamp used: {timestamp}')
return signature
# For POST requests, params is the JSON body string
# For GET requests, params is the query string
debug_signature('YOUR_KEY', 'YOUR_SECRET', 'accountType=UNIFIED')
Order-related errors in the 110xxx range are where most bot developers spend their debugging time. Unlike simple authentication failures, order errors often require understanding Bybit's trading rules for each specific instrument. Platforms like Bybit and OKX both enforce minimum order sizes and price precision rules at the API level — your bot needs to handle these gracefully rather than crashing.
import requests
import time
import hmac
import hashlib
import json
BASE_URL = 'https://api.bybit.com'
def place_order(api_key: str, api_secret: str, symbol: str,
side: str, qty: str, order_type: str = 'Market'):
endpoint = '/v5/order/create'
timestamp = str(int(time.time() * 1000))
recv_window = '5000'
body = {
'category': 'spot',
'symbol': symbol,
'side': side,
'orderType': order_type,
'qty': qty
}
body_str = json.dumps(body)
raw = timestamp + api_key + recv_window + body_str
sign = hmac.new(api_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
headers = {
'X-BAPI-API-KEY': api_key,
'X-BAPI-TIMESTAMP': timestamp,
'X-BAPI-RECV-WINDOW': recv_window,
'X-BAPI-SIGN': sign,
'Content-Type': 'application/json'
}
response = requests.post(f'{BASE_URL}{endpoint}',
headers=headers, data=body_str)
data = response.json()
# Handle specific order errors
error_handlers = {
110007: lambda: print('Insufficient balance — check your wallet'),
110012: lambda: print('Price too low — check symbol min price'),
110013: lambda: print(f'Qty too small for {symbol} — check minOrderQty'),
110043: lambda: print('Set leverage first for this symbol'),
10006: lambda: (print('Rate limited — sleeping 1s'), time.sleep(1))
}
ret_code = data['retCode']
if ret_code != 0:
handler = error_handlers.get(ret_code)
if handler:
handler()
else:
print(f'Unhandled error {ret_code}: {data["retMsg"]}')
return None
return data['result']
# Example usage
result = place_order('YOUR_KEY', 'YOUR_SECRET', 'BTCUSDT', 'Buy', '0.001')
if result:
print(f'Order placed: {result["orderId"]}')
The error_handlers dict pattern is particularly useful because it lets you add handling for new error codes without refactoring your flow logic. When you encounter a new code in production logs, just add an entry.
Error 10006 is the rate limit code, and hitting it repeatedly can get your IP or API key temporarily blocked. Bybit's rate limits vary by endpoint — the order endpoints are more restrictive than market data endpoints. Unlike Binance which uses a weight-based system, Bybit uses a requests-per-second model. Check the X-Bapi-Limit-Status and X-Bapi-Limit-Reset-Timestamp headers in every response to stay ahead of limits before you hit them.
const axios = require('axios');
const crypto = require('crypto');
const API_KEY = 'your_api_key';
const API_SECRET = 'your_api_secret';
const BASE_URL = 'https://api.bybit.com';
// Exponential backoff retry wrapper for Bybit API calls
async function bybitRequest(endpoint, params = {}, method = 'GET', retries = 3) {
const timestamp = Date.now().toString();
const recvWindow = '5000';
const queryString = new URLSearchParams(params).toString();
const rawString = timestamp + API_KEY + recvWindow + queryString;
const signature = crypto
.createHmac('sha256', API_SECRET)
.update(rawString)
.digest('hex');
const headers = {
'X-BAPI-API-KEY': API_KEY,
'X-BAPI-TIMESTAMP': timestamp,
'X-BAPI-RECV-WINDOW': recvWindow,
'X-BAPI-SIGN': signature
};
for (let attempt = 0; attempt < retries; attempt++) {
try {
const response = await axios({
method,
url: `${BASE_URL}${endpoint}`,
params: method === 'GET' ? params : undefined,
data: method === 'POST' ? params : undefined,
headers
});
const { retCode, retMsg, result } = response.data;
// Log rate limit status from headers
const limitRemaining = response.headers['x-bapi-limit-status'];
const limitReset = response.headers['x-bapi-limit-reset-timestamp'];
if (limitRemaining && parseInt(limitRemaining) < 10) {
console.warn(`Rate limit warning: ${limitRemaining} requests remaining`);
}
if (retCode === 10006) {
// Rate limited — exponential backoff
const delay = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Retrying in ${delay}ms (attempt ${attempt + 1})`);
await new Promise(r => setTimeout(r, delay));
continue;
}
if (retCode === 10002) {
// Timestamp error — sync time offset
console.error('Timestamp error — check system clock sync');
throw new Error(`Timestamp error: ${retMsg}`);
}
if (retCode !== 0) {
throw new Error(`Bybit API error ${retCode}: ${retMsg}`);
}
return result;
} catch (err) {
if (attempt === retries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
}
}
}
// Usage
bybitRequest('/v5/market/tickers', { category: 'spot', symbol: 'BTCUSDT' })
.then(data => console.log('BTC ticker:', data.list[0].lastPrice))
.catch(err => console.error('Failed:', err.message));
If you're running bots on multiple exchanges simultaneously — say Bybit for futures and Binance for spot — consider using a signal platform like VoiceOfChain to coordinate your entry signals. Getting a signal and then hammering the API to execute it wastes rate limit budget. Queue your orders and execute them in batches when signals arrive.
The difference between a bot that survives months in production and one that silently fails after a week is usually logging. When Bybit returns an error code, you need to know which order triggered it, what the parameters were, and whether it's happening repeatedly. Exchanges like Gate.io and KuCoin have similar requirements — a good error logging pattern works across all of them.
Structure your logs around three things: the error code, the full request parameters, and the timestamp. This lets you replay failed requests in testing and identify patterns — like 110007 (insufficient balance) errors that only happen during high volatility, or 10006 (rate limit) clusters that reveal your bot is spiking requests during signal events. If you're getting trade signals from VoiceOfChain or similar platforms, correlate your error timestamps with signal events to find these patterns.
Bybit API error codes aren't obstacles — they're a communication channel. Each code tells you exactly what went wrong if you take the time to read it correctly. The authentication errors (10003, 10004, 33004) tell you about your credentials and key configuration. The order errors (110xxx) tell you about instrument rules and account state. The system errors (10002, 10006) tell you about timing and throughput. Build your bot to treat every non-zero retCode as information, not just as a failure to log and ignore. Pair your error handling with real-time market context — platforms like VoiceOfChain can help you understand whether a failed order was a technical error or just a market condition that made your signal invalid before it could execute. Solid error handling is what separates a bot that runs for a week from one that runs for a year.