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.
| 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 and API key security for 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.
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
tradeMinQuantityarrived as the JSON number 0.0001;parse_float=Decimalkeeps 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.
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×tamp=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
A1anda1collide; see client order ID.
Other schemes: API signature generation; Fincode's 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.
| 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 and exponential backoff with jitter.
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.
| 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.
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 (adjustType0) 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/testtakes 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.
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.
For integration work in general, see Fincode's exchange API integration page; 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) (opens in a new tab)
- BingX API Docs v3: Signature Authentication (opens in a new tab)
- BingX API Docs v3: WebSocket Rules (hosts, GZIP, Ping and Pong, connection limits) (opens in a new tab)
- BingX API Docs v3: FAQ (servers, keys, risk controls, HTTP 403) (opens in a new tab)
- BingX API Docs v3: Error Code Reference (opens in a new tab)
- BingX API Docs v3: change log (account-group IP limits removed 16 December 2025) (opens in a new tab)
- BingX API Docs v3: USDT-M Perp Futures symbols (opens in a new tab)
- BingX API Docs v3: Place order (clientOrderId, precision truncation, limits) (opens in a new tab)
- BingX API Docs v3: Test Order (opens in a new tab)
- BingX API Docs v3: Apply VST (demo trading host) (opens in a new tab)
- BingX API Docs, older site: Rate limit (UID limits, market-data IP limit only) (opens in a new tab)
- BingX server time endpoint, live (opens in a new tab)
- BingX USDT-M contract rules for BTC-USDT, live (opens in a new tab)
- RFC 4231: Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384 and HMAC-SHA-512 (opens in a new tab)
- Capital Markets Law No. 6362, Article 99/A (Turkish, consolidated text) (opens in a new tab)
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.