Bitget has several API surfaces that don't behave the same way: V3/UTA REST, public and private V3 WebSocket streams, a V3 WebSocket API for order commands, and the V2/Classic APIs for accounts that haven't moved to the Unified Trading Account. Signing, routing, reconnects and resubscribes add up quickly if you wire them yourself. This tutorial walks through bitget-api, an MIT-licensed Node.js, JavaScript and TypeScript SDK for Bitget, from install to a demo order.

The SDK comes from the same maintainers as the binance, bybit-api and okx-api JS SDKs, with over 4.5 million npm downloads across 10+ SDKs. The overview lives in the Bitget JavaScript SDK docs, and the longer walkthrough (more V3 calls, Classic APIs, proxies and production notes) is the Bitget API JavaScript tutorial for Node.js.

What you get

Use case SDK surface
V3/UTA REST RestClientV3
V3 WebSocket streams WebsocketClientV3
V3 WebSocket API commands WebsocketAPIClient
Classic REST RestClientV2
Classic WebSocket streams WebsocketClientV2

For new UTA work, start with the V3 clients. Use the V2 clients only when an account or workflow is still on Bitget Classic.

Install and API keys

npm install bitget-api 

Public market data needs no keys. Private REST, private streams and WebSocket API orders need three credentials: apiKey, apiSecret and apiPass. apiPass is the passphrase you set when creating the key, not your account password.

Demo trading uses separate demo keys plus demoTrading: true. Grant the minimum permissions you need, leave withdrawals off, and whitelist IPs if your server has fixed outbound addresses.

1. Public REST

import { RestClientV3 } from 'bitget-api'; const client = new RestClientV3(); async function main() { const tickers = await client.getTickers({ category: 'SPOT', symbol: 'BTCUSDT' }); const orderBook = await client.getOrderBook({ category: 'SPOT', symbol: 'BTCUSDT', limit: '5', }); const candles = await client.getCandles({ category: 'SPOT', symbol: 'BTCUSDT', interval: '1m', limit: '5', }); console.log({ ticker: tickers.data[0], bestBid: orderBook.data.b[0], bestAsk: orderBook.data.a[0], candles: candles.data, }); } main().catch(console.error); 

2. Private REST

The same class takes credentials and signs each request.

import { RestClientV3 } from 'bitget-api'; const client = new RestClientV3({ apiKey: process.env.BITGET_API_KEY!, apiSecret: process.env.BITGET_API_SECRET!, apiPass: process.env.BITGET_API_PASS!, }); async function main() { const balances = await client.getBalances(); const settings = await client.getAccountSettings(); console.log({ assetMode: settings.data.assetMode, usdtEquity: balances.data.usdtEquity, assets: balances.data.assets.slice(0, 5), }); } main().catch(console.error); 

3. Public WebSocket streams

import { WebsocketClientV3, WS_KEY_MAP } from 'bitget-api'; const ws = new WebsocketClientV3({}); ws.on('update', (data) => console.log('market update', JSON.stringify(data))); ws.on('response', (data) => console.log('response', JSON.stringify(data))); ws.on('reconnected', (data) => console.log('reconnected', data?.wsKey)); ws.on('exception', console.error); ws.subscribe( [ { topic: 'ticker', payload: { instType: 'spot', symbol: 'BTCUSDT' } }, { topic: 'books', payload: { instType: 'spot', symbol: 'BTCUSDT' } }, ], WS_KEY_MAP.v3Public, ); 

The client keeps the connection alive, reconnects on drops and resubscribes for you.

One timing note: from 17 November 2026 (already live on demo trading since 4 November), Bitget stops sending an initial snapshot on the kline and publicTrade channels and pushes only update events. If your code waited for that first snapshot to seed candles or recent trades, load the history with getCandles() over REST first and then apply the stream updates on top.

4. Private account streams

For V3 private streams, instType is always 'UTA'.

import { WebsocketClientV3, WS_KEY_MAP } from 'bitget-api'; const ws = new WebsocketClientV3({ apiKey: process.env.BITGET_API_KEY!, apiSecret: process.env.BITGET_API_SECRET!, apiPass: process.env.BITGET_API_PASS!, }); ws.on('authenticated', (data) => console.log('authenticated', data.wsKey)); ws.on('update', (data) => console.log('account update', JSON.stringify(data))); ws.on('reconnected', (data) => { console.log('reconnected', data?.wsKey); // Reconcile balances, positions and open orders over REST here. }); ws.on('exception', console.error); ws.subscribe( [ { topic: 'account', payload: { instType: 'UTA' } }, { topic: 'position', payload: { instType: 'UTA' } }, { topic: 'fill', payload: { instType: 'UTA' } }, { topic: 'order', payload: { instType: 'UTA' } }, ], WS_KEY_MAP.v3Private, ); 

5. A demo order over REST

Use demo keys and keep demoTrading: true while testing. A successful response means Bitget accepted the order, not that it filled.

import { RestClientV3 } from 'bitget-api'; const client = new RestClientV3({ apiKey: process.env.BITGET_API_KEY!, apiSecret: process.env.BITGET_API_SECRET!, apiPass: process.env.BITGET_API_PASS!, demoTrading: true, }); async function main() { const ticker = await client.getTickers({ category: 'SPOT', symbol: 'BTCUSDT' }); const lastPrice = ticker.data[0]?.lastPrice; if (!lastPrice) throw new Error('No BTCUSDT price returned.'); const result = await client.submitNewOrder({ category: 'SPOT', symbol: 'BTCUSDT', side: 'buy', orderType: 'limit', price: lastPrice, qty: '0.001', timeInForce: 'gtc', clientOid: `demo-rest-${Date.now()}`, }); console.log(result); } main().catch(console.error); 

6. The same order over the WebSocket API

WebsocketAPIClient sends commands over a persistent connection and gives you a promise per command.

import { WebsocketAPIClient } from 'bitget-api'; const wsApi = new WebsocketAPIClient({ apiKey: process.env.BITGET_API_KEY!, apiSecret: process.env.BITGET_API_SECRET!, apiPass: process.env.BITGET_API_PASS!, demoTrading: true, }); async function main() { await wsApi.getWSClient().connectWSAPI(); const clientOid = `demo-wsapi-${Date.now()}`; const placed = await wsApi.submitNewOrder('spot', { symbol: 'BTCUSDT', side: 'buy', orderType: 'limit', price: '10000', qty: '0.001', timeInForce: 'gtc', clientOid, }); console.log('placed', placed); const cancelled = await wsApi.cancelOrder('spot', { clientOid }); console.log('cancelled', cancelled); } main().catch(console.error); 

One thing that trips people up: order option casing differs by surface. V3 REST uses reduceOnly: 'yes' | 'no', while the V3 WebSocket API and V2 futures use 'YES' | 'NO'. The type definitions for each client tell you which one applies.

Where to go next

More docs

API reference and usage guides also live on the Siebly docs portal (Hyperliquid, Bybit and Binance pages so far).