Bybit API Secret: Complete Setup Guide for Traders
Learn to generate, configure, and secure your Bybit API key and secret for automated trading. Covers permissions, mobile app setup, testnet, and error fixes.
Learn to generate, configure, and secure your Bybit API key and secret for automated trading. Covers permissions, mobile app setup, testnet, and error fixes.
Your Bybit API key and secret are the gateway to automated crypto trading. They let your bot place orders, fetch balances, and stream market data without you clicking a single button. Get them set up correctly and you've solved the hardest part of building a trading system — everything after that is just logic.
Bybit uses a standard two-part credential system, the same model adopted by Binance, OKX, KuCoin, and most serious exchanges. The API key is a public identifier — it tells Bybit which account is making the request. The API secret is the private component used to compute a cryptographic signature that gets attached to every request. Bybit's servers verify that signature before executing anything, which means intercepted requests can't be replayed without your secret.
The API secret is shown only once when you create the key. Bybit does not store it after that. If you lose it, delete the key and generate a fresh one — there is no 'show secret again' option.
Creating a Bybit API key takes about two minutes on both desktop and the Bybit API key mobile app. The process is identical either way — both route through the same API Management interface. Keys created on mobile work on the mainnet API just like desktop-generated keys, there is no functional difference between them.
IP whitelisting is one of the most underused security features on Bybit and other exchanges. If your trading bot runs on a server with a fixed IP address — which it should in production — whitelist that IP during key creation. Even if your API secret leaks, requests from any non-whitelisted IP get rejected outright. Platforms like Bybit and OKX both support this at the key level, and it takes ten seconds to configure.
Bybit API key permissions are granular — you choose exactly what each key can do. Follow the principle of least privilege and only grant what your bot actually needs. A read-only key can't place orders, and a trading key without withdrawal access can't drain your account if it's ever compromised. Misconfigured permissions are also one of the leading causes of 'bybit api key is invalid' or permission-denied errors when you first start testing.
| Permission | What It Allows | Typical Use Case |
|---|---|---|
| Read Only | Balances, positions, order history, market data | Portfolio trackers, signal verification, VoiceOfChain integration |
| Trade | Place, modify, cancel spot orders | Spot trading bots |
| Derivatives Trade | Perpetual and options order management | Futures bots, USDT perpetuals |
| Wallet Transfer | Move funds between sub-accounts | Multi-account management systems |
| Withdrawal | Send funds to external addresses | Avoid on any automated key — keep this manual only |
Never enable the Withdrawal permission on a key used by a bot. If that key is compromised, an attacker can empty your account immediately. Keep withdrawal-capable keys completely separate from your automation stack.
Bybit v5 API uses HMAC-SHA256 signatures. Each request is signed using a string that concatenates your timestamp, API key, receive window, and the request parameters. For GET requests, parameters become the query string component of that string. For POST requests, you sign the raw JSON body. Here is a complete authentication setup you can use as a foundation:
import hmac
import hashlib
import time
import requests
API_KEY = 'your_bybit_api_key'
API_SECRET = 'your_bybit_api_secret'
BASE_URL = 'https://api.bybit.com'
def make_signature(params_str, timestamp, recv_window='5000'):
sign_str = f'{timestamp}{API_KEY}{recv_window}{params_str}'
return hmac.new(
API_SECRET.encode('utf-8'),
sign_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
def build_headers(params_str, timestamp, recv_window='5000'):
return {
'X-BAPI-API-KEY': API_KEY,
'X-BAPI-TIMESTAMP': timestamp,
'X-BAPI-SIGN': make_signature(params_str, timestamp, recv_window),
'X-BAPI-RECV-WINDOW': recv_window,
'Content-Type': 'application/json'
}
With auth in place, fetching your wallet balance is a straightforward GET request. Note how the params dictionary is serialized into a query string for the signature — this trips up many beginners who sign the dict object instead of the serialized string and get a persistent 'invalid signature' error in return.
def get_wallet_balance():
timestamp = str(int(time.time() * 1000))
params = {'accountType': 'UNIFIED'}
# Serialize params into query string for GET signature
params_str = '&'.join(f'{k}={v}' for k, v in params.items())
headers = build_headers(params_str, timestamp)
response = requests.get(
f'{BASE_URL}/v5/account/wallet-balance',
headers=headers,
params=params
)
data = response.json()
if data['retCode'] == 0:
coins = data['result']['list'][0]['coin']
for coin in coins:
name = coin['coin']
balance = coin['walletBalance']
if float(balance) > 0:
print(f'{name}: {balance}')
else:
msg = data['retMsg']
print(f'API error: {msg}')
get_wallet_balance()
Most Bybit API errors follow predictable patterns once you've seen them a few times. The 'bybit api key is invalid' error (retCode 10003) almost always comes down to a copy-paste issue — a trailing space or missing character in the key string. The bybit api key expired error (retCode 33004) happens when a key hits its validity window or was set with a short expiry. Here is a centralized error handler that translates Bybit's return codes into actionable messages:
ERROR_CODES = {
10003: 'Invalid API key — re-copy from Bybit API Management, check for whitespace',
10004: 'Invalid signature — verify API_SECRET is exact and params are serialized correctly',
10010: 'Insufficient permissions — enable required scope in API key settings',
33004: 'API key expired — delete and regenerate in Account > API Management',
10006: 'Rate limit hit — add backoff delay between requests',
}
def safe_request(response_data):
ret_code = response_data.get('retCode', -1)
if ret_code == 0:
return response_data['result']
ret_msg = response_data.get('retMsg', 'no message')
default_msg = f'Unknown error {ret_code}: {ret_msg}'
error_msg = ERROR_CODES.get(ret_code, default_msg)
raise RuntimeError(f'Bybit API [{ret_code}]: {error_msg}')
# Example: make a raw call then pass it through the error handler
timestamp = str(int(time.time() * 1000))
params = {'accountType': 'UNIFIED'}
params_str = '&'.join(f'{k}={v}' for k, v in params.items())
headers = build_headers(params_str, timestamp)
raw = requests.get(
f'{BASE_URL}/v5/account/wallet-balance',
headers=headers,
params=params
).json()
try:
result = safe_request(raw)
print(result)
except RuntimeError as e:
print(e)
The Bybit API key testnet is a completely separate environment from mainnet. Testnet keys won't work on the live API, and mainnet keys won't work on testnet — they're isolated by design. The bybit api key free testnet account comes with paper trading funds so you can validate your signing logic and order flow without risking real money. Binance and Gate.io have similar sandbox environments if you're building exchange-agnostic systems and want to verify your code works consistently across platforms.
# Bybit testnet — register at testnet.bybit.com for free API keys and paper funds
TESTNET_URL = 'https://api-testnet.bybit.com'
TESTNET_API_KEY = 'your_testnet_api_key'
TESTNET_API_SECRET = 'your_testnet_api_secret'
def get_testnet_balance():
timestamp = str(int(time.time() * 1000))
params = {'accountType': 'UNIFIED'}
params_str = '&'.join(f'{k}={v}' for k, v in params.items())
sign_str = f'{timestamp}{TESTNET_API_KEY}5000{params_str}'
signature = hmac.new(
TESTNET_API_SECRET.encode('utf-8'),
sign_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
headers = {
'X-BAPI-API-KEY': TESTNET_API_KEY,
'X-BAPI-TIMESTAMP': timestamp,
'X-BAPI-SIGN': signature,
'X-BAPI-RECV-WINDOW': '5000'
}
resp = requests.get(
f'{TESTNET_URL}/v5/account/wallet-balance',
headers=headers,
params=params
)
return resp.json()
print(get_testnet_balance())
If you're using a real-time signal platform like VoiceOfChain to drive your trading decisions, you'll typically want two keys working in parallel: a read-only key to verify current positions and portfolio state, and a trade-enabled key for your execution bot. Keep them as separate keys in Bybit's API Management — it makes permission auditing clean and limits exposure if one credential is ever compromised. VoiceOfChain generates signals from live market data independently, so your API keys stay entirely on your own infrastructure.
Setting up your Bybit API key and secret correctly the first time prevents most of the headaches traders run into later. Generate keys with only the permissions your use case actually requires, whitelist your server's IP address, store credentials in environment variables rather than hardcoded strings, and run everything against the testnet before going live with real funds. The HMAC signing logic is the same across every endpoint — get that foundation solid and the rest of your trading automation becomes straightforward.