Route by category
Keep spot, linear, inverse, and option category values explicit in shared workflow code.
Open sectionBuild Bybit API integrations without writing your own request signing, category endpoint routing, WebSocket authentication, reconnect loops, resubscribe logic, or WebSocket API response matching.
import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5();
async function main() {
const serverTime = await client.getServerTime();
const instruments = await client.getInstrumentsInfo({
category: 'linear',
symbol: 'BTCUSDT',
});
const ticker = await client.getTickers({
category: 'linear',
symbol: 'BTCUSDT',
});
const orderBook = await client.getOrderbook({
category: 'linear',
symbol: 'BTCUSDT',
limit: 50,
});
const candles = await client.getKline({
category: 'linear',
symbol: 'BTCUSDT',
interval: '1',
limit: 5,
});
console.log({
serverTime,
instrument: instruments.result.list[0]?.symbol,
ticker: ticker.result.list[0],
orderBook,
candles,
});
}
main().catch(console.error);API surface map
The Bybit API includes REST API calls, public streams, private streams, and WebSocket API command paths. Use the SDK surface that matches each workflow.
Your app
Bot, dashboard, worker, tool
Any Node.js or JavaScript-compatible service that needs Bybit market data, account state, order management, or reconciliation.
npm package
npm install bybit-apiRestClientV5
Current REST API client
WebsocketClient
Public and private streams
WebsocketAPIClient
Awaitable WebSocket API commands
SpotClientV3
Legacy compatibility only
Bybit API
REST API calls across market, trade, position, account, and asset APIs
Public streams by product category
Private account streams
WebSocket API commands
API Categories
The same SDK method can cover more than one product family. Make the category value a first-class part of your request builder, logs, tests, and reconciliation keys.
category: 'spot'Spot market data and spot order workflows where the endpoint supports category-based routing.
category: 'linear'USDT and USDC linear contracts, including common perpetual and futures workflows.
category: 'inverse'Inverse perpetual and inverse futures contracts with inverse symbols and account state.
category: 'option'Options market data, orders, positions, greeks, and WebSocket topics where supported.
What this tutorial covers
Start with a working public request, then build through credentials, category-based routing, private streams, demo and testnet behavior, WebSocket API commands, reconnect recovery, and production rollout checks.
Use one current REST API client while keeping category, symbol, account mode, and product behavior explicit.
Separate live, testnet, demo trading, HMAC, RSA, and regional routing decisions before trading.
Subscribe to market and account topics with reconnect-aware state handling and REST API reconciliation.
Use promise-wrapped WebSocket API commands, then confirm state changes through streams or the REST API.
Start building
Run one focused example first, then add the surrounding account-state and recovery workflow once the client, credentials, and category are correct.
import { RestClientV5 } from 'bybit-api'; const client = new RestClientV5(); async function main() { const serverTime = await client.getServerTime(); const instruments = await client.getInstrumentsInfo({ category: 'linear', symbol: 'BTCUSDT', }); const ticker = await client.getTickers({ category: 'linear', symbol: 'BTCUSDT', }); const orderBook = await client.getOrderbook({ category: 'linear', symbol: 'BTCUSDT', limit: 50, }); const candles = await client.getKline({ category: 'linear', symbol: 'BTCUSDT', interval: '1', limit: 5, }); console.log({ serverTime, instrument: instruments.result.list[0]?.symbol, ticker: ticker.result.list[0], orderBook, candles, });} main().catch(console.error);import { WebsocketClient, isWsOrderbookEventV5 } from 'bybit-api'; const ws = new WebsocketClient(); ws.on('open', (data) => console.log('connected', data.wsKey, data.wsUrl));ws.on('response', (data) => console.log('response', JSON.stringify(data)));ws.on('update', (data) => { if (isWsOrderbookEventV5(data)) { console.log('orderbook update', data.data.s, data.type); return; } console.log('stream update', JSON.stringify(data));});ws.on('reconnect', (data) => console.log('reconnecting', data.wsKey));ws.on('reconnected', (data) => console.log('reconnected', data.wsKey));ws.on('exception', console.error); ws.subscribeV5(['orderbook.50.BTCUSDT', 'tickers.BTCUSDT', 'publicTrade.BTCUSDT'], 'linear');import { WebsocketClient } from 'bybit-api'; const ws = new WebsocketClient({ key: process.env.BYBIT_API_KEY!, secret: process.env.BYBIT_API_SECRET!,}); ws.on('authenticated', (data) => { console.log('authenticated', data.wsKey);}); ws.on('update', (data) => { console.log('account event', JSON.stringify(data));}); ws.on('reconnect', ({ wsKey }) => { console.log('reconnecting', wsKey);}); ws.on('reconnected', ({ wsKey }) => { console.log('reconnected', wsKey); // Fetch wallet, positions, open orders, or recent executions here if needed.}); ws.on('exception', console.error); ws.subscribeV5(['order', 'execution', 'position', 'wallet'], 'linear');import { RestClientV5 } from 'bybit-api'; const client = new RestClientV5({ key: process.env.BYBIT_API_KEY!, secret: process.env.BYBIT_API_SECRET!, demoTrading: true, throwExceptions: true,}); async function placeDemoOrder() { await client.requestDemoTradingFunds(); const orderRequest = { category: 'linear', symbol: 'BTCUSDT', side: 'Buy', orderType: 'Limit', qty: '0.001', price: '10000', timeInForce: 'PostOnly', orderLinkId: `demo-${Date.now()}`, } as const; const result = await client.submitOrder(orderRequest); console.log(result);} placeDemoOrder().catch(console.error);import { WebsocketAPIClient } from 'bybit-api'; const wsApi = new WebsocketAPIClient({ key: process.env.BYBIT_API_KEY!, secret: process.env.BYBIT_API_SECRET!, // Use testnet API keys with this option. testnet: true,}); const wsClient = wsApi.getWSClient(); wsClient.on('open', (data) => console.log('ws api open', data.wsKey));wsClient.on('authenticated', (data) => { console.log('ws api authenticated', data.wsKey);});wsClient.on('exception', console.error); async function main() { await wsClient.connectWSAPI(); if (process.env.BYBIT_PLACE_ORDER !== 'true') { console.log('Set BYBIT_PLACE_ORDER=true when you are ready to submit.'); return; } const result = await wsApi.submitNewOrder({ category: 'linear', symbol: 'BTCUSDT', side: 'Buy', orderType: 'Limit', qty: '0.001', price: '10000', timeInForce: 'PostOnly', orderLinkId: `wsapi-${Date.now()}`, }); console.log(result);} main().catch(console.error);Workflow diagrams
A REST API response, a stream update, and a WebSocket API acknowledgement each tell you something different. The diagrams below show where routing, subscription state, reconnect recovery, and command acknowledgement fit.
Most current Bybit workflows start with RestClientV5 and a product category rather than a separate product client.
Choose categoryYour codeBuild typed requestYour codeCall RestClientV5 methodYour codeSign and route requestSDK handlesReturn Bybit responseBybitNormalize resultYour codeAfter a private connection drops, stream resubscription is only part of recovery. Rebuild account state before risky actions resume.
on(reconnect)EventPause order logicYour codeReconnect and authenticateSDK handlesResubscribe cached topicsSDK handleson(reconnected)EventBackfill wallet, positions, ordersYour codeResume from known stateYour codeThe WebSocket API acknowledgement is not a fill. Treat it as command acceptance, then watch order and execution state.
connectWSAPI()Your codeAuthenticate v5PrivateTradeSDK handlesawait submitNewOrder()Your codeSend signed order.createSDK handlesReceive acknowledgementEventTrack order/execution streamYour codeReconcile with REST API when neededYour codeProduction rollout
The important work starts after the first request succeeds: credentials, account mode, category routing, reconnect behavior, final order state, and exchange-region availability all need to be predictable and observable.
Keep live, testnet, and demo trading credentials separate.
Make category, symbol, account type, and position mode explicit in order code.
Use private order and execution streams to confirm final order state.
Backfill wallet, positions, open orders, and executions after private stream reconnects.
Prefer throwExceptions: true for RestClientV5 order workflows; if disabled, treat retCode === 0 as REST business acceptance.
Include triggerDirection for triggered stop-loss orders and normalize hydrated defaults before deciding keep, amend, cancel_place, cancel, or place.
Check regional API routing and exchange-side availability before production rollout.
Choose your path
Keep spot, linear, inverse, and option category values explicit in shared workflow code.
Open sectionUse subscribeV5 with the right category for public topics such as order books, trades, tickers, and klines.
Open sectionSubscribe to order, execution, position, and wallet topics, then reconcile with the REST API after reconnects.
Open sectionUse the REST API for broad coverage and demo trading, or WebsocketAPIClient for awaitable WebSocket API commands.
Open sectionThis tutorial focuses on the Bybit API pieces developers usually need first: REST API calls, category-specific public streams, private account topics, WebSocket API commands, demo trading, testnet, regional routing, reconnects, and rollout checks.
This tutorial walks through a practical Bybit REST API, WebSocket stream, and WebSocket API integration using bybit-api, the Bybit JavaScript and TypeScript SDK by Siebly.io.
The SDK handles the repetitive parts: HMAC and RSA request signing, Bybit API endpoint routing, testnet and demo trading differences, WebSocket authentication, heartbeats, reconnects, resubscribe behavior, WebSocket API request/response matching, and TypeScript request and response definitions. The sections below move from installation and client choice to public calls, private auth, trading flows, WebSocket API commands, environments, and production checks.
Key links
bybit-apitiagosiebler/bybit-apiThe Bybit API is unified, but a real integration still has several moving parts:
category parameter such as spot, linear, inverse, or option.v5PrivateTrade.The SDK gives you the main surfaces needed for those workflows:
RestClientV5 for Bybit REST API calls.WebsocketClient for public and private streams.WebsocketAPIClient for promise-driven WebSocket API commands.SpotClientV3 for the remaining legacy Spot V3 endpoint. New integrations should use the current Bybit API surface. The V3 client might be removed at any time.The method names stay close to Bybit's endpoint names, while the SDK handles base URLs, request signatures, request routing, headers, WebSocket lifecycle, authentication, topic tracking, reconnects, and typed request shapes. It also lets you use the WebSocket API in a request/response style: send a command and await the matching response, similar to a REST API.
If you do not have Node.js installed yet, install it first. The SDK is published to both GitHub and npm.
Install the SDK with npm:
npm install bybit-apiOr use another npm-compatible package manager:
pnpm install bybit-api
yarn add bybit-apiCreate API keys from the relevant Bybit page:
Always use the minimum permissions needed for your scenario. Trading does not require withdrawal permissions. Analytics does not require trading permissions. Always use strict IP whitelisting for API keys whenever your deployment environment allows it.
The main auth and environment rules are:
key and secret.key and secret.key and secret.secret.Typical environment variables:
export BYBIT_API_KEY='your-api-key'
export BYBIT_API_SECRET='your-api-secret-or-rsa-private-key'Create a private REST API client:
import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
});If you are only testing public endpoints, you do not need keys:
import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5();For RSA setup details, see examples/Auth/RSA-sign.md.
For new Bybit integrations, start with the current API. Older Bybit SDK surfaces were split into many product-specific clients; this SDK now centers the current API around one REST API client plus WebSocket clients.
| Use case | SDK surface | Common usage |
|---|---|---|
| REST API | RestClientV5 | Public market data, account reads, order management, positions, wallet, asset transfers, user APIs, Earn, broker, P2P, RFQ, and other endpoint groups |
| Public and private streams | WebsocketClient | Live order books, trades, klines, tickers, liquidations, private orders, executions, positions, wallet, and greeks |
| WebSocket API commands | WebsocketAPIClient | Awaitable order create, amend, cancel, and batch order commands over Bybit's WebSocket API |
| Raw WebSocket API commands | WebsocketClient.sendWSAPIRequest(...) | Lower-level WebSocket API usage where you want to send an operation directly |
The category parameter matters:
| Category | Meaning |
|---|---|
spot | Spot market and Spot orders |
linear | USDT and USDC linear contracts |
inverse | Inverse perpetual and inverse futures contracts |
option | Options |
As a rule of thumb:
RestClientV5 for current Bybit REST API endpoints.WebsocketClient.subscribeV5(...) when you want streaming data.WebsocketAPIClient when you want to send commands over WebSocket and await the response.docs/endpointFunctionList.md when you already know the Bybit endpoint path and need the matching SDK method.For a complete method map, see docs/endpointFunctionList.md.
Bybit exposes several different integration flows. Keep them separate in your architecture:
| Flow | SDK surface | Best for | What the SDK handles |
|---|---|---|---|
| REST API | RestClientV5 | Request/response calls, broad endpoint coverage, public reads, private account reads, order submission, reconciliation | Base URLs, timestamps, HMAC/RSA signing, headers, response parsing, optional rate-limit parsing |
| Public WebSocket streams | WebsocketClient.subscribeV5(...) | Real-time market data such as order books, trades, klines, tickers, and liquidations | Endpoint routing by category, subscribe requests, heartbeats, reconnects, resubscribe |
| Private WebSocket streams | WebsocketClient.subscribeV5(...) with keys | Account events such as orders, executions, wallet, positions, and greeks | Authentication, private endpoint routing, reconnects, resubscribe |
| WebSocket API commands | WebsocketAPIClient or sendWSAPIRequest(...) | Order create, amend, cancel, and batch order operations over a persistent WebSocket connection | Connection setup, authentication, request IDs, signing, promise resolution, response/error correlation |
Use the REST API when you want maximum endpoint coverage or a simple one-off request. Use WebSocket streams when you need realtime lower-latency updates. Use the WebSocket API when you want a command path over an already-open WebSocket connection.
If you only want the fastest path to a working integration, start here.
import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5();
async function main() {
const serverTime = await client.getServerTime();
const instruments = await client.getInstrumentsInfo({
category: 'linear',
symbol: 'BTCUSDT',
});
const ticker = await client.getTickers({
category: 'linear',
symbol: 'BTCUSDT',
});
const orderBook = await client.getOrderbook({
category: 'linear',
symbol: 'BTCUSDT',
limit: 50,
});
const candles = await client.getKline({
category: 'linear',
symbol: 'BTCUSDT',
interval: '1',
limit: 5,
});
console.log({
serverTime,
instrument: instruments.result.list[0]?.symbol,
ticker: ticker.result.list[0],
orderBook,
candles,
});
}
main().catch(console.error);That confirms public REST API access is wired correctly.
See also: public REST API example
import { WebsocketClient, isWsOrderbookEventV5 } from 'bybit-api';
const ws = new WebsocketClient();
ws.on('open', (data) => console.log('connected', data.wsKey, data.wsUrl));
ws.on('response', (data) => console.log('response', JSON.stringify(data)));
ws.on('update', (data) => {
if (isWsOrderbookEventV5(data)) {
console.log('orderbook update', data.data.s, data.type);
return;
}
console.log('stream update', JSON.stringify(data));
});
ws.on('reconnect', (data) => console.log('reconnecting', data.wsKey));
ws.on('reconnected', (data) => console.log('reconnected', data.wsKey));
ws.on('exception', console.error);
ws.subscribeV5(['orderbook.50.BTCUSDT', 'tickers.BTCUSDT', 'publicTrade.BTCUSDT'], 'linear');For public streams, pass the category so the SDK can route the topic to the right public endpoint.
See also: public WebSocket example
import { WebsocketClient } from 'bybit-api';
const ws = new WebsocketClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
});
ws.on('authenticated', (data) => {
console.log('authenticated', data.wsKey);
});
ws.on('update', (data) => {
console.log('account event', JSON.stringify(data));
});
ws.on('reconnect', ({ wsKey }) => {
console.log('reconnecting', wsKey);
});
ws.on('reconnected', ({ wsKey }) => {
console.log('reconnected', wsKey);
// Fetch wallet, positions, open orders, or recent executions here if needed.
});
ws.on('exception', console.error);
ws.subscribeV5(['order', 'execution', 'position', 'wallet'], 'linear');Private topics are routed to the private endpoint. The category argument is still required by subscribeV5(...), but it is only used for public-topic routing.
See also: private WebSocket example
Use demo trading before placing live orders. Demo trading uses a separate Bybit demo account and separate API keys.
import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
demoTrading: true,
throwExceptions: true,
});
async function placeDemoOrder() {
await client.requestDemoTradingFunds();
const orderRequest = {
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
orderLinkId: `demo-${Date.now()}`,
} as const;
const result = await client.submitOrder(orderRequest);
console.log(result);
}
placeDemoOrder().catch(console.error);This submits to Bybit demo trading because demoTrading: true is set. Do not remove that option or switch to live keys until you are ready to place real orders.
For order workflows, prefer throwExceptions: true so non-zero Bybit business responses throw and can be handled in one structured catch path. If you intentionally set throwExceptions: false, a resolved REST promise can still be an exchange business rejection. Treat retCode === 0 as acceptance and any non-zero retCode as a rejected or unknown submission state.
See also: Demo trading example
The WebSocket API lets you submit order commands over a persistent WebSocket connection and await responses. Bybit supports WebSocket API order commands in live and testnet environments, but not demo trading.
import { WebsocketAPIClient } from 'bybit-api';
const wsApi = new WebsocketAPIClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
// Use testnet API keys with this option.
testnet: true,
});
const wsClient = wsApi.getWSClient();
wsClient.on('open', (data) => console.log('ws api open', data.wsKey));
wsClient.on('authenticated', (data) => {
console.log('ws api authenticated', data.wsKey);
});
wsClient.on('exception', console.error);
async function main() {
await wsClient.connectWSAPI();
if (process.env.BYBIT_PLACE_ORDER !== 'true') {
console.log('Set BYBIT_PLACE_ORDER=true when you are ready to submit.');
return;
}
const result = await wsApi.submitNewOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
orderLinkId: `wsapi-${Date.now()}`,
});
console.log(result);
}
main().catch(console.error);The WebSocket API acknowledgement tells you the command was accepted. Track the final order state through the private order and execution streams, or reconcile with the REST API.
See also: WebSocket API client example
Most Bybit integrations start with RestClientV5. It covers the current REST API surface and uses Bybit's category parameter to distinguish product groups where the endpoint requires it.
RestClientV5import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5();Public market calls do not require keys.
RestClientV5import { RestClientV5 } from 'bybit-api';
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
recv_window: 5000,
parseAPIRateLimits: true,
});Private REST API methods are signed automatically. You do not need to add timestamps, signatures, X-BAPI-API-KEY or X-BAPI-SIGN headers yourself.
const serverTime = await client.getServerTime();
const instruments = await client.getInstrumentsInfo({
category: 'linear',
symbol: 'BTCUSDT',
});
const orderBook = await client.getOrderbook({
category: 'linear',
symbol: 'BTCUSDT',
limit: 50,
});
const ticker = await client.getTickers({
category: 'linear',
symbol: 'BTCUSDT',
});
const candles = await client.getKline({
category: 'linear',
symbol: 'BTCUSDT',
interval: '5',
limit: 10,
});
const recentTrades = await client.getPublicTradingHistory({
category: 'linear',
symbol: 'BTCUSDT',
limit: 10,
});
const funding = await client.getFundingRateHistory({
category: 'linear',
symbol: 'BTCUSDT',
limit: 10,
});
const openInterest = await client.getOpenInterest({
category: 'linear',
symbol: 'BTCUSDT',
intervalTime: '15min',
});For Spot market data, use category: 'spot'. For inverse contracts, use category: 'inverse'. For options, use category: 'option' where the endpoint supports it.
const accountInfo = await client.getAccountInfo();
const systemStatus = await client.getSystemStatus();
const wallet = await client.getWalletBalance({
accountType: 'UNIFIED',
});
const positions = await client.getPositionInfo({
category: 'linear',
symbol: 'BTCUSDT',
});
const openOrders = await client.getActiveOrders({
category: 'linear',
symbol: 'BTCUSDT',
});
const orderHistory = await client.getHistoricOrders({
category: 'linear',
symbol: 'BTCUSDT',
limit: 20,
});
const executions = await client.getExecutionList({
category: 'linear',
symbol: 'BTCUSDT',
limit: 20,
});
const feeRate = await client.getFeeRate({
category: 'linear',
symbol: 'BTCUSDT',
});
const transactions = await client.getTransactionLog({
accountType: 'UNIFIED',
});See also:
Market order:
await client.submitOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Market',
qty: '0.001',
orderLinkId: `market-${Date.now()}`,
});Limit order:
await client.submitOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'GTC',
orderLinkId: `limit-${Date.now()}`,
});Post-only limit order:
await client.submitOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
orderLinkId: `postonly-${Date.now()}`,
});Amend an order:
await client.amendOrder({
category: 'linear',
symbol: 'BTCUSDT',
orderId: 'existing-order-id',
price: '11000',
qty: '0.002',
});Cancel an order:
await client.cancelOrder({
category: 'linear',
symbol: 'BTCUSDT',
orderId: 'existing-order-id',
});Cancel open orders for a category and symbol:
await client.cancelAllOrders({
category: 'linear',
symbol: 'BTCUSDT',
});Batch submit orders:
await client.batchSubmitOrders('linear', [
{
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
orderLinkId: `batch-a-${Date.now()}`,
},
{
symbol: 'ETHUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.01',
price: '1000',
timeInForce: 'PostOnly',
orderLinkId: `batch-b-${Date.now()}`,
},
]);Pre-check an order where your account mode and product support it:
await client.preCheckOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
});Bybit order acknowledgements are asynchronous. After submitting, use private WebSocket order and execution events, or REST API reads such as getActiveOrders(...), getHistoricOrders(...), and getExecutionList(...), to track what actually happened.
const positions = await client.getPositionInfo({
category: 'linear',
symbol: 'BTCUSDT',
});
await client.setLeverage({
category: 'linear',
symbol: 'BTCUSDT',
buyLeverage: '3',
sellLeverage: '3',
});
await client.switchPositionMode({
category: 'linear',
coin: 'USDT',
mode: 3,
});
await client.setTradingStop({
category: 'linear',
symbol: 'BTCUSDT',
positionIdx: 0,
stopLoss: '25000',
slTriggerBy: 'LastPrice',
});
const closedPnl = await client.getClosedPnL({
category: 'linear',
symbol: 'BTCUSDT',
});Position mode, margin mode, leverage, risk limit, and TP/SL behavior are account-sensitive. Read your current state first, then apply changes intentionally.
const wallet = await client.getWalletBalance({
accountType: 'UNIFIED',
});
const allCoins = await client.getAllCoinsBalance({
accountType: 'UNIFIED',
coin: 'USDT,BTC',
});
const coinBalance = await client.getCoinBalance({
accountType: 'UNIFIED',
coin: 'USDT',
});
const transferableCoins = await client.getTransferableCoinList('UNIFIED', 'FUND');
const deposits = await client.getDepositRecords({
coin: 'USDT',
});
const withdrawals = await client.getWithdrawalRecords({
coin: 'USDT',
});Withdrawal and transfer permissions are high risk. Keep those on separate keys where possible, and do not grant withdrawal permissions to trading services that do not need them.
RestClientV5 also includes many specialized parts of the Bybit API. The most useful way to find the exact method is usually the endpoint map, but the groups below show the shape of the surface:
| Group | Example SDK methods |
|---|---|
| Spread trading | getSpreadInstrumentsInfo(...), submitSpreadOrder(...), cancelAllSpreadOrders(...) |
| Spot margin | toggleSpotMarginTrade(...), setSpotMarginLeverageV2(...), getSpotMarginLoanAccountInfo(...) |
| Crypto loans | borrowCryptoLoan(...), repayCryptoLoan(...), getUnpaidLoanOrders(...) |
| Earn | getEarnProduct(...), submitStakeRedeem(...), getEarnPosition(...) |
| Broker and affiliate | getBrokerRateLimitCap(...), setBrokerRateLimit(...), getAffiliateUserList(...) |
| User and sub-account | getSubUIDList(...), createSubMember(...), createSubUIDAPIKey(...) |
| Convert | requestConvertQuote(...), confirmConvertQuote(...), getConvertHistory(...) |
| P2P | getP2POrders(...), sendP2POrderMessage(...), uploadP2PChatFile(...) |
| RFQ | createRFQ(...), createRFQQuote(...), executeRFQQuote(...) |
If an endpoint exists in Bybit's API docs, search for the endpoint path or method group in docs/endpointFunctionList.md.
Use WebsocketClient when you want event-driven updates instead of REST API polling. The same client handles public streams, private account streams, and raw WebSocket API commands.
Typical setup is: create a client, attach event handlers, provide keys if private topics are needed, and subscribe to topics. The SDK opens the correct endpoint, authenticates when needed, sends subscribe requests, tracks topics, monitors heartbeats, reconnects dropped sockets, and resubscribes cached topics after reconnect.
WebsocketClient events| Event | Meaning |
|---|---|
open | Connection established |
update | Streaming topic data received |
response | Subscribe, unsubscribe, auth, or WebSocket API acknowledgement |
reconnect | Connection dropped and the SDK is replacing it |
reconnected | Replacement connection opened and cached subscriptions can resume |
close | Socket closed |
authenticated | Private authentication succeeded |
exception | Errors and unexpected conditions |
Use exception, not the deprecated error event.
WS_KEY_MAPWS_KEY_MAP tells the SDK which Bybit WebSocket endpoint family a connection belongs to:
| Key | Use |
|---|---|
v5SpotPublic | Spot public market data |
v5LinearPublic | Linear public market data |
v5InversePublic | Inverse public market data |
v5OptionPublic | Options public market data |
v5Private | Private account streams |
v5PrivateTrade | WebSocket API commands |
You normally do not need to pass these keys when subscribing to ordinary topics. subscribeV5(...) derives the correct connection from the topic and category. They are still useful for diagnostics, explicit connection calls, inspecting the internal topic store, and lower-level WebSocket API usage.
import { WebsocketClient } from 'bybit-api';
const ws = new WebsocketClient();
ws.on('update', (data) => console.log('public update', JSON.stringify(data)));
ws.on('exception', console.error);
ws.subscribeV5(
['orderbook.50.BTCUSDT', 'publicTrade.BTCUSDT', 'tickers.BTCUSDT', 'kline.5.BTCUSDT'],
'linear',
);For Spot:
ws.subscribeV5(['orderbook.50.BTCUSDT', 'tickers.BTCUSDT'], 'spot');For inverse:
ws.subscribeV5(['orderbook.50.BTCUSD', 'tickers.BTCUSD'], 'inverse');For options:
ws.subscribeV5('publicTrade.BTC', 'option');See also:
import { WebsocketClient } from 'bybit-api';
const ws = new WebsocketClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
});
ws.on('authenticated', (data) => console.log('authenticated', data.wsKey));
ws.on('update', (data) => console.log('private update', JSON.stringify(data)));
ws.on('exception', console.error);
ws.subscribeV5(['order', 'execution', 'position', 'wallet', 'greeks'], 'linear');Private topics currently share the private endpoint. The category parameter is ignored for private routing, but passing the category keeps your code consistent with subscribeV5(...).
ws.unsubscribeV5('kline.5.BTCUSDT', 'linear');
// Close all active WebSocket connections when shutting down a process.
ws.closeAll();unsubscribeV5(...) removes the topic from the SDK's subscription cache, so it will not be resubscribed after a reconnect.
The SDK groups topics by Bybit endpoint. If you want to isolate heavy streams, create separate client instances and split topics yourself:
const marketDataA = new WebsocketClient();
const marketDataB = new WebsocketClient();
marketDataA.subscribeV5(['orderbook.50.BTCUSDT'], 'linear');
marketDataB.subscribeV5(['kline.1.BTCUSDT', 'publicTrade.BTCUSDT'], 'linear');Do not subscribe to the same topic in multiple clients unless you intentionally want duplicate events.
Bybit's WebSocket API is a request/response API over a persistent WebSocket connection. In this SDK, you can use it in two ways:
WebsocketAPIClient for promise-driven methods such as submitNewOrder(...), amendOrder(...), and cancelOrder(...).WebsocketClient.sendWSAPIRequest(...) for lower-level operation calls such as order.create.The promise-driven client is the easiest place to start.
import { WebsocketAPIClient } from 'bybit-api';
const wsApi = new WebsocketAPIClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
// Use testnet API keys with this option.
testnet: true,
});
await wsApi.getWSClient().connectWSAPI();
const createResult = await wsApi.submitNewOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
orderLinkId: `wsapi-${Date.now()}`,
});
console.log(createResult);Amend and cancel:
await wsApi.amendOrder({
category: 'linear',
symbol: 'BTCUSDT',
orderId: createResult.data.orderId,
price: '11000',
});
await wsApi.cancelOrder({
category: 'linear',
symbol: 'BTCUSDT',
orderId: createResult.data.orderId,
});Batch commands:
await wsApi.batchSubmitOrders('linear', [
{
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
orderLinkId: `wsapi-batch-a-${Date.now()}`,
},
]);Raw command style:
import { WS_KEY_MAP, WebsocketClient } from 'bybit-api';
const ws = new WebsocketClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
testnet: true,
});
const result = await ws.sendWSAPIRequest(WS_KEY_MAP.v5PrivateTrade, 'order.create', {
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.001',
price: '10000',
timeInForce: 'PostOnly',
});
console.log(result);Bybit's WebSocket API response means the request was accepted for processing. Use private streams to confirm order status and executions.
See also:
Live is the default environment:
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
});Testnet uses separate credentials and separate API domains:
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
testnet: true,
});
const ws = new WebsocketClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
testnet: true,
});Use testnet for endpoint wiring, permissions, and safe integration checks. Do not treat testnet market behavior as representative of live market behavior.
Demo trading uses a mainnet demo account with simulated trading and separate demo keys.
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
demoTrading: true,
});Private demo WebSocket streams are also supported:
const ws = new WebsocketClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
demoTrading: true,
});
ws.subscribeV5(['order', 'execution', 'position', 'wallet'], 'linear');Do not combine testnet: true with demoTrading: true. Bybit's demo trading docs also note that WebSocket API commands are not supported in demo trading, so use REST API demo trading or private demo streams for demo workflows, and use testnet for WebSocket API command testing.
By default, REST API calls use the global Bybit domain. If your account belongs to a regional Bybit domain, set apiRegion:
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
apiRegion: 'EU',
});Supported API region values in this SDK:
defaultbytickNLTKKZHKGEUAEEUNew API regions will be supported as they become available. If you're looking for a region not yet supported, please get in touch.
You can also pass baseUrl for a custom REST API domain, or wsUrl for a custom WebSocket URL when needed.
See also: custom REST API URL example
Before a Bybit integration trades unattended, make these decisions explicit.
Move from read-only behavior to order placement one layer at a time:
Keep each layer observable before adding the next one.
Listen for reconnect and reconnected. A dropped WebSocket connection is a normal production condition, especially during volatility or scheduled exchange-side disconnects.
When the SDK emits reconnect, pause risky actions if your strategy depends on stream state. When it emits reconnected, query the REST API for the account state you may have missed:
ws.on('reconnected', async ({ wsKey }) => {
console.log('reconnected', wsKey);
const [wallet, positions, regularOpenOrders, stopOpenOrders] = await Promise.all([
client.getWalletBalance({ accountType: 'UNIFIED' }),
client.getPositionInfo({ category: 'linear', settleCoin: 'USDT' }),
client.getActiveOrders({
category: 'linear',
settleCoin: 'USDT',
openOnly: 0,
orderFilter: 'Order',
}),
client.getActiveOrders({
category: 'linear',
settleCoin: 'USDT',
openOnly: 0,
orderFilter: 'StopOrder',
}),
]);
const openOrders = [
...(regularOpenOrders.result?.list ?? []),
...(stopOpenOrders.result?.list ?? []),
];
// Reconcile these with your local state before resuming risky actions.
console.log({ wallet, positions, openOrders });
});For linear position managers that depend on conditional stop orders, do not assume the no-orderFilter active-order response covers both regular orders and StopOrder rows unless you have captured and verified that response shape for the account mode.
TypeScript validates the request fields you pass to the SDK. It does not prove that Bybit accepted a live request, private stream payload, or hydrated order state. For order-management services, prefer throwExceptions: true on RestClientV5 so non-zero Bybit retCode responses throw and flow through your normal SDK/API error classifier. Preserve retCode, retMsg, result, request context, and product/symbol scope from the thrown error where available.
If you intentionally use throwExceptions: false, business rejections resolve as response objects and must be classified manually:
type BybitResponse<T> = {
retCode: number;
retMsg: string;
result: T;
};
function classifyBybitResponse<T>(response: BybitResponse<T>) {
if (response.retCode === 0) return { ok: true as const, response };
return {
ok: false as const,
code: response.retCode,
message: response.retMsg,
response,
};
}For order-management services, stop later non-sent intents on any Bybit business rejection, log the sanitized error or response, block or surface deterministic request failures, and reconcile before submitting later exposure.
triggerDirection for conditional stopsFor Bybit V5 triggered stop-loss orders, verify the current request type and include triggerDirection. For a long position, the stop-loss exit usually sells when price falls to the trigger, so triggerDirection is 2. For a short position, the stop-loss exit usually buys when price rises to the trigger, so triggerDirection is 1.
await client.submitOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Sell',
orderType: 'Market',
qty: '0.001',
triggerPrice: '60000',
triggerDirection: 2,
triggerBy: 'MarkPrice',
orderFilter: 'StopOrder',
positionIdx: 0,
reduceOnly: true,
closeOnTrigger: true,
orderLinkId: `long-sl-${Date.now()}`,
});Hydrated active orders may include explicit defaults such as closeOnTrigger: false, reduceOnly: false, empty trigger fields, or stop-order defaults that were omitted from your original request. Compare desired and hydrated orders by order kind and normalize irrelevant defaults before deciding to cancel and replace an app-owned order.
Private requests are timestamp-sensitive. Keep your system clock synced first. If you still see receive-window errors, set the receive window intentionally.
REST API calls use recv_window:
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
recv_window: 5000,
});
await client.fetchLatencySummary();WebSockets use recvWindow:
const ws = new WebsocketClient({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
recvWindow: 5000,
});
ws.setTimeOffsetMs(-500);Use time offsets as a last resort. Fix host clock sync first. Refer to the timestamp guidance if you're having persistent issues with it: https://github.com/sieblyio/awesome-crypto-examples/wiki/Timestamp-for-this-request-is-outside-of-the-recvWindow
Live, testnet, and demo credentials are different. Keep them separate in your secrets manager and deployment configuration.
Use separate keys for separate risk levels:
Do not put private keys in frontend code. Use IP whitelisting.
The SDK can parse Bybit REST API rate-limit headers into responses when parseAPIRateLimits: true is enabled:
const client = new RestClientV5({
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
parseAPIRateLimits: true,
});
const response = await client.getPositionInfo({
category: 'linear',
symbol: 'BTCUSDT',
});
console.log(response.rateLimitApi);Bybit also returns rate-limit information in WebSocket API response headers. Use that data to reduce polling, back off safely, and prefer streaming updates where possible.
If you want SDK logs in your own monitoring stack, pass a logger:
import { DefaultLogger, WebsocketClient } from 'bybit-api';
const customLogger: typeof DefaultLogger = {
...DefaultLogger,
trace: () => {},
info: (...params) => console.info(new Date(), ...params),
error: (...params) => console.error(new Date(), ...params),
};
const ws = new WebsocketClient(
{
key: process.env.BYBIT_API_KEY!,
secret: process.env.BYBIT_API_SECRET!,
},
customLogger,
);For raw HTTP request/response tracing during local debugging, the repo also supports the BYBITTRACE environment variable. Do not enable verbose tracing in production logs if it could expose sensitive request data.
Do I need API keys for public market data?
No. Public REST API market data and public WebSocket market data do not usually require API keys.
Which REST API client should I use?
Use RestClientV5 for new Bybit API work. SpotClientV3 remains in the package for legacy compatibility, but new integrations should use the current API surface.
Why does every example use category?
The Bybit API uses category to distinguish Spot, Linear, Inverse, and Options behavior. The same SDK method can often cover several product groups, so the category tells Bybit which product family the request belongs to. Refer to Bybit's API documentation for exact guidance on expected request parameters.
Why both WebsocketClient and WebsocketAPIClient?
WebsocketClient is for subscriptions and streaming topics.WebsocketAPIClient is for commands over Bybit's WebSocket API. Think request/response methods over a persistent WebSocket connection.Can I use one key for everything?
Only if the key belongs to the correct environment and has the required permissions. For production systems, keep keys scoped by environment and permission level. Avoid withdrawal permissions unless your service truly needs them.
Does the SDK support RSA authentication?
Yes. Pass your Bybit API key as key and your PEM private key as secret. The SDK detects RSA private keys automatically.
Should I use demo trading or testnet?
Use demo trading for simulated trading with live-like market context where Bybit supports the endpoint. Use testnet for API wiring and WebSocket API command testing. Do not use testnet market behavior as evidence that a strategy will behave well live. Read more about CEX testnets here: https://github.com/sieblyio/awesome-crypto-examples/wiki/CEX-Testnets
What happens if a WebSocket connection drops?
The SDK detects dead connections, opens a replacement connection, authenticates where needed, and resubscribes cached topics. Listen for reconnect and reconnected, then reconcile state with the REST API before resuming risky trading actions.
Does this guide cover every SDK method?
No. This guide covers the common first steps and production concerns. For full method coverage, see:
If you want to learn more about integrating with the Bybit API and WebSockets:
bybit-apitiagosiebler/bybit-apiReturn to install snippets, direct examples, endpoint maps, and package links.
Open the runnable Bybit WebSocket API client example referenced in the tutorial.
Use the Bybit guide for orderLinkId context lookup, retCode gates, triggerDirection stops, private streams, and demo execution boundaries.
Browse SDK source, releases, issues, and endpoint coverage from GitHub.
We use essential cookies and optional analytics. Read the Privacy Policy.
Essential cookies stay on. Toggle analytics if you want to share anonymous usage insights. You can revisit this anytime via Cookie Settings in the footer.