Blog
WebSocketsTrading systemsTypeScriptNode.jsJavaScript

Bitget Futures API Node.js SDK: A Professional Integration Guide

Signing Bitget requests by hand is a common time sink.

Siebly.io10 min readMarkdown

Overview

Signing Bitget requests by hand is a common time sink. HMAC strings, RSA keys, clock drift, and the split between classic V2 endpoints and V3 Unified Trading Account (UTA) APIs all sit between you and actual order logic. Raw HTTP plus a homemade WebSocket client usually fails in the same places: reconnects, heartbeats, and mismatched REST vs stream payloads.

This guide shows how to integrate Bitget futures in Node.js with the bitget-api package. You get request signing, typed REST clients, and an awaitable WebSocket API for order placement. Rate limits stay your problem. The SDK does not throttle for you.

New work should target V3/UTA via RestClientV3. Use RestClientV2 only if the account is still on classic mode. After you upgrade to UTA, V2 account endpoints stop working unless you revert.

Key Takeaways

  • Use bitget-api so HMAC and RSA signing, timestamps, and reconnects are handled in one client instead of custom crypto.
  • Pick the right class: RestClientV3 and WebsocketClientV3 for UTA, RestClientV2 and WebsocketClientV2 if you have not upgraded yet.
  • Place futures orders over REST with submitNewOrder, not placeOrder. The WebSocket API client uses the same method name, with the market type as the first argument.
  • Bitget does not ship a separate testnet. Paper trade with demoTrading: true and a Demo API key.
  • Public market data is capped around 20 requests per second. Private limits are per endpoint. The SDK will not queue or throttle those calls for you.

The Challenge of Manual Bitget Futures API Integration

Bitget currently runs two generations of APIs. Classic accounts talk to V2. Unified Trading Account talks to V3. Docs, parameter names, and WebSocket topic shapes differ between the two. If you mix them, you get auth errors or empty payloads, not a helpful compiler warning.

Manual HMAC-SHA256 signing is the other tax. Each private REST call signs timestamp + METHOD + requestPath + body. One encoding mismatch and you get an invalid signature. RSA is worse if you are assembling PEM headers yourself. Clock skew on the signing timestamp is a frequent source of 40004 / 40008 failures.

The Problem with DIY API Wrappers

A thin axios wrapper looks cheap until Bitget changes a field name, or a WebSocket drops and you have to resubscribe, re-auth, and rebuild local order state. Heartbeats, pong handling, and reconnect logic are the parts people underestimate. TypeScript types for every futures order shape are also tedious to keep current.

Siebly.io as the Preferred Implementation Layer

The Bitget Futures API Node.js SDK is the bitget-api npm package. It signs REST and WebSocket traffic, exposes V2 and V3 clients, and ships TypeScript types for request and response bodies.

It does not implement rate-limit queues. Bitget documents limits per endpoint, plus a global public cap of about 20 market-data requests per second and 6000 requests per IP per minute. Stay inside those yourself.

Initializing the bitget-api SDK for Futures Trading

Install the package:

Imported example

Shell
npm install bitget-api

Or yarn add bitget-api. Put API credentials in environment variables. Do not hardcode keys. When you create a Bitget key for bots, leave withdrawals disabled.

HMAC keys need three values: API key, secret, and passphrase. In this SDK those map to apiKey, apiSecret, and apiPass.

Imported example

JavaScript
import { RestClientV3 } from "bitget-api";
// or if you prefer require:
// const { RestClientV3 } = require('bitget-api');

const client = new RestClientV3({
  apiKey: process.env.API_KEY_COM,
  apiSecret: process.env.API_SECRET_COM,
  apiPass: process.env.API_PASS_COM,
});

For public market data only, skip credentials:

Imported example

JavaScript
const publicClient = new RestClientV3();

V3 methods are available after the account is on UTA. If it is still classic, use RestClientV2 instead.

Authentication and Client Configuration

HMAC is the default. Pass a normal API secret string.

RSA is detected from the secret itself. There is no signMethod flag. Put the PEM private key in apiSecret, including the BEGIN PRIVATE KEY / END PRIVATE KEY lines. Bitget never sees that private key. You upload the matching public key when you create the API key.

Imported example

JavaScript
import { RestClientV3 } from "bitget-api";

const rsaPrivateKey = `-----BEGIN PRIVATE KEY-----
...your RSA private key...
-----END PRIVATE KEY-----`;

const client = new RestClientV3({
  apiKey: process.env.API_KEY_COM,
  apiSecret: rsaPrivateKey,
  apiPass: process.env.API_PASS_COM,
});

Demo trading is not a separate base URL you toggle as "testnet". Create a Demo API key on Bitget (switch the UI to demo mode, then create the key), then set demoTrading: true. The SDK adds the paptrading: 1 header on REST and routes WebSockets to the demo hosts.

Imported example

JavaScript
const client = new RestClientV3({
  apiKey: process.env.API_KEY_COM,
  apiSecret: process.env.API_SECRET_COM,
  apiPass: process.env.API_PASS_COM,
  demoTrading: true,
});

Behind a proxy, pass axios options as the second constructor argument:

Imported example

JavaScript
const client = new RestClientV3(
  {
apiKey: process.env.API_KEY_COM,
apiSecret: process.env.API_SECRET_COM,
apiPass: process.env.API_PASS_COM,
  },
  {
proxy: { host: "127.0.0.1", port: 8080 },
  },
);

You can also override baseUrl in the first argument if you need a custom REST host.

TypeScript Integration and Type Safety

bitget-api ships type declarations. Futures orders use string quantities and prices (qty, price), which matches Bitget's API. That avoids JavaScript number rounding if you keep those values as strings. The SDK is not a BigNumber library. If you convert '0.001' to a float and back, that is on you.

Public and private calls share the same RestClientV3 instance. Spot vs futures is a category field, not a second client class. REST futures use USDT-FUTURES, COIN-FUTURES, or USDC-FUTURES. WebSocket market topics use lowercase usdt-futures. Mix those strings and the call fails. For a full method list, see the Bitget JavaScript SDK docs.

Executing REST API Workflows for Market and Private Data

REST is still the right tool for snapshots: balances, open positions, instrument rules, historical candles. RestClientV3 methods return promises and throw on Bitget error payloads (when code is not 00000).

Market Data Ingestion Patterns

No keys needed for these:

Imported example

JavaScript
import { RestClientV3 } from "bitget-api";

const restClient = new RestClientV3();
const symbol = "BTCUSDT";

const candles = await restClient.getCandles({
  symbol,
  category: "USDT-FUTURES",
  interval: "1m",
});

const tickers = await restClient.getTickers({
  category: "USDT-FUTURES",
  symbol,
});

const book = await restClient.getOrderBook({
  category: "USDT-FUTURES",
  symbol,
  limit: "20",
});

Use REST candles and books as a backup, not as your live feed. For history, getHistoryCandles is the sibling of getCandles.

Private Account and Order Management

Check balance and positions before you send size:

Imported example

JavaScript
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,
});

const balanceResult = await client.getBalances();
const usdtAsset = balanceResult.data.assets?.find(
  (asset) => asset.coin === "USDT",
);

const positionsResult = await client.getCurrentPosition({
  category: "USDT-FUTURES",
});
const openPositions = positionsResult.data.list.filter(
  (pos) => pos.total !== "0",
);

The place-order method is submitNewOrder. Cancel is cancelOrder. Amend is modifyOrder. There is no placeOrder helper on this client.

Imported example

JavaScript
const order = await client.submitNewOrder({
  category: "USDT-FUTURES",
  orderType: "market",
  side: "buy",
  qty: "0.001",
  symbol: "BTCUSDT",
});

await client.cancelOrder({
  orderId: order.data.orderId,
});

Instrument filters (minOrderQty, tick size) come from getInstruments({ category: 'USDT-FUTURES', symbol: 'BTCUSDT' }). Size your order from that, not from a hardcoded lot.

Bitget error codes worth knowing:

  • 40012: API key or passphrase is wrong
  • 40014: API key is missing a required permission
  • 25202: insufficient balance (V3)
  • 25227: no position to close
  • 43012: insufficient balance on older V2 paths

The SDK throws a parsed error object. Bitget's code lives on error.body.code, not as a magic 40012 for "not enough margin".

Rate limits: the SDK will not slow you down. Public market endpoints are documented at 20 requests per second. Private endpoints each have their own cap in Bitget's docs. Do not treat "10 private requests per second" as a single global budget.

Leveraging Awaitable WebSockets for Low-Latency Execution

REST is fine for account checks. For live execution you want a persistent socket. The Bitget Futures API Node.js SDK splits that into two classes on V3:

  • WebsocketClientV3 for market and account streams
  • WebsocketAPIClient for order placement that returns a promise

Awaitable vs. Event-Driven WebSockets

WebsocketClientV3 is event-driven: you subscribe(), then handle update. That is the right shape for tickers, books, fills, and position pushes.

WebsocketAPIClient wraps the V3 WebSocket trade API. You call submitNewOrder(category, params) and await the matching response. Under the hood it still uses a long-lived connection. You skip the HTTP handshake on every order.

REST category values are uppercase (USDT-FUTURES). The WebSocket API category is lowercase (usdt-futures).

Imported example

JavaScript
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,
});

async function start() {
  await wsClient.getWSClient().connectWSAPI();

  const res = await wsClient.submitNewOrder("usdt-futures", {
orderType: "limit",
price: "50000",
qty: "0.001",
side: "buy",
symbol: "BTCUSDT",
timeInForce: "gtc",
  });

  console.log("WS API submitNewOrder result: ", res);

  await wsClient.cancelOrder("usdt-futures", {
orderId: res.args?.[0]?.orderId,
  });
}

start().catch((e) => console.error("Exception in example: ", e));

Batch place and batch cancel exist as placeBatchOrders and cancelBatchOrders. Batch place can return code: "0" on the envelope while individual rows fail. Read each row's code and msg.

For market and account data, use WebsocketClientV3:

Imported example

JavaScript
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,
});

wsClient.on("update", (data) => {
  console.log("WS raw message received ", data);
});

wsClient.on("open", (data) => {
  console.log("WS connection opened:", data.wsKey);
});

wsClient.on("exception", (data) => {
  console.log("WS error", data);
});

wsClient.subscribe(
  {
topic: "ticker",
payload: {
instType: "usdt-futures",
symbol: "BTCUSDT",
},
  },
  WS_KEY_MAP.v3Public,
);

wsClient.subscribe(
  [
{
topic: "account",
payload: { instType: "UTA" },
},
{
topic: "position",
payload: { instType: "UTA" },
},
{
topic: "order",
payload: { instType: "UTA" },
},
{
topic: "fill",
payload: { instType: "UTA" },
},
  ],
  WS_KEY_MAP.v3Private,
);

Account-level private topics use instType: 'UTA', not usdt-futures.

If you are still on classic V2, use WebsocketClientV2 and subscribeTopic('USDT-FUTURES', 'ticker', symbol). V2 has no WebsocketAPIClient.

Maintaining WebSocket Stability

The SDK sends pings and reconnects on drop. After a reconnect it resubscribes to topics it already tracked. You do not need to call subscribe() again for those topics.

You still need to reconcile state. A fill can happen while the socket is down. Listen for reconnected and then REST-check positions and open orders:

Imported example

JavaScript
wsClient.on("reconnect", ({ wsKey }) => {
  console.log("WS automatically reconnecting.... ", wsKey);
});

wsClient.on("reconnected", async (data) => {
  console.log("WS reconnected ", data?.wsKey);
  const positions = await client.getCurrentPosition({
category: "USDT-FUTURES",
  });
  console.log("positions after reconnect: ", positions.data.list);
});

Bitget's documented WebSocket caps are 100 connections per IP, 300 connection attempts per IP per 5 minutes, and a recommendation to keep under 50 channels on one connection. Group tickers and books for the same symbols on one public socket instead of opening a socket per market.

More walkthroughs: bitget-api package and the Bitget Node.js SDK tutorial.

Architectural Best Practices for Resilient Trading Systems

API connectivity is the easy part. Put limits in your own process: max position, max order size, kill switch on stale data, keys with trade permission and no withdraw. The SDK gives you typed calls and a stable socket. Risk checks stay in your code.

Typed clients also help when you generate glue code. Request shapes for submitNewOrder are in the package, so missing category or a numeric qty shows up in the editor instead of at the matching engine.

Simulation and Testnet Workflows

There is no Bitget testnet flag in this SDK. Use demo trading:

  1. Switch the Bitget UI to demo mode and create a Demo API key.
  2. Pass that key to RestClientV3 / WebsocketAPIClient / WebsocketClientV3.
  3. Set demoTrading: true.

Same method names as live. Different funds. For broader Node.js system design, see Algorithmic Trading System Architecture.

Scaling Your Integration

Keep market sockets independent from order logic so a blocked REST call cannot stall ticker handling. One RestClientV3 can serve spot and futures through category. You do not need a client per market type.

If you run several accounts, give each one its own client instance and its own rate-limit budget. The package will not share a global throttle across processes.

For production, treat the Siebly Bitget SDK as the transport layer and keep strategy code away from signing and URL details. That is the point of V2 vs V3 clients: swap the class when the account model changes, without rewriting HMAC.

Building Resilient Execution Infrastructure on Bitget

A working Bitget futures stack in Node is: RestClientV3 for state, WebsocketClientV3 for streams, WebsocketAPIClient when you want awaitable orders on the V3 socket. Signing and reconnects are already done. You still own throttling, demo vs live keys, and what happens after reconnected.

Use TypeScript, keep qty and price as strings, and run the flow on demo trading before you point keys at live futures. Official Bitget docs remain the source of truth for endpoint-level rate limits and error codes.

If you want the package and examples in one place, start building with the Siebly Bitget SDK.

Frequently Asked Questions

Does the bitget-api SDK support Bitget V3 and UTA APIs?

Yes. RestClientV3, WebsocketClientV3, and WebsocketAPIClient are the UTA/V3 surface. Classic accounts should use RestClientV2 and WebsocketClientV2. After you migrate to UTA, V2 account APIs are no longer valid for that account.

How do I handle Bitget API rate limits in Node.js?

The SDK does not throttle. Public market data is documented at 20 requests per second. Private limits are listed per endpoint in Bitget's API docs, not as one number for every call. HTTP 429 means back off. Put a queue in your process if you burst.

Can I use the bitget-api SDK for both Spot and Futures trading?

Yes, on the same client. REST uses category: 'SPOT' or category: 'USDT-FUTURES' (also COIN-FUTURES and USDC-FUTURES). WebSocket market topics use instType: 'spot' or instType: 'usdt-futures'. You do not construct a separate futures-only class.

Does this SDK support RSA authentication for enhanced security?

Yes. Pass the PEM private key as apiSecret, including the begin/end private key lines. HMAC secrets stay a normal string. The SDK picks the algorithm from the key material.

How does the bitget-api SDK handle WebSocket reconnections?

Pings and reconnects are automatic. Topics you already subscribed to are resubscribed after the socket comes back. The client emits reconnected. You should still fetch positions and open orders over REST after that event, because you can miss fills during the gap.

Is TypeScript support included in the bitget-api package?

Yes. Request and response types ship with the package. Futures order fields such as qty and price are strings.

How can I place futures orders via WebSockets using this SDK?

Use WebsocketAPIClient. Call submitNewOrder('usdt-futures', {... }) and await the result. Cancel with cancelOrder('usdt-futures', { orderId }). This is V3/UTA only. Classic V2 has streaming, not this awaitable trade API.

What is the best way to synchronize timestamps with Bitget servers?

REST signing uses your local Date.now(). The SDK does not fetch server time on startup and does not apply an automatic offset. Keep the machine clock accurate. If you want to measure drift, RestClientV3 has fetchLatencySummary() and getServerTime(). WebSocket clients expose setTimeOffsetMs if you need to nudge auth timestamps.

Disclaimer

*Technical and legal disclaimer: Siebly.io provides software development tools, SDKs, documentation, and educational engineering content for crypto exchange API integrations. This content is for software engineering education only and is not financial, investment, legal, tax, accounting, compliance, or trading advice.

Nothing in this article is a recommendation, invitation, or inducement to buy, sell, hold, trade, long, short, or allocate to any cryptoasset, exchange product, strategy, bot, or automated workflow. Examples, code patterns, simulations, backtests, and architecture diagrams are illustrative only and must not be treated as trading signals, investment recommendations, or evidence of future performance.

Cryptoasset markets are high risk and volatile. If you choose to build or operate exchange-connected software, you are responsible for your own legal, regulatory, tax, security, exchange-account, API-key, and risk-management obligations. Use public data, testnet, demo, dry-run, or paper-trading workflows before any live execution. Keep API keys server-side, use least-privilege permissions, and never enable withdrawals for automation keys unless you fully understand and accept the risks.

Siebly.io is not an exchange, custodian, investment adviser, trading-signal provider, or managed trading service. Official exchange documentation remains the source of truth for exchange-specific rules, API behavior, and terms of use. Use of Siebly.io content and software is also subject to the Siebly.io terms and conditions.*

Related articles

Continue from here

Related Siebly resources

All articles

Subscribe on Substack

Complete the Substack form below to join our newsletter. Substack handles all subscriber data directly.