Overview
Private Bitget calls need a signature, a timestamp, and the passphrase you set when you created the key. Streams sit on a different client from order calls. The bitget-api package from Siebly.io is the TypeScript client for that work. Setup notes also live in the Bitget JavaScript SDK guide.
This guide follows the clients in the current package: a public REST call, a signed REST call, a market stream, and an order sent on Bitget's V3 WebSocket API. The client signs requests, sends timestamps, and types the inputs. Your code still decides how fast to call, and what to do when Bitget returns a limit error.
Bitget's docs are the contract. Use the V3 Unified Trading Account docs or the V2 docs, depending on the account. The SDK is how you call those endpoints from Node.js, TypeScript, or plain JavaScript.
Key takeaways
- Use
RestClientV3andWebsocketClientV3on a Unified Trading Account. UseRestClientV2andWebsocketClientV2on a classic account. - Public market calls need no keys. Private calls need
apiKey,apiSecret, andapiPasstogether. WebsocketAPIClientplaces V3 orders over a WebSocket and returns a promise. Stream updates come fromWebsocketClientV3. Treat those as separate paths.- The WebSocket client reconnects and resubscribes. It does not retry a failed REST call, and it does not throttle you.
- Start with a public call. For private calls, turn on
demoTradingand use demo keys.
Which client to construct
bitget-api ships five clients:
RestClientV3for V3 REST on a Unified Trading AccountWebsocketClientV3for V3 market and account streamsWebsocketAPIClientfor V3 orders over a WebSocket, returned as a promiseRestClientV2for V2 REST on a classic accountWebsocketClientV2for V2 streams
V3 account calls, private streams, and WebSocket orders need a Unified Trading Account. After you upgrade, V2 account calls stop working until you switch the account back to classic mode. If the account is still classic, stay on the V2 clients.
The package signs with HMAC by default, through the Web Crypto API. An RSA key works too: put the PEM private key in apiSecret, and keep the BEGIN PRIVATE KEY header in the string so the client treats it as RSA. You still pass apiPass. That value is the passphrase from API key creation, not the Bitget login password.
There is no V1 REST client in the current package. Use V2 or V3.
Install the package and call public data
Imported example
npm install bitget-api
Private signing uses Web Crypto (globalThis.crypto). Current Node LTS has it. If crypto is missing, the client tells you to move to Node LTS (v22 or newer), or to pass customSignMessageFn and sign with node:crypto yourself. The repo example for that is examples/auth/fasterHmacSign.ts.
Load keys from the environment. Do not commit them, and do not print them. A public client does not need keys at all.
This is the V3 candle call from examples/V3 - UTA/Rest/rest-public-UTA-spot.ts. The published package import is bitget-api.
Imported example
import { RestClientV3 } from "bitget-api";
const restClient = new RestClientV3();
const candles = await restClient.getCandles({
symbol: "BTCUSDT",
category: "SPOT",
interval: "1m",
});
console.log(candles.data.length);
On a classic account, the matching public call is RestClientV2.getSpotCandles():
Imported example
import { RestClientV2 } from "bitget-api";
const restClient = new RestClientV2();
const candles = await restClient.getSpotCandles({
symbol: "BTCUSDT",
granularity: "1min",
limit: "100",
});
console.log(candles.data.length);
require('bitget-api') works the same way if the project is CommonJS. A browser build is available through the package webpack bundle.
Sign a private REST call
Pass all three credentials. If you set any one of them and leave another empty, the constructor throws: key, secret, and passphrase are all required.
demoTrading: true sends Bitget's demo header (paptrading: 1) and expects demo keys. Live keys on that flag will not do what you want. Bitget documents the demo REST setup here.
Imported example
import { RestClientV3 } from "bitget-api";
const client = new RestClientV3({
apiKey: process.env.API_KEY_COM,
apiSecret: process.env.API_SECRET_COM,
apiPass: process.env.API_PASS_COM,
// demoTrading: true,
});
try {
const balances = await client.getBalances();
console.log(balances.data);
} catch (error) {
console.error("request failed:", error);
}
That pattern is the private example in examples/V3 - UTA/Rest/rest-private.ts. The client builds the timestamp and signature. The REST receive window defaults to 5000 ms. Set recvWindow on the client if you need a different window.
A completed HTTP call can still be a failed order or a rejected read. If Bitget answers with HTTP 200 and a code other than 00000, the client throws. The thrown object carries the status, message, and body. Before it is thrown, apiSecret and apiPass are stripped out of requestOptions. Log the body. Do not log the client options you started with.
The client does not retry that failure. If you retry, do it in your code, and only for calls that are safe to send twice. A place-order call is not one of those unless you have your own idempotency key and you know Bitget will honor it.
Same story for rate limits. bitget-api does not queue, delay, or throttle requests. The other Siebly exchange clients in this set (Binance, Bitmart, Bybit, Coinbase, Gate, HTX, Kraken, KuCoin, OKX) do not throttle for you either. Pace the calls yourself, and decide what a limit response means for that job.
V3 REST order fields are typed as PlaceOrderRequestV3. Category lives on the object, in Bitget's REST spelling: SPOT, MARGIN, USDT-FUTURES, COIN-FUTURES, USDC-FUTURES. Keep that next to you when you look at the WebSocket sample below. The WebSocket category strings are different.
Streams and awaitable WebSocket calls
Subscribing to a topic and placing an order are different clients.
WebsocketClientV3 is the stream client. It connects when you subscribe, sends heartbeats, reconnects if the socket drops, and resubscribes to the topics it was holding. Listen for update when you want the data. reconnected tells you the socket is back. Your own state still has to be rebuilt from REST or from the next events. A reconnect event is not a full account snapshot.
Public ticker subscription, from examples/V3 - UTA/Websocket/ws-public.ts:
Imported example
import { WebsocketClientV3, WS_KEY_MAP } from "bitget-api";
const wsClient = new WebsocketClientV3();
wsClient.on("update", (data) => {
console.log("WS message", data);
});
wsClient.on("exception", (data) => {
console.log("WS error", data);
});
wsClient.on("reconnected", (data) => {
console.log("WS reconnected", data?.wsKey);
});
wsClient.subscribe(
{
topic: "ticker",
payload: {
instType: "spot",
symbol: "BTCUSDT",
},
},
WS_KEY_MAP.v3Public,
);
Account and order streams need keys, and on V3 the account topics use instType: 'UTA' on the private socket:
Imported example
import { WebsocketClientV3, WS_KEY_MAP } from "bitget-api";
const wsClient = new WebsocketClientV3({
apiKey: process.env.API_KEY_COM,
apiSecret: process.env.API_SECRET_COM,
apiPass: process.env.API_PASS_COM,
// demoTrading: true,
});
wsClient.on("update", (data) => {
console.log("WS message", data);
});
wsClient.subscribe(
{
topic: "order",
payload: {
instType: "UTA",
},
},
WS_KEY_MAP.v3Private,
);
WebsocketAPIClient is the awaitable path. It wraps WebsocketClientV3 and returns a promise that settles when the matching response arrives. This needs a V3 Unified Trading Account key. The call below is the one in examples/V3 - UTA/WS-API/ws-api-client-trade.ts. Running it sends an order. Leave demoTrading on unless you mean to trade.
Imported example
import { WebsocketAPIClient } from "bitget-api";
const wsClient = new WebsocketAPIClient({
apiKey: process.env.API_KEY_COM,
apiSecret: process.env.API_SECRET_COM,
apiPass: process.env.API_PASS_COM,
// demoTrading: true,
});
try {
const res = await wsClient.submitNewOrder("spot", {
orderType: "limit",
price: "100",
qty: "0.1",
side: "buy",
symbol: "BTCUSDT",
timeInForce: "gtc",
});
console.log("submitNewOrder result:", res);
} catch (error) {
console.error("submitNewOrder failed:", error);
}
The category is the first argument here: spot, usdt-futures, coin-futures, or usdc-futures. On REST, the same idea is a field named category with values like SPOT and USDT-FUTURES. Those strings are not interchangeable. reduceOnly differs too: REST uses yes and no, the WebSocket place-order params use YES and NO.
By default WebsocketAPIClient logs connection events. Set attachEventListeners: false and listen on wsClient.getWSClient() if you want to handle open, reconnect, and reconnected yourself.
The promise resolving means Bitget answered that request. It does not mean your stream handler has already seen the matching order event. Read the response in the order path. Read fills and order updates from the stream path.
If you call placeBatchOrders, read each item in the response. That call resolves even when individual orders are rejected. Check code and msg on each order.
Check the integration before you add more
Use a short sequence:
- Read the method on
RestClientV3orRestClientV2and the matching page in Bitget's docs. - Run one public call and check the shape you actually branch on. For V3 candles that is
response.data. - For anything private, use demo keys with
demoTrading: true. Turn off withdrawal permission on keys that software holds. - Force a bad call, such as a missing passphrase or a symbol the endpoint does not accept, and make sure the error reaches the caller.
- Pin the installed version. Before a bump, read the Siebly SDK release notes and run the same calls again.
A test can show that you built the request, read the response, and surfaced the error. It cannot tell you how that workflow behaves in a live market.
Keep the exchange calls in one module. Let the rest of the app see balances, orders, and errors in whatever shape you actually store. The SDK client is that boundary. Pacing, retries, and what you do after a disconnect stay in the app.
Frequently asked questions
What is the Bitget API TypeScript SDK?
It is the bitget-api package: a TypeScript client for Bitget V2 and V3 REST and WebSockets, usable from JavaScript and Node.js. It signs requests, sends timestamps, and types the request and response objects. V3 WebSocket orders go through WebsocketAPIClient. Bitget's docs still define the endpoint behavior.
How do I install it?
npm install bitget-api
Import RestClientV3, RestClientV2, WebsocketClientV3, WebsocketClientV2, or WebsocketAPIClient from bitget-api. Check the Bitget JavaScript SDK guide if the import in an old snippet does not match the version you installed.
Does the SDK handle rate limits?
No. Nothing in bitget-api delays or batches calls to stay under Bitget's limits. The same is true of the other Siebly exchange SDKs. Your process has to pace requests and react when Bitget says you hit a limit.
There is also no REST retry policy. If a call throws, it stays thrown until your code decides to send it again.
Can I use it with Node.js and TypeScript?
Yes. The library is written in TypeScript and the npm build works from TypeScript or JavaScript, including require. Private calls need Web Crypto, which current Node LTS provides. The package also builds a browser bundle.
How do I test before using live keys?
Call a public method such as getCandles with new RestClientV3() and no credentials. For private methods, create demo API keys and construct the client with demoTrading: true. Confirm a failure still shows up as an error, and confirm logs do not contain the secret or passphrase.
Can WebSocket calls be awaited?
Yes, for V3 operations exposed on WebsocketAPIClient, including submitNewOrder. The promise resolves or rejects when the matching response arrives. Ongoing events are a subscription on WebsocketClientV3, not the return value of that call. The account has to be on the Unified Trading Account, and the key has to be a V3 key.
Related articles
Continue from here