---
title: "Bybit Inverse Perpetual API Node.js"
description: "A production engineering guide for the Bybit Inverse Perpetual API Node.js. Streamline authentication and WebSocket orders using the bybit-api SDK boilerplate."
canonical: "https://siebly.io/blog/bybit-inverse-perpetual-api-nodejs-a-production-engineering-guide"
---

# Bybit Inverse Perpetual API Node.js: A Production Engineering Guide

A production engineering guide for the Bybit Inverse Perpetual API Node.js. Streamline authentication and WebSocket orders using the bybit-api SDK boilerplate.

## Technical Disclaimer

These articles are software engineering references for exchange API integrations. They are not financial, investment, legal, tax, compliance, or trading advice. Use public data, demo, testnet, or paper workflows first. Keep API credentials out of frontend code and disable withdrawal permissions for automation keys.

## Overview {#overview}

Building a signing engine for the Bybit V5 API by hand is busywork most trading systems do not need. The V5 docs give you the spec. You still have to sign requests, keep timestamps inside the receive window, and keep WebSocket connections alive. Inverse perpetuals add another layer: coin-settled contracts, separate collateral per coin, and hedge mode via `positionIdx`.

The [bybit-api](/sdk/bybit/javascript) SDK is the Node.js layer we use for this. It signs REST calls, authenticates private sockets, and exposes awaitable WebSocket trade calls so order placement looks like a normal promise. You still own rate limits, retries, and how you rebuild local state. This guide walks through that setup for inverse perps, from client init to live streams.

## Key Takeaways {#key-takeaways}

- Inverse perps on Bybit V5 use `category: 'inverse'`. Collateral and PnL sit in the base coin (BTC, ETH), while order `qty` is in USD contracts.
- `RestClientV5` signs HMAC-SHA256 (or RSA) for you. Pass `key`, `secret`, and `testnet` or `demoTrading`. Default `recv_window` is 5000 ms.
- For lower-latency orders, use `WebsocketAPIClient.submitNewOrder()`. The SDK matches the exchange reply to your call via `reqId`.
- `WebsocketClient` opens the right V5 public or private stream, sends heartbeats, reconnects, and resubscribes on drop.
- The SDK does not throttle you. Enable `parseAPIRateLimits: true` to read remaining quota from response headers. IP floods return HTTP 403, not 429. UID over-limit returns `retCode` 10006.



## Understanding Bybit Inverse Perpetual Mechanics for Node.js Integration {#understanding-bybit-inverse-perpetual-mechanics-for-nodejs-integration}

Inverse perpetuals are [perpetual futures](https://en.wikipedia.org/wiki/Perpetual_futures) where the coin you trade is also the margin and settlement asset. BTCUSD is margined in BTC. ETHUSD is margined in ETH. Size is still quoted in USD contracts. Linear USDT perps (`category: 'linear'`) are a different product. Same V5 client, different `category`.

### Inverse vs Linear Contracts: The Engineering Difference {#inverse-vs-linear-contracts-the-engineering-difference}

Linear books share one USDT or USDC margin pool. Inverse books do not. If you trade BTCUSD and ETHUSD, you track two coin balances. On a Unified Trading Account query `accountType: 'UNIFIED'`. On a classic inverse account query `accountType: 'CONTRACT'`.

Hedge mode is not a separate API. After you switch position mode, every order needs `positionIdx`: `0` one-way, `1` buy side, `2` sell side. Miss it and Bybit rejects the order with a position-idx error.

The SDK does not hide that. It gives you one `submitOrder()` shape. You set `category: 'inverse'` and the rest of the V5 fields.

```js title="Imported example"
import { RestClientV5 } from "bybit-api";
// const { RestClientV5 } = require('bybit-api');

const client = new RestClientV5({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  testnet: true,
});

const tickers = await client.getTickers({
  category: "inverse",
  symbol: "BTCUSD",
});

const klines = await client.getKline({
  category: "inverse",
  symbol: "BTCUSD",
  interval: "60",
});

const positions = await client.getPositionInfo({
  category: "inverse",
  symbol: "BTCUSD",
});
```

### The Complexity of Raw V5 API Implementations {#the-complexity-of-raw-v5-api-implementations}

Private REST calls need an HMAC-SHA256 (or RSA-SHA256) signature over `timestamp + apiKey + recv_window + body`. Bybit does not use a nonce. Clock skew bigger than `recv_window` (default 5 seconds) gets you a recv-window error. A few milliseconds of drift is fine.

The SDK builds that signature and sends `X-BAPI-TIMESTAMP` / `X-BAPI-RECV-WINDOW` / `X-BAPI-SIGN`. It does not magically fix a machine clock that is minutes off. Sync NTP first. If you still see recv-window errors, raise `recv_window` or set a time offset. `enable_time_sync` exists and is off by default. Do not turn it on unless you know why. One slow public time call can shift every later request.

Rate limits are also on you. The IP cap is 600 HTTP requests per 5 seconds. Blow that and Bybit returns HTTP 403 (`access too frequent`), then may block the IP for about 10 minutes. UID limits are per endpoint and usually hit you first. Those show up as `retCode` 10006, not HTTP 429. Start from the [bybit-api tutorial](/sdk/bybit/javascript/tutorial) if you want the full client surface.

## Setting Up the bybit-api SDK for Inverse Perpetual Workflows {#setting-up-the-bybit-api-sdk-for-inverse-perpetual-workflows}

Install the npm package, create `RestClientV5`, keep secrets out of git. The library ships TypeScript types for V5 request and response shapes. JavaScript works the same. Types just fail earlier.

### Installation and Client Configuration {#installation-and-client-configuration}

```bash title="Imported example"
npm install bybit-api
```

Yarn is the same idea: `yarn add bybit-api`.

```js title="Imported example"
import { RestClientV5 } from "bybit-api";

const client = new RestClientV5({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  testnet: true, // api-testnet.bybit.com, needs testnet keys
  recv_window: 5000, // ms, default is already 5000
  parseAPIRateLimits: true,
});
```

`testnet: true` is the sandbox. `demoTrading: true` is Bybit's V5 demo on the live domain (`api-demo.bybit.com`). Do not set both. Demo uses live API keys with simulated funds. Testnet needs a separate testnet key. Demo market data is closer to production. Testnet data often is not.

Public calls do not need keys:

```js title="Imported example"
const publicClient = new RestClientV5();
const inverseTickers = await publicClient.getTickers({ category: "inverse" });
```

More setup patterns live in the [bybit-api tutorial](/sdk/bybit/javascript/tutorial).

### Security Best Practices for API Credentials {#security-best-practices-for-api-credentials}

Put keys in env vars or a secret store. Never commit them. In the Bybit key UI, grant Contract Trade on a classic inverse account, or Unified Trading on UTA. Leave Withdraw off. Restrict the key to the IPs your servers actually use.

There is no separate "Orders" permission on Bybit. If you copy Binance-style permission names here, the key will not match what Bybit shows.

The SDK signs. It does not rotate keys, whitelist IPs, or cap withdraw rights. That is account config. For a wider checklist see [best practices for secure exchange authentication](/blog). If you run more than one venue, the same client style exists across the [Siebly SDKs](/sdk) ([binance](/sdk/binance/javascript), [okx-api](/sdk/okx/javascript), [bitget-api](/sdk/bitget/javascript), and the rest).

## Executing Trades: REST API vs Awaitable WebSockets {#executing-trades-rest-api-vs-awaitable-websockets}

REST is fine for balances, position queries, and low-frequency orders. The WebSocket trade API reuses one authenticated connection, so you skip HTTP setup per order. Both paths use the same order fields. REST method is `submitOrder()`. WS method is `submitNewOrder()`.

### Placing Inverse Perpetual Orders via REST {#placing-inverse-perpetual-orders-via-rest}

`qty` on inverse is USD contracts, not BTC. `100` on BTCUSD is $100 notional, settled in BTC. `symbol` is `BTCUSD`, not `BTCUSDT`.

```js title="Imported example"
import { RestClientV5 } from "bybit-api";

const client = new RestClientV5({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  testnet: true,
  parseAPIRateLimits: true,
});

try {
  const response = await client.submitOrder({
category: "inverse",
symbol: "BTCUSD",
side: "Buy",
orderType: "Limit",
qty: "100",
price: "50000",
timeInForce: "GTC",
// hedge mode only:
// positionIdx: 1, // 0 one-way, 1 buy, 2 sell
  });

  console.log("orderId", response.result.orderId);
  console.log("uid rate limit", response.rateLimitApi);
} catch (error) {
  console.error("submitOrder failed", error);
}
```

Cancel and amend use the same client:

```js title="Imported example"
await client.amendOrder({
  category: "inverse",
  symbol: "BTCUSD",
  orderId: "your-order-id",
  price: "51000",
});

await client.cancelOrder({
  category: "inverse",
  symbol: "BTCUSD",
  orderId: "your-order-id",
});
```

Check `response.retCode === 0` before you trust `result`. The SDK returns the V5 envelope. It does not throw on every exchange error unless you set `throwExceptions: true`.

### Leveraging Awaitable WebSockets for Execution {#leveraging-awaitable-websockets-for-execution}

`WebsocketAPIClient` wraps the V5 trade socket (`order.create`, `order.amend`, `order.cancel`). Each call returns a promise. The SDK stamps a `reqId` and resolves when the matching reply arrives. You do not wire a one-off listener per order.

Demo trading does not support this trade WebSocket. Use testnet or live keys.

```js title="Imported example"
import { WebsocketAPIClient } from "bybit-api";

const wsApi = new WebsocketAPIClient({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  // testnet: true,
});

async function placeInverseOrder() {
  // optional: open the trade socket before the first order so the first call is not a cold start
  // await wsApi.getWSClient().connectWSAPI();

  try {
const response = await wsApi.submitNewOrder({
category: "inverse",
symbol: "BTCUSD",
side: "Buy",
orderType: "Limit",
qty: "100",
price: "50000",
});
console.log("ws order", response.data.orderId, response.retCode);
  } catch (error) {
console.error("ws submitNewOrder failed", error);
  }

  try {
const amended = await wsApi.amendOrder({
category: "inverse",
symbol: "BTCUSD",
orderId: "your-order-id",
price: "51000",
});
console.log("amended", amended.data.orderId);
  } catch (error) {
console.error("ws amendOrder failed", error);
  }
}

placeInverseOrder();
```

Batch WS helpers on this SDK (`batchSubmitOrders`, `batchAmendOrder`, `batchCancelOrder`) are typed for `linear` and `option` only. Inverse goes through the single-order methods above.

Heartbeats, reconnect, and re-auth on that trade socket are handled inside `WebsocketClient`. Listen to `exception` if you want to know when it is not healthy.



## Managing Real-Time Market Data and Account State {#managing-real-time-market-data-and-account-state}

Do not poll tickers or positions in a tight REST loop. You will chew the 600/5s IP budget and the tighter UID caps. Public and private V5 sockets are the right feed. REST stays for snapshots, order entry if you are not on WS trade, and recovery after a gap.

### Subscribing to Inverse Perpetual Public Streams {#subscribing-to-inverse-perpetual-public-streams}

Public inverse data lives on its own endpoint. `subscribeV5(topic, 'inverse')` routes there. Linear and spot topics go elsewhere. The client splits that for you.

```js title="Imported example"
import { WebsocketClient } from "bybit-api";

const wsClient = new WebsocketClient();

wsClient.on("update", (data) => {
  console.log("public update", JSON.stringify(data));
});

wsClient.on("open", ({ wsKey }) => {
  console.log("open", wsKey);
});

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

// depth is part of the topic name: orderbook.1 / 50 / 200 / 500
wsClient.subscribeV5("orderbook.50.BTCUSD", "inverse");
wsClient.subscribeV5(["tickers.BTCUSD", "publicTrade.BTCUSD"], "inverse");
```

The payload is still Bybit's JSON. Snapshot then deltas for the orderbook. The SDK does not build a local book. You do. If book math blocks the event loop, move it off the main thread. Patterns for that sit in [building reliable market data ingestion pipelines](/ai/historical-live-data-pipeline).

### Private Account and Position Stream Management {#private-account-and-position-stream-management}

Pass `key` and `secret`. Auth happens before private topics. There is one private V5 account socket, so the category argument is ignored for `position` / `order` / `execution` / `wallet`. You can still pass `'inverse'` to match the rest of your code.

```js title="Imported example"
import { WebsocketClient } from "bybit-api";

const wsClient = new WebsocketClient({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  // testnet: true,
  // demoTrading: true, // private user streams only. not the WS trade API
});

wsClient.on("update", (data) => {
  // topic is on the payload: position, order, execution, wallet, ...
  console.log("private update", data.topic, JSON.stringify(data.data));
});

wsClient.on("authenticated", ({ wsKey }) => {
  console.log("authenticated", wsKey);
});

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

wsClient.on("reconnected", ({ wsKey }) => {
  // subscriptions are restored by the SDK. resync your own in-memory state here.
  console.log("reconnected", wsKey);
});

wsClient.subscribeV5(["position", "order", "execution", "wallet"], "inverse");
```

On reconnect the client respawns the socket, re-auths, and resubscribes to whatever you have not unsubscribed. You do not have to call `subscribeV5` again in the `reconnected` handler. You should refresh local position and order state, because you may have missed fills while the socket was down. REST `getPositionInfo` / `getActiveOrders` is the usual snapshot. See [managing exchange state for Bybit](/ai/exchange-state/bybit) and the [Bybit JavaScript SDK](/sdk/bybit/javascript).

## Engineering Best Practices for Production Systems {#engineering-best-practices-for-production-systems}

The SDK keeps the connection and the signature correct. Production still needs a queue in front of REST, backoff on exchange errors, and logs that include `retCode`, `retMsg`, and rate-limit headers.

### Rate Limiting and Error Handling Strategies {#rate-limiting-and-error-handling-strategies}

[bybit-api](/sdk/bybit/javascript) does not run a token bucket. Turn on header parsing and read `rateLimitApi` after private calls:

```js title="Imported example"
const client = new RestClientV5({
  key: process.env.API_KEY,
  secret: process.env.API_SECRET,
  parseAPIRateLimits: true,
});

const openOrders = await client.getActiveOrders({
  category: "inverse",
  symbol: "BTCUSD",
});

const limit = openOrders.rateLimitApi;
if (limit && limit.remainingRequests < 5) {
  // remainingRequests comes from X-Bapi-Limit-Status
  // maxRequests from X-Bapi-Limit
  // resetAtTimestamp from X-Bapi-Limit-Reset-Timestamp
  console.log("slow down", limit);
}
```

Two different limits:

- IP: 600 requests / 5 seconds across `api.bybit.com` and regional hosts. Over it: HTTP 403, then a cool-off. This is not a 429.
- UID / endpoint: rolling per-second caps (create-order is much tighter than 600/5s). Over it: `retCode` 10006. Headers tell you remaining quota for that endpoint.

Retry 5xx and network drops with backoff. Do not retry 403 IP bans in a tight loop. Log timestamp, params, `retCode`, and `rateLimitApi`. That is what you will want during a bad session.

### Scaling with Siebly AI Prompt Frameworks {#scaling-with-siebly-ai-prompt-frameworks}

[Siebly AI prompt frameworks](/ai) are a way to generate typed V5 glue (orderbook reconstructors, multi-coin collateral checks) without inventing a new client. The runtime should still be this SDK. Signing and socket lifecycle are already solved. Use the prompts for the parts that are unique to your book, not for a second REST wrapper.

For a Node.js inverse stack, `bybit-api` is the client we recommend. Point AI agents at the [bybit-api tutorial](/sdk/bybit/javascript/tutorial) so they copy `RestClientV5`, `WebsocketClient`, and `WebsocketAPIClient` instead of raw `fetch`.

## Building Resilient Trading Infrastructure with Bybit V5 {#building-resilient-trading-infrastructure-with-bybit-v5}

Inverse perps on V5 are one category flag plus coin-margined accounting. `RestClientV5` covers REST. `WebsocketClient` covers market and account streams, including reconnect and resubscribe. `WebsocketAPIClient` covers awaitable order entry on the trade socket.

What you still write: throttle against IP 403 and UID 10006, rebuild the orderbook, and pick `positionIdx` when you run hedge mode. [Get started with the bybit-api SDK on Siebly.io](/sdk/bybit/javascript/tutorial).

## Frequently Asked Questions {#frequently-asked-questions}

### Is the bybit-api SDK compatible with the Bybit V5 API? {#is-the-bybit-api-sdk-compatible-with-the-bybit-v5-api}

Yes. `RestClientV5` is the V5 REST client. Spot, linear, inverse, and options share that client. You select the product with `category`. Inverse perps are `category: 'inverse'`. Older v2/v3 clients in the repo are not what you want for new work.

### How do I handle WebSocket reconnection in Node.js for Bybit? {#how-do-i-handle-websocket-reconnection-in-node-js-for-bybit}

Listen for `reconnect` and `reconnected`. The SDK already sends ping/pong, opens a new socket, re-auths private connections, and resubscribes to stored topics. Use `reconnected` to refresh your own caches (positions, working orders), not to replay `subscribeV5` unless you previously unsubscribed.

### Does the Siebly bybit-api SDK handle rate limiting automatically? {#does-the-siebly-bybit-api-sdk-handle-rate-limiting-automatically}

No throttle, no queue. Optional parsing only: set `parseAPIRateLimits: true` and read `response.rateLimitApi`. Watch `X-Bapi-Limit-Status` that way instead of scraping headers yourself. IP over-limit is HTTP 403. UID over-limit is `retCode` 10006. Plan for both.

### Can I use TypeScript with the Bybit Inverse Perpetual API? {#can-i-use-typescript-with-the-bybit-inverse-perpetual-api}

Yes. The package is written in TypeScript. `OrderParamsV5`, `CategoryV5`, and the WS payload types are in the published `.d.ts` files. `category: 'inverse'` is a real union member, not a string you hope the server accepts.

### How do I sign private requests for the Bybit V5 API in JavaScript? {#how-do-i-sign-private-requests-for-the-bybit-v5-api-in-javascript}

Pass `key` and `secret` into `RestClientV5` or `WebsocketClient`. HMAC-SHA256 is the default. If `secret` is an RSA PEM (`BEGIN PRIVATE KEY`), the SDK switches to RSA-SHA256. You do not build the sign string yourself. If latency on Node signing matters, inject `customSignMessageFn` with `crypto.createHmac`, as in the SDK's faster HMAC example.

### What is the difference between Inverse and Linear contracts in the Bybit API? {#what-is-the-difference-between-inverse-and-linear-contracts-in-the-bybit-api}

Inverse: `category: 'inverse'`, symbols like `BTCUSD`, margin and PnL in the base coin, `qty` in USD. Linear: `category: 'linear'`, symbols like `BTCUSDT`, margin in USDT/USDC. Same methods (`submitOrder`, `getPositionInfo`). Wrong `category` or symbol and the order never hits the book you wanted.

### How do I test my Bybit Node.js integration without risking real funds? {#how-do-i-test-my-bybit-node-js-integration-without-risking-real-funds}

Two flags, two environments:

```js title="Imported example"
// testnet keys, testnet hosts
new RestClientV5({ key, secret, testnet: true });

// live keys, Bybit demo funds. set testnet to false or omit it
new RestClientV5({ key, secret, demoTrading: true });
```

Demo trading is better for strategy tests because the data looks like production. The WebSocket trade API (`WebsocketAPIClient`) is not supported on demo. Private user streams (`order`, `position`, `wallet`) are. Treat demo keys like live keys.

### Is it possible to place orders via WebSockets using the bybit-api package? {#is-it-possible-to-place-orders-via-websockets-using-the-bybit-api-package}

Yes. `WebsocketAPIClient.submitNewOrder()` / `amendOrder()` / `cancelOrder()` return promises keyed by `reqId`. That works with `category: 'inverse'`. Batch WS methods in this SDK are for linear and option only.

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

- [Bybit Linear Perpetual API in Node.js: Professional Engineering Guide 2026](/blog/bybit-linear-perpetual-api-in-nodejs-professional-engineering-guide-2026)
- [Implementing Bybit V5 API Integration in Node.js: A Production-Ready Guide](/blog/implementing-bybit-v5-api-integration-in-nodejs-a-production-ready-guide)
- [Bybit Node.js SDK: Engineering Reliable V5 API Integrations](/blog/bybit-nodejs-sdk-engineering-reliable-v5-api-integrations)


## Related Siebly Resources

- [Binance JavaScript SDK](/sdk/binance/javascript)
- [Bybit JavaScript SDK](/sdk/bybit/javascript)
- [OKX JavaScript SDK](/sdk/okx/javascript)
- [Siebly SDK directory](/sdk)
- [Siebly AI Prompt Framework & Skills](/ai)
