Bybit API Error 33004: Fix Invalid Key Issues Fast
Bybit API error 33004 signals an authentication failure. Learn what causes it, how to debug your credentials, and fix it with working Python and JS code.
Bybit API error 33004 signals an authentication failure. Learn what causes it, how to debug your credentials, and fix it with working Python and JS code.
If you've built a trading bot or connected to Bybit's REST API and suddenly hit error 33004, you're not alone. This is one of the most common authentication errors on Bybit — and it almost always comes down to one of a handful of fixable mistakes. Understanding exactly what the API is rejecting saves you hours of guessing.
Error 33004 is returned by Bybit when the API cannot verify your identity. The full error message is typically: {"retCode": 33004, "retMsg": "apikey/ip not matched"}. This means the API key you're sending either doesn't exist in Bybit's system, is associated with a different account, or the request is coming from an IP address that isn't whitelisted for that key.
Unlike error 10003 (which is a rate limit issue) or error 33002 (wrong signature), error 33004 is specifically about the key itself not being recognized or not being permitted to make requests from your current network location. Bybit, like OKX and Binance, enforces strict IP binding on API keys when you configure it during key creation.
Important: Bybit API keys are environment-specific. A key created on the Testnet will NOT work on Mainnet and vice versa. Always double-check which base URL you're hitting: testnet.bybit.com vs api.bybit.com.
The most underrated cause? Copy-paste errors. When you copy your API key from Bybit's dashboard and paste it into a .env file or config.yaml, trailing spaces and invisible characters tag along. On Binance and KuCoin this sometimes gets caught before a request is sent — on Bybit, the malformed key hits the server and comes back as 33004.
Before touching any code, go through this checklist methodically. Most developers fix the issue at step two or three.
| Step | Check | How to Verify |
|---|---|---|
| 1 | Correct environment | Confirm base URL: api.bybit.com for live, testnet.bybit.com for test |
| 2 | Key exists | Log into Bybit > Account > API Management > verify key is listed and active |
| 3 | IP whitelist | Check allowed IPs on the key — add your current IP or remove restriction for testing |
| 4 | Key formatting | Print len(api_key) and compare with expected 18-char Bybit key length |
| 5 | Permissions | Confirm the key has the permission scope needed (Read / Trade / Withdraw) |
| 6 | Account type | Check if your account is Unified Trading or Classic — some endpoints differ |
import os
api_key = os.getenv("BYBIT_API_KEY", "")
api_secret = os.getenv("BYBIT_API_SECRET", "")
# Strip hidden whitespace that causes 33004
api_key = api_key.strip()
api_secret = api_secret.strip()
print(f"Key length: {len(api_key)} (expected: 18)")
print(f"Secret length: {len(api_secret)} (expected: 36)")
print(f"Key starts with: {api_key[:4]}...")
# Bybit Mainnet key format check
if len(api_key) != 18:
print("WARNING: API key length looks wrong — check for whitespace or truncation")
if len(api_secret) != 36:
print("WARNING: API secret length looks wrong")
Even when your key is correct, a malformed signature can sometimes produce error 33004 on certain Bybit endpoint versions. Bybit uses HMAC-SHA256 signing. The signature payload must be assembled in the exact order Bybit expects: timestamp + api_key + recv_window + query_string_or_body. Getting this order wrong is a silent killer — the key appears valid but the signed request doesn't match.
import hashlib
import hmac
import time
import requests
API_KEY = os.getenv("BYBIT_API_KEY").strip()
API_SECRET = os.getenv("BYBIT_API_SECRET").strip()
BASE_URL = "https://api.bybit.com" # use testnet.bybit.com for testing
def generate_signature(secret: str, payload: str) -> str:
return hmac.new(
secret.encode("utf-8"),
payload.encode("utf-8"),
hashlib.sha256
).hexdigest()
def get_wallet_balance(coin: str = "USDT") -> dict:
endpoint = "/v5/account/wallet-balance"
timestamp = str(int(time.time() * 1000))
recv_window = "5000"
params = f"accountType=UNIFIED&coin={coin}"
# Bybit V5 signature: timestamp + api_key + recv_window + params
sign_payload = timestamp + API_KEY + recv_window + params
signature = generate_signature(API_SECRET, sign_payload)
headers = {
"X-BAPI-API-KEY": API_KEY,
"X-BAPI-TIMESTAMP": timestamp,
"X-BAPI-SIGN": signature,
"X-BAPI-RECV-WINDOW": recv_window,
"Content-Type": "application/json"
}
response = requests.get(
f"{BASE_URL}{endpoint}",
headers=headers,
params={"accountType": "UNIFIED", "coin": coin}
)
data = response.json()
if data.get("retCode") == 33004:
raise PermissionError(
"Error 33004: API key not matched. Check IP whitelist, "
"key validity, and that you're using the correct environment (mainnet vs testnet)."
)
return data
# Test the connection
try:
result = get_wallet_balance("USDT")
print("Connection successful:", result)
except PermissionError as e:
print(e)
If you're running a bot in Node.js — common when building signal relay systems that pull from platforms like VoiceOfChain and push orders to Bybit — here's the proper authentication setup with error handling built in. This pattern also works cleanly when routing signals from Bybit to Gate.io or Bitget using a unified adapter.
const crypto = require('crypto');
const axios = require('axios');
const API_KEY = process.env.BYBIT_API_KEY?.trim();
const API_SECRET = process.env.BYBIT_API_SECRET?.trim();
const BASE_URL = 'https://api.bybit.com'; // swap for testnet if needed
function generateSignature(secret, payload) {
return crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
}
async function getBybitBalance(coin = 'USDT') {
const endpoint = '/v5/account/wallet-balance';
const timestamp = Date.now().toString();
const recvWindow = '5000';
const queryString = `accountType=UNIFIED&coin=${coin}`;
const signPayload = timestamp + API_KEY + recvWindow + queryString;
const signature = generateSignature(API_SECRET, signPayload);
try {
const response = await axios.get(`${BASE_URL}${endpoint}`, {
params: { accountType: 'UNIFIED', coin },
headers: {
'X-BAPI-API-KEY': API_KEY,
'X-BAPI-TIMESTAMP': timestamp,
'X-BAPI-SIGN': signature,
'X-BAPI-RECV-WINDOW': recvWindow,
'Content-Type': 'application/json'
}
});
const { retCode, retMsg, result } = response.data;
if (retCode === 33004) {
console.error(`[Bybit 33004] ${retMsg}`);
console.error('Checklist: IP whitelist, key environment (mainnet/testnet), key active status');
return null;
}
if (retCode !== 0) {
console.error(`[Bybit Error ${retCode}] ${retMsg}`);
return null;
}
return result;
} catch (err) {
console.error('HTTP request failed:', err.message);
return null;
}
}
// Run
getBybitBalance('USDT').then(balance => {
if (balance) console.log('Balance:', JSON.stringify(balance, null, 2));
});
Once you've fixed the immediate issue, you want to make sure it doesn't silently kill your bot at 3am during a volatile market move. Error 33004 in production is particularly nasty because it can look like the bot is running — no crash — but every order silently fails. Here's how to harden your setup.
Pro tip: Bybit API keys can be restricted to specific permissions (Read, Trade, Withdraw, Transfer). A read-only key hitting a POST /v5/order/create endpoint will sometimes return 33004 instead of a permission error. Always check your key's permission scope matches your bot's required actions.
Bybit API error 33004 is fixable in minutes once you know where to look. The root cause is almost always one of three things: wrong environment (testnet vs mainnet), IP not whitelisted, or a key that was deleted or never copied correctly. Run the key length check, verify the key is active in your dashboard, confirm your IP is authorized, and test against the correct base URL.
For algo traders using real-time signals from platforms like VoiceOfChain to drive automated orders on Bybit, gate your order execution behind a startup API health check. A 33004 that surfaces at launch is a minor inconvenience. One that surfaces mid-trade — silently dropping your stop-loss order — is expensive. Build the validation in once, and your bot handles it cleanly from that point forward.