Short answer: A Binance Futures testnet API key is created on Binance's Futures Demo Trading site: log in at demo.binance.com, open API Management and create a key. Binance's USD-M General Info page gives https://demo-fapi.binance.com as the testnet REST URL, while other official pages and the Python SDK's testnet example still name testnet.binancefuture.com; on 5 November 2026 both hosts served the same market.
Binance's own pages disagree about which futures test host is current. This guide compares them as read on 5 November 2026, tests both hosts that evening (19:02 to 20:07 UTC) and gives a Python client for signed USD-M orders. No key was created: signed calls went to a local mock, plus placeholder-key calls that both test hosts rejected.
This is a technical guide, not a recommendation of Binance, futures or any setting. Futures are leveraged, and a small price move can liquidate a position. Availability depends on your jurisdiction and Binance's terms. Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized, and Article 14 of Capital Markets Board (SPK) Communiqué III-35/B.2 bars leverage and derivatives on crypto assets listed on Turkish platforms, so nothing here describes a route for readers in Turkey.
How to get a Binance Futures testnet API key
You create it on the Futures Demo Trading site, not a separate testnet website. Binance's testing FAQ (updated 6 October 2025) gives these steps; the derivatives Quick Start (last modified 30 October 2026) points to the same site:
- Log in to Futures Demo Trading at
demo.binance.com/en/futures, or use Create if you have no demo account. - Open API Management from the account icon, or go to
demo.binance.com/en/my/settings/api-management. - Click Create API and name the key.
- Key type. USD-M General Info covers HMAC SHA256 and RSA keys; the USD-M WebSocket API page allows only Ed25519 keys for session login.
- The old website.
https://testnet.binancefuture.com/redirected (HTTP 301) todemo.binance.com/en/futures/BTCUSDTon 5 November 2026, so older tutorials are out of date; its API still answered.
The Spot Testnet is separate; see the Binance API Python guide.
Binance futures testnet URL: what the official pages say
USD-M General Info names demo-fapi.binance.com; the user data stream, order and WebSocket API pages and the SDK's TESTNET constants still name testnet.binancefuture.com.
| Source | Date shown | REST | WebSocket |
|---|---|---|---|
| USD-M General Info | Last modified 30 October 2026 | https://demo-fapi.binance.com, where most endpoints work | wss://demo-fstream.binance.com |
| User Data Streams and New Order pages | None seen | https://testnet.binancefuture.com | Not given |
| USD-M WebSocket API General Info | Last modified 30 October 2026 | Not given | wss://testnet.binancefuture.com/ws-fapi/v1 (WebSocket API) |
| Market Streams page | None seen | Not given | wss://stream.binancefuture.com |
binance-common 4.8.0, the SDK's constants | Published 29 October 2026 | TESTNET: testnet.binancefuture.com; DEMO: demo-fapi.binance.com | wss://fstream.binancefuture.com; no demo constant |
Both dated pages show 30 October 2026, so dates do not settle which text is newer. No page says the older host is retired or which endpoints the demo host lacks. Every listed host answered on 5 November 2026. Keep the host in one configuration value.
Are demo-fapi and testnet.binancefuture.com the same market?
In tests on 5 November 2026 they behaved as one market: the same trades, symbols and filters, and one rate-limit counter.
Public endpoints only (Python 3.11, requests):
import requests
DEMO, OLD, LIVE = ("https://demo-fapi.binance.com", "https://testnet.binancefuture.com",
"https://fapi.binance.com")
http = requests.Session()
def get(base, path, **params):
r = http.get(base + path, params=params, timeout=10)
r.raise_for_status()
return r.json(), r.headers.get("X-MBX-USED-WEIGHT-1M")
def one(items, key, value):
found = [x for x in items if x[key] == value]
if len(found) == 1:
return found[0]
raise LookupError(f"{value}: {len(found)} matches") # never pick the last of several
def btc_filter(base, kind):
info, _ = get(base, "/fapi/v1/exchangeInfo")
return one(one(info["symbols"], "symbol", "BTCUSDT")["filters"], "filterType", kind)
last, used_a = get(DEMO, "/fapi/v1/aggTrades", symbol="BTCUSDT", limit=1)
same, used_b = get(OLD, "/fapi/v1/aggTrades", symbol="BTCUSDT", fromId=last[0]["a"], limit=1)
print("same trade on both hosts:", last == same, "| used weight", used_a, "then", used_b)
print("LOT_SIZE step: demo", btc_filter(DEMO, "LOT_SIZE")["stepSize"],
"production", btc_filter(LIVE, "LOT_SIZE")["stepSize"])
Output at 20:00 UTC on 5 November 2026:
same trade on both hosts: True | used weight 20 then 40
LOT_SIZE step: demo 0.0001 production 0.001
The second count includes the first host's 20. Both hosts listed the same 743 symbols (942 on production) with identical BTCUSDT filters. This is one evening's observation, not a Binance statement.
Binance futures API in Python: signed orders on the demo host
Signed USD-M calls send the key in the X-MBX-APIKEY header with an HMAC SHA256 signature of the query string, per the General Info page.
This client refuses non-test hosts, checks credentials before any network call, rounds to the filters with Decimal and separates a refusal from an unknown outcome. Continuing from the first snippet, pass {k: btc_filter(DEMO, k) for k in ("LOT_SIZE", "PRICE_FILTER", "MIN_NOTIONAL")} as filters.
import hashlib, hmac, os, re, time
from decimal import Decimal, ROUND_DOWN
from urllib.parse import urlencode, urlsplit
import requests
BASE = os.environ.get("FUTURES_BASE", "https://demo-fapi.binance.com")
TEST_HOSTS = {"demo-fapi.binance.com", "testnet.binancefuture.com", "127.0.0.1"} # last: mock
CLIENT_ID = re.compile(r"^[\.A-Z\:/a-z0-9_-]{1,36}$") # from the New Order page
class Refused(Exception): pass # Binance said no: the order was not accepted
class Unknown(Exception): pass # the order may exist: look it up before any retry
def on_step(value, f, step="stepSize", lo="minQty", hi="maxQty"):
if isinstance(value, float):
raise TypeError("pass a str or Decimal, not a float")
try:
d = Decimal(value)
if not d.is_finite():
raise ValueError(f"not finite: {repr(value)}")
d = (d / Decimal(f[step])).to_integral_value(ROUND_DOWN) * Decimal(f[step])
except ArithmeticError: # not a number, or overflow
raise ValueError(f"not a usable number: {repr(value)}") from None
if not Decimal(f[lo]) <= d <= Decimal(f[hi]):
raise ValueError(f"{value} rounds outside {f[lo]} to {f[hi]}")
return format(d.normalize(), "f")
def signed(method, path, **params):
key, secret = os.environ.get("DEMO_API_KEY"), os.environ.get("DEMO_API_SECRET")
if not key or not secret: # before any network call
raise SystemExit("set DEMO_API_KEY and DEMO_API_SECRET")
if urlsplit(BASE).hostname not in TEST_HOSTS:
raise SystemExit(f"refusing {BASE}: not a test host")
params.update(timestamp=int(time.time() * 1000), recvWindow=5000)
query = urlencode(params)
query += "&signature=" + hmac.new(secret.encode(), query.encode(), hashlib.sha256).hexdigest()
try:
r = requests.request(method, f"{BASE}{path}?{query}",
headers={"X-MBX-APIKEY": key}, timeout=10)
except requests.RequestException as err:
raise Unknown(type(err).__name__) from err
if r.status_code >= 500 or r.status_code == 408:
raise Unknown(r.status_code)
try:
body = r.json()
except ValueError: # an HTML page, say
outcome = Unknown if r.status_code < 400 else Refused # 2xx: may be accepted
raise outcome(r.status_code, None, r.text[:30]) from None
if r.status_code >= 400:
err = body if isinstance(body, dict) else {}
if err.get("code") in (-1006, -1007):
raise Unknown(err["code"])
raise Refused(r.status_code, err.get("code"), err.get("msg"))
return body
def limit_order(filters, side, qty, price, cid, test=True):
if not isinstance(cid, str) or not CLIENT_ID.fullmatch(cid):
raise ValueError(f"client order id {repr(cid)} does not match the pattern")
q = on_step(qty, filters["LOT_SIZE"])
p = on_step(price, filters["PRICE_FILTER"], "tickSize", "minPrice", "maxPrice")
if Decimal(q) * Decimal(p) < Decimal(filters["MIN_NOTIONAL"]["notional"]):
raise ValueError("notional below the minimum")
return signed("POST", "/fapi/v1/order/test" if test else "/fapi/v1/order",
symbol="BTCUSDT", side=side, type="LIMIT", timeInForce="GTC",
quantity=q, price=p, newClientOrderId=cid)
Output at 20:38 UTC on 5 November 2026. Only line two reached Binance (the demo host, malformed key placeholder); lines four, five and seven used a local mock that checks signatures and mimics documented answers:
no key set: stopped: set DEMO_API_KEY and DEMO_API_SECRET
real demo host, placeholder key: Refused (401, -2014, 'API-key format invalid.')
production host: stopped: refusing https://fapi.binance.com: not a test host
mock, order: {'orderId': 1001, 'clientOrderId': 'demo-1', 'status': 'NEW', 'price': '60000', 'origQty': '0.0023'}
mock, same id again: Refused (400, -4116, 'clientOrderId is duplicated')
qty 1e400: ValueError: 1e400 rounds outside 0.0001 to 1000
mock timeout: Unknown ('ReadTimeout',)
- Duplicates. A repeated open client order ID gets
-4116, per the USD-M error page; an empty ID, or one ending in a newline thatre.matchwith$would pass, never leaves the process. - Bad input.
"1e400"and"1e-400"fail after rounding instead of reaching the wire as a huge number or zero. A float,nan,"1e999999", an HTML 403, an error body withoutcodeand a wrong secret also failed cleanly. - Unknown outcomes. A 503, a 408 (General Info: backend timeout), a 200 with an HTML body, a read timeout,
-1007and a refused connection raisedUnknown: look the order up by client ID before sending again.
Both hosts answered POST /fapi/v1/order/test with -2014 and an unknown path with 404; SDK 18.2.0 documents it as a test order not sent to the matching engine. See Binance API error codes.
Binance futures connector for Python: which package is current
The current official package is binance-sdk-derivatives-trading-usds-futures; the older binance-futures-connector calls itself deprecated on PyPI.
| Package | Version (uploaded) | Python | Status |
|---|---|---|---|
binance-sdk-derivatives-trading-usds-futures | 18.2.0 (29 October 2026) | 3.11 to 3.15 | Current |
binance-futures-connector | 4.2.0 (30 April 2026) | 3.7 or later | Deprecated |
Three details in the 18.2.0 README trap a copy-paste: the REST testnet example uses the TESTNET constant, not DERIVATIVES_TRADING_USDS_FUTURES_REST_API_DEMO_URL; its Futures Testnet link points to the Spot Testnet, testnet.binance.vision; and the streams example imports one constant but passes another, a NameError. The SDK was not run; constants come from its wheel files.
What the Binance futures demo cannot show you
The demo host tests signing and order handling, but its filters, symbols, limits and stream paths differ from production; see also paper trading API.
| Item | Demo (both hosts) | Production |
|---|---|---|
| LOT_SIZE step and minimum | 0.0001 | 0.001 |
| PRICE_FILTER range (tick 0.10 on both) | 261.10 to 809,484 | 556.80 to 4,529,764 |
| PERCENT_PRICE band | 0.95 to 1.05 | 0.97 to 1.03 |
| MAX_NUM_ORDERS | 10,000 | 200 |
| MIN_NOTIONAL | 50 | 50 |
| REQUEST_WEIGHT per minute | 6,000 | 2,400 |
Hypothetical example. A bot tuned on the demo host sends 0.0015 BTC: valid for the demo step of 0.0001, rejected by production's 0.001. Reading filters at startup avoids this.
- Stream paths. The USD-M changelog added
/public,/marketand/privatepaths on 5 March 2026 and on 2 April set 23 April 2026 to decommission legacy URLs. At 20:02 UTC on 5 November 2026, production's legacywss://fstream.binance.com/ws/btcusdt@markPriceand@aggTradesent nothing in 20 seconds while@bookTickerstreamed; markPrice streamed on the demo host's legacy path and production's/market/ws/path. See the Binance WebSocket guide.
Historical files for replay tests
A Python check downloaded the daily USD-M BTCUSDT 1h kline file for 4 November 2026 from data.binance.vision (Last-Modified 08:03 GMT on 5 November) with its .CHECKSUM, compared the SHA-256 and read the CSV without printing prices. Output at 20:01 UTC that day:
checksum ok: True
['open_time', 'open'] 24 rows; open_time digits: 13
Spot files use microseconds from 1 January 2025, per the README, which gives no unit for futures; this file used milliseconds. The dataset terms, last updated 26 August 2026, allow only non-commercial use under CC BY-NC-SA 4.0 and exclude commercial trading bot platforms. See the Binance historical data guide.
Binance futures leverage and liquidation risk
Futures multiply gains and losses, and a liquidation can take a position's whole margin.
This guide suggests no leverage, margin mode or strategy. The liquidation price calculator shows on hypothetical numbers how leverage moves a liquidation price, and perpetual futures and funding rate explains funding.
For integration work in general, see exchange API integration; naming Binance here does not mean Fincode integrates it or has built on it.
Frequently asked questions
Is the Binance futures testnet free?
Binance's testing FAQ (updated 6 October 2025) says you can test your use cases for free. Access depends on your jurisdiction and Binance's terms.
Does testnet.binancefuture.com still work?
Its REST and WebSocket APIs answered on 5 November 2026, the REST API serving the same market as demo-fapi.binance.com, but its website redirects to demo.binance.com.
Can I use my Spot Testnet key for futures?
No official page read on 5 November 2026 says so. Binance's FAQ sends futures users to demo.binance.com for keys.
Why does the futures testnet return -2014 or -2015?
-2014 means a malformed key; both hosts returned it for a placeholder key on 5 November 2026. -2015 means the key, IP or permission was rejected, so check that the key came from the demo site.
Sources
- Binance Derivatives: USD-M Futures General Info (testnet base URLs, last modified 30 October 2026) (opens in a new tab)
- Binance Derivatives: Quick Start (Futures Demo Trading page, last modified 30 October 2026) (opens in a new tab)
- Binance Derivatives: USD-M User Data Streams (testnet.binancefuture.com) (opens in a new tab)
- Binance Derivatives: USD-M New Order (client order ID pattern) (opens in a new tab)
- Binance Derivatives: USD-M WebSocket Market Streams (URL paths) (opens in a new tab)
- Binance Derivatives: Change log (USD-M WebSocket entries of 5 March and 2 April 2026) (opens in a new tab)
- Binance Derivatives: USD-M error codes (-1007, -2014, -2015, -4116) (opens in a new tab)
- Binance FAQ: How to Test My Functions on Binance Testnet (updated 6 October 2025) (opens in a new tab)
- Binance Derivatives: USD-M WebSocket API General Info (testnet URL, Ed25519 session login, last modified 30 October 2026) (opens in a new tab)
- PyPI: binance-sdk-derivatives-trading-usds-futures (official USD-M SDK) (opens in a new tab)
- PyPI: binance-futures-connector (deprecated) (opens in a new tab)
- binance-public-data: README (Spot timestamps in microseconds from 1 January 2025) (opens in a new tab)
- Binance Vision dataset terms (last updated 26 August 2026) (opens in a new tab)
- Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish) (opens in a new tab)
- Resmî Gazete: SPK Communiqué III-35/B.2 on crypto-asset service providers, Article 14 (Turkish) (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.