# BingX API in Python: signing, VST demo and error codes

URL: https://fincodelabs.com/en/blog/bingx-api-python · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Exchange APIs · Published: 2026-11-06 · Updated: 2026-11-06

BingX API Python guide, docs read 6 Nov 2026: X-BX-APIKEY and HMAC signing, rate limits, error codes, the VST demo host and tested code.

**Short answer:** To use the **BingX API in Python**, sign the sorted parameters and a millisecond timestamp with HMAC-SHA256, add the hex result as `signature`, and send your key in the `X-BX-APIKEY` header. Live calls go to `https://open-api.bingx.com`, demo (VST) calls to `https://open-api-vst.bingx.com`, and errors usually arrive as HTTP 200 with a non-zero `code` (read and tested 6 November 2026).

BingX's official documentation at `bingx-api.github.io/docs-v3` is a JavaScript application; it was read in a browser and from its script bundle on 6 November 2026 (bundle last modified 3 November).

**What ran.** On 6 November 2026, 02:03 to 02:13 Istanbul time (23:03 to 23:13 UTC on 5 November), Python 3.11.3 with requests 2.31.0 and websockets 13.1 called BingX's live and demo public endpoints and its live futures stream. Private calls used placeholder keys; no order was placed.

This guide recommends no platform or strategy. Automated trading can lose money, and perpetual futures carry a high risk of rapid loss. Availability depends on your jurisdiction and BingX's terms. For readers in Turkey: Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized.

## BingX API documentation: hosts and base URLs

BingX documents one REST host for live trading, one for demo trading with virtual USDT (VST) and separate WebSocket hosts.

**Hosts in BingX's v3 documentation, read 6 November 2026**

| Purpose | Host | Note |
| --- | --- | --- |
| REST, live | `https://open-api.bingx.com` | Paths start with `/openApi/` |
| REST, demo (VST) | `https://open-api-vst.bingx.com` | Listed on endpoint pages |
| REST, backup | `open-api.bingx.io` | Traditional Chinese page only: for outages, 60 requests a minute in total |
| Futures market stream | `wss://open-api-swap.bingx.com/swap-market` | Demo: `wss://vst-open-api-ws.bingx.com/swap-market` |

Symbols take a hyphen (`BTCUSDT` returned 109400). The FAQ says the servers run on AWS in Singapore (ap-southeast-1) and one key serves spot and USDT-M futures, with separate permissions.

## BingX API key: permissions and IP whitelist

Create keys under API Management on the BingX website. The Signature Authentication page says new keys are read-only until you enable more and strongly recommends an IP whitelist.

- **Permissions.** Endpoint pages name Read, Spot Trading, Professional Futures Trading, Universal transfer and Withdraw, among others; a program that only trades needs neither of the last two. Missing permission: 100004; address outside the whitelist: 100419.
- **Leaks.** Delete a leaked key and create a new one. The FAQ's error-report template asks for the API key: never send the secret.

See the [exchange API key guide](https://fincodelabs.com/en/blog/exchange-api-key-how-to) and [API key security for trading bots](https://fincodelabs.com/en/blog/api-key-security-trading-bots).

## BingX API Python example: market data with real output

Market data needs no key: this measures the clock offset, reads contract rules and floors a hypothetical order to them.

```python
from decimal import Decimal, ROUND_DOWN
import time
import requests

BASE = "https://open-api.bingx.com"

def public(path, **params):
    r = requests.get(BASE + path, params=params, timeout=10)
    try:
        body = r.json(parse_float=Decimal)     # keeps 0.0001 exact
    except ValueError:
        body = {}
    if r.status_code == 200 and body.get("code") == 0:
        return body.get("data"), r.headers
    raise RuntimeError(f"HTTP {r.status_code} code {body.get('code')}: {body.get('msg')}")

def only(rows, symbol):
    hits = [row for row in rows or [] if row.get("symbol") == symbol]
    if len(hits) == 1:
        return hits[0]
    raise RuntimeError(f"{len(hits)} rows for {symbol}")    # never guess

def floor_to(value, places):
    d = Decimal(str(value))
    if not d.is_finite() or d.adjusted() > 12:          # inf, nan, 1e400
        raise ValueError(f"unusable number {value}")
    q = d.quantize(Decimal(1).scaleb(-places), ROUND_DOWN)
    if q <= 0:                                           # 1e-400 floors to 0
        raise ValueError(f"{value} floors to {q}")
    return q

sent = time.time()
data, hdr = public("/openApi/swap/v2/server/time")
offset_ms = data["serverTime"] - int((sent + time.time()) * 500)   # vs round-trip midpoint
print("offset ms", offset_ms, "| remain", hdr.get("X-RateLimit-Requests-Remain"))

spec = only(public("/openApi/swap/v2/quote/contracts", symbol="BTC-USDT")[0], "BTC-USDT")
bids = (public("/openApi/swap/v2/quote/depth", symbol="BTC-USDT", limit=5)[0] or {}).get("bids")
if not bids:
    raise RuntimeError("empty order book")
price = floor_to(Decimal(bids[0][0]) * Decimal("0.95"), spec["pricePrecision"])  # hypothetical
qty = floor_to(10 / price, spec["quantityPrecision"])                            # 10 USDT
print("price", price, "qty", qty, "| minimums met:",
      qty >= spec["tradeMinQuantity"] and qty * price >= spec["tradeMinUSDT"])

for bad in ("1e400", "1e-400", float("1e400")):
    try:
        floor_to(bad, 4)
    except ValueError as err:
        print("rejected:", err)
```

Output on 6 November 2026:

```
offset ms 1335 | remain 499
price 71556.2 qty 0.0001 | minimums met: True
rejected: unusable number 1e400
rejected: 1e-400 floors to 0.0000
rejected: unusable number inf
```

- **Numbers are not always strings.** The Basic Information page says decimals come as strings, but `tradeMinQuantity` arrived as the JSON number 0.0001; `parse_float=Decimal` keeps it exact.
- **Excess precision is truncated, not rejected** (0.123456 at a 0.0001 step becomes 0.1234, per Place order), so floor locally.
- **Clock.** This machine ran 1,335 ms behind BingX; the window is 5 seconds by default, adjustable with `recvWindow`.

## How to sign BingX API requests in Python

Sort every parameter, timestamp included, by key in ASCII order, join them as `key=value` pairs with `&`, compute HMAC-SHA256 with your secret and send the lowercase hex digest as `signature`.

Query strings are signed unencoded; if one contains `[` or `{`, URL-encode its values. Form bodies are signed encoded, as sent, and the sub-account endpoints the page lists take a JSON body carrying `timestamp` and `signature`.

```python
import hashlib, hmac, json, os, time, uuid
from decimal import Decimal
from urllib.parse import quote
import requests

BASE = "https://open-api-vst.bingx.com"        # VST demo; live: https://open-api.bingx.com

def sign(secret, text):
    return hmac.new(secret.encode(), text.encode(), hashlib.sha256).hexdigest()

def signing_string(params):
    return "&".join(f"{k}={params[k]}" for k in sorted(params))    # timestamp sorted too

assert sign("Jefe", "what do ya want for nothing?") == \
    "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"   # RFC 4231 case 2

def private(method, path, params, offset_ms=0):
    key, secret = os.environ.get("BINGX_KEY", ""), os.environ.get("BINGX_SECRET", "")
    if not (key and secret):                            # fail before any network call
        raise RuntimeError("set BINGX_KEY and BINGX_SECRET")
    p = {}
    for k, v in params.items():
        if isinstance(v, float):                        # float("1e400") is inf
            raise TypeError(f"{k}: pass Decimal or str, not float")
        if isinstance(v, Decimal):
            if not v.is_finite():                       # Decimal("inf") too
                raise ValueError(f"{k}: {v} is not finite")
            v = format(v, "f")                          # never 1E-7 on the wire
        if isinstance(v, (dict, list)):                 # no Infinity inside JSON
            v = json.dumps(v, separators=(",", ":"), allow_nan=False)
        if v not in (None, ""):                         # drop empty IDs
            p[k] = v
    p["timestamp"] = int(time.time() * 1000) + offset_ms
    raw = signing_string(p)
    enc = "[" in raw or "{" in raw                      # then encode values only
    query = "&".join(f"{k}={quote(str(p[k]), safe='') if enc else p[k]}" for k in sorted(p))
    r = requests.request(method, f"{BASE}{path}?{query}&signature={sign(secret, raw)}",
                         headers={"X-BX-APIKEY": key}, timeout=10)
    try:
        body = r.json()
    except ValueError:
        body = {}
    if r.status_code == 200 and body.get("code") == 0:  # errors also arrive as HTTP 200
        return body.get("data")
    raise RuntimeError(f"HTTP {r.status_code} code {body.get('code')}: {body.get('msg')}")

order = {"symbol": "BTC-USDT", "side": "BUY", "positionSide": "LONG", "type": "LIMIT",
         "quantity": "0.0001", "price": "50000", "clientOrderId": uuid.uuid4().hex}
try:
    print(private("POST", "/openApi/swap/v2/trade/order/test", order))
except RuntimeError as err:
    print(err)
```

Output without keys, then with placeholder strings:

```
set BINGX_KEY and BINGX_SECRET
HTTP 200 code 100413: Incorrect apiKey, please check your valid api key in https://bingx.com/en/account/api
```

Offline tests with a mocked `requests` passed for the no-key, float, infinity, empty-ID, JSON-encoding and error-body paths.

> **Example of a documentation conflict.** For `symbol=BTC-USDT` and `type=LIMIT`, the Signature page's rule signs `symbol=BTC-USDT&timestamp=1696751141337&type=LIMIT`, but the Python sample the documentation generates puts the timestamp last; its JavaScript samples sort it in. The server's rule could not be tested without a real key, so the code follows the Signature page and sends parameters in signed order.

- **Hex case.** The page says lowercase; the JavaScript sample for JSON-body endpoints uppercases it.
- **Check order.** With a placeholder key, a missing signature or a 60-second-old timestamp still returned 100413.
- **Client order IDs** are converted to lowercase (1 to 40 characters, LIMIT and MARKET orders only), so `A1` and `a1` collide; see [client order ID](https://fincodelabs.com/en/blog/client-order-id).

Other schemes: API signature generation; Fincode's [HMAC signature generator](https://fincodelabs.com/en/tools/hmac-signature-generator) checks a digest in the browser.

## BingX API rate limits: UID limits, IP limits and headers

Private endpoints have per-UID limits and market data a per-IP limit; the Basic Information page says over-frequent requests are limited and restored after 5 minutes.

**BingX documentation, read 6 November 2026**

| Limit | Value |
| --- | --- |
| Market data | 500 requests per 10 seconds per IP |
| USDT-M Place order, Cancel order | 10 per second per UID |
| Test order | 5 per second per UID |
| WebSocket | 60 connections per IP, 200 topics per connection, at most 10 subscriptions a second |

The IP limits once listed for account groups 2 and 3 (2,000 requests per 10 seconds, 300 per endpoint) were removed on 16 December 2025, according to BingX's change log.

Responses carry `X-RateLimit-Requests-Remain` and `X-RateLimit-Requests-Expire` (documented as window expiry; values seen were milliseconds left). On 6 November 2026 each public path had its own counter, starting at 499 and 10000; a server-time call on the demo host after two live ones read 497, so live and VST shared that counter from one IP.

The error reference adds HTTP 429, and 418 for an IP banned after ignoring 429. The FAQ calls HTTP 403 a firewall block, usually five minutes, and restricts accounts that often place and cancel without trading; orders with no intention to trade can be market abuse. See [token bucket rate limiter in Python](https://fincodelabs.com/en/blog/token-bucket-rate-limiter-python) and [exponential backoff with jitter](https://fincodelabs.com/en/blog/exponential-backoff-jitter-python).

## BingX API error codes you will meet first

Check `code` in the body, not just the HTTP status: every error seen on 6 November 2026 came with HTTP 200, although the Error Code Reference lists HTTP 401 for an invalid key.

**Error Code Reference, read 6 November 2026, and replies seen that day**

| Code | Documented meaning | Seen |
| --- | --- | --- |
| 100413 | API key incorrect or missing | Null apiKey without the header; Incorrect apiKey with a placeholder |
| 100001, 100412 | Signature failed; signature missing | Needs a real key |
| 100421 | Common list: timestamp missing or off server time. Spot list: LIMIT order cap of 1,000 reached, or pair closed to API orders | Needs a real key |
| 100410 | Rate limit exceeded | Not provoked |
| 100400 | Common list: wrong path or method. Spot list: bad parameter or order not found | this api is not exist, for a wrong path |
| 109400 | Invalid futures parameters | Symbol without a hyphen |
| 101481, 109201 | Client order ID already used; same order submitted twice in a short period | Needs a real key |

Codes differ by list, so log `msg` and the endpoint with each code. After HTTP 504 (status unconfirmed), query the order by `clientOrderId` before resending; see this [order state machine](https://fincodelabs.com/en/blog/order-state-machine).

## BingX demo trading (VST) and the test order endpoint

Test without real funds on the VST demo host or with the test order endpoint.

- **VST.** `POST /openApi/swap/v2/trade/getVst`, demo host only, adds (`adjustType` 0) or removes (1) whole units of virtual USDT: at most 1,000,000 a request, 10,000,000 in total. The v3 pages do not say whether demo trading needs a separate key.
- **Test order.** `POST /openApi/swap/v2/trade/order/test` takes Place order's parameters; the page says it returns a fake order and deducts no funds.
- **Demo prices differ.** At 02:03 Istanbul time on 6 November 2026, within a second, VST BTC-USDT last traded at 75,800.2 and live at 75,670.7.

Demo results test software, not a strategy; see [paper trading APIs](https://fincodelabs.com/en/blog/paper-trading-api).

## BingX WebSocket in Python: gzip, Ping and Pong

Every market-stream frame is GZIP-compressed binary, including the Ping the server sends about every 5 seconds; answer it with Pong.

Subscribe with `{"id": "...", "reqType": "sub", "dataType": "BTC-USDT@depth5"}` and decompress frames with `gzip.decompress`. In 12 seconds on 6 November 2026 the live futures stream sent an acknowledgement, 21 depth snapshots and two Pings 5 seconds apart. The FAQ's first suspect for dropped connections is a missing Pong; see [WebSocket heartbeat and reconnect](https://fincodelabs.com/en/blog/websocket-heartbeat-reconnect).

For integration work in general, see [Fincode's exchange API integration page](https://fincodelabs.com/en/services/exchange-api-integration); naming BingX here does not mean Fincode integrates it or has built on it.

## Frequently asked questions

### Where is the official BingX API documentation?

At bingx-api.github.io/docs-v3, which renders only in a browser; the older /docs site forwards some pages there (checked 6 November 2026).

### Is there an official BingX Python SDK?

The v3 documentation names none (6 November 2026); each endpoint page generates a requests-based Python sample.

### Why does the BingX API return 100413?

The X-BX-APIKEY header is missing (Null apiKey) or the key is wrong or deleted (Incorrect apiKey). The key is checked before the signature and timestamp.

### What is the BingX API rate limit?

Each private endpoint has a per-UID limit, such as 10 requests a second for USDT-M Place order, and market data allows 500 requests per 10 seconds per IP (read 6 November 2026). Account groups 2 and 3 lost their IP limits on 16 December 2025, per BingX's change log.

## Sources

- [BingX API Docs v3: Basic Information (timestamps, numbers as strings, rate-limit headers)](https://bingx-api.github.io/docs-v3/#/en/Quick%20Start/Basic%20Information)
- [BingX API Docs v3: Signature Authentication](https://bingx-api.github.io/docs-v3/#/en/Quick%20Start/Signature%20Authentication)
- [BingX API Docs v3: WebSocket Rules (hosts, GZIP, Ping and Pong, connection limits)](https://bingx-api.github.io/docs-v3/#/en/Quick%20Start/WebSocket%20Rules/WebSocket%20Rules)
- [BingX API Docs v3: FAQ (servers, keys, risk controls, HTTP 403)](https://bingx-api.github.io/docs-v3/#/en/Quick%20Start/FAQ)
- [BingX API Docs v3: Error Code Reference](https://bingx-api.github.io/docs-v3/#/en/Quick%20Start/Error%20Code%20Reference)
- [BingX API Docs v3: change log (account-group IP limits removed 16 December 2025)](https://bingx-api.github.io/docs-v3/#/en/changes/logs)
- [BingX API Docs v3: USDT-M Perp Futures symbols](https://bingx-api.github.io/docs-v3/#/en/Swap/Market%20Data/USDT-M%20Perp%20Futures%20symbols)
- [BingX API Docs v3: Place order (clientOrderId, precision truncation, limits)](https://bingx-api.github.io/docs-v3/#/en/Swap/Trades%20Endpoints/Place%20order)
- [BingX API Docs v3: Test Order](https://bingx-api.github.io/docs-v3/#/en/Swap/Trades%20Endpoints/Test%20Order)
- [BingX API Docs v3: Apply VST (demo trading host)](https://bingx-api.github.io/docs-v3/#/en/Swap/Trades%20Endpoints/Apply%20VST)
- [BingX API Docs, older site: Rate limit (UID limits, market-data IP limit only)](https://bingx-api.github.io/docs/#/en-us/swapV2/base-info.html#Rate%20limit)
- [BingX server time endpoint, live](https://open-api.bingx.com/openApi/swap/v2/server/time)
- [BingX USDT-M contract rules for BTC-USDT, live](https://open-api.bingx.com/openApi/swap/v2/quote/contracts?symbol=BTC-USDT)
- [RFC 4231: Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384 and HMAC-SHA-512](https://www.rfc-editor.org/rfc/rfc4231)
- [Capital Markets Law No. 6362, Article 99/A (Turkish, consolidated text)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the [editorial policy](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/bingx-api-python

Cite as: Fincode, "BingX API in Python: signing, VST demo and error codes", updated November 6, 2026, https://fincodelabs.com/en/blog/bingx-api-python
