— Contents 17 sections
  1. 01 How the Binance API works
  2. 02 Before you start: keys, permissions, and one auth helper
  3. 03 Getting the current price from the Binance API
  4. 04 Fetching Binance candlestick data (klines)
  5. 05 Checking your Binance balance in Python
  6. 06 Placing a market order with the Binance API
  7. 07 Placing a limit order with the Binance API (and where stop-limit fits)
  8. 08 Canceling orders and listing open Binance orders
  9. 09 Checking Binance order status and trade history
  10. 10 Getting a Binance deposit address (and deposit history)
  11. 11 Withdrawing funds with the Binance API
  12. 12 Moving funds between Binance wallets (spot, funding, futures)
  13. 13 Binance WebSocket streams: live prices in Python
  14. 14 Binance API rate limits and common errors
  15. 15 Running your Binance API code as a bot
  16. 16 Conclusion and next steps
  17. 17 Frequently asked questions

The official Binance API documentation is a reference: complete, accurate, and hard to use when all you want is the five lines of Python that place an order or fetch your balance. This guide is the other half — for each common operation, what the docs specify in one or two sentences, then the shortest code that actually works, then the mistakes that cost people time. Everything here was verified against the current official documentation and, where the endpoint allows it, against the live API. The scope is spot trading plus the wallet operations (deposits, withdrawals, transfers) a typical account needs.

How the Binance API works

A person standing where four paths meet, each path leading to a different door marked with a coin, a safe, a chart, or a lightning-bolt icon

Binance does not have one API. The developer portal fronts documentation for more than thirty products, and several of them expose a same-named operation on a different host with different parameters and different rate limits. Most confusion with “the Binance API” starts with reading the wrong tree. These are the main families, each with its own documentation tree:

API familyHost and prefixWhat it isIn this guide
Spotapi.binance.com + /api/v3buying and selling actual coins: prices, orders, balances, streamsYes
Walletapi.binance.com + /sapi/v1moving funds: deposits, withdrawals, transfers between walletsYes
Futures (USDⓈ-M)fapi.binance.com + /fapi/v1USDT/USDC-margined perpetuals and futures, with leverage and positionsNo — separate tree
Futures (COIN-M)dapi.binance.com + /dapi/v1coin-margined contractsNo
Marginapi.binance.com + /sapi/v1/marginborrowing against collateral to trade spot with leverageNo
Optionseapi.binance.com + /eapi/v1European-style optionsNo

This guide covers the first two — the spot API plus the wallet operations every account needs. The futures APIs are complete, separate trees: different hosts, different parameters (even the time-in-force values differ), a different demo environment, and a position/leverage model that deserves its own walkthrough — mixing their values into spot code is exactly the confusion this guide exists to prevent, so futures belongs in its own guide rather than a shared chapter here.

Two traps worth naming now: wallet and margin share the spot host, so their calls look like “part of the spot API” — they are separate trees with separate documentation and rate-limit accounting. And Binance.US is a separate company with its own API and documentation (docs.binance.us) — everything in this guide is for the global exchange.

Two transports matter for most users. REST is request/response over HTTPS: one call, one answer — right for orders, balances and anything occasional. WebSocket streams push data to you continuously — right for live prices, where polling REST would burn your rate limit. Requests that touch your account are signed: you send a timestamp and an HMAC-SHA256 signature computed with your secret key (Binance now recommends Ed25519 keys, which sign with a private key instead — the request shape is the same). Rate limiting is weight-based per IP: every endpoint costs a documented weight against a budget of 6,000 per minute on the spot tree.

Before you start: keys, permissions, and one auth helper

An open carrying case holding a shield with a golden key emblem next to a hand-press seal stamp

You need a Binance account with identity verification and two-factor authentication, and at least one deposit before the account can create API keys — if you don’t have one, our Binance review covers registration. Create the key under Profile → [Account] → [API Management], enable only what you need, and keep withdrawals off unless a recipe below explicitly requires them. One rule breaks more first attempts than everything else combined: a system-generated (HMAC) key with unrestricted IP access is read-only in practice — to trade, either IP-restrict the key or use a self-generated Ed25519 key. The full walkthrough, including the testnet, is in our Binance trading bot guide.

Two diagnostics worth knowing before you debug anything the hard way:

  • Ask Binance what your key can do. GET /sapi/v1/account/apiRestrictions returns the actual toggles on your key (enableReading, enableSpotAndMarginTrading, enableWithdrawals, permitsUniversalTransfer, ipRestrict and more). When an operation fails with a permission error, this one call replaces guesswork. It is a /sapi endpoint, so production only.
  • Check your signing code offline. The official docs publish a worked example with a sample secret key and the exact expected signature. If your helper reproduces that signature, your signing is correct — no API key or network needed. That is the cheapest fix for -1022 Invalid signature (the published vector is HMAC-only; it cannot check an Ed25519 setup).

The docs’ sample pair is safe to print precisely because it is not a real key — real keys never belong in a code block:

payload:  symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000&timestamp=1499827319559
secret:   NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j
expected: c8db56825ae71d6d79447849e617115f4a920fa2acdcab2b053c4b2838bd6b71

All signed examples below share this helper. Point BASE at the testnet while you practice and at production when you go live:

import hmac, hashlib, time, os
import requests

BASE = "https://testnet.binance.vision/api"   # production: https://api.binance.com/api
SAPI = "https://api.binance.com/sapi"         # wallet tree — production only, no sandbox
# Keys must match the host they are used against: the testnet pair (from
# testnet.binance.vision) signs the signed_* calls while BASE points at the
# testnet; the sapi_* helpers always hit production, so they sign with the
# production pair (only needed once you reach the deposit/withdraw recipes).
KEY = os.environ["BINANCE_KEY"]
SECRET = os.environ["BINANCE_SECRET"]
PKEY = os.environ.get("BINANCE_PROD_KEY", "")
PSECRET = os.environ.get("BINANCE_PROD_SECRET", "")

def _signed(method, base, path, **params):
    key, secret = (PKEY, PSECRET) if base == SAPI else (KEY, SECRET)
    params["timestamp"] = int(time.time() * 1000)
    query = "&".join(f"{k}={v}" for k, v in params.items())
    sig = hmac.new(secret.encode(), query.encode(), hashlib.sha256).hexdigest()
    url = f"{base}{path}?{query}&signature={sig}"
    return requests.request(method, url, headers={"X-MBX-APIKEY": key}).json()

def signed_get(path, **params):    return _signed("GET", BASE, path, **params)
def signed_post(path, **params):   return _signed("POST", BASE, path, **params)
def signed_delete(path, **params): return _signed("DELETE", BASE, path, **params)
def sapi_get(path, **params):      return _signed("GET", SAPI, path, **params)
def sapi_post(path, **params):     return _signed("POST", SAPI, path, **params)

The signed_* helpers hit the spot tree (switchable to the testnet). The sapi_* helpers hit the wallet tree, which exists on production only — so the deposit, withdrawal and transfer recipes below sign with your production pair (BINANCE_PROD_KEY / BINANCE_PROD_SECRET above), not a testnet one, even while BASE still points at the testnet. The helper signs with an HMAC key (the system-generated kind). The Ed25519 route needs two changes, not one: sign with your private key and base64-encode the result, then percent-encode that signature before putting it in the query string — see the Ed25519 signed-request example in the official docs. As a bonus, the permission self-check from above is now a one-liner: sapi_get("/v1/account/apiRestrictions").

Getting the current price from the Binance API

A person kneeling with a magnifying glass to inspect a large coin resting on a small gauge

Docs say: GET /api/v3/ticker/price returns the latest price for a symbol. Security type NONE — no API key, no signature. Permission: none. This even works on a dedicated keyless host, data-api.binance.vision, before you have an account.

import requests
price = requests.get("https://data-api.binance.vision/api/v3/ticker/price",
                     params={"symbol": "BTCUSDT"}).json()
print(price)   # {'symbol': 'BTCUSDT', 'price': '...'}

Gotchas: REST symbols are uppercase with no separator. btcusdt or BTC/USDT fails with -1100 Illegal characters found in parameter 'symbol' — the legal-range regex in that message excludes lowercase letters outright (-1121 Invalid symbol is the different error you get for a well-formed pair that does not exist). WebSocket stream names are the exact opposite, lowercase. And don’t poll this in a loop for a live feed: the rate-limit error text itself tells you to use WebSocket streams instead — see the streams section below. Testnet: works (https://testnet.binance.vision/api). Reference: ticker price

Fetching Binance candlestick data (klines)

Docs say: GET /api/v3/klines returns OHLCV candles for a symbol and interval (1s1M, case-sensitive), up to 1000 per call. No key needed. Permission: none.

rows = requests.get("https://data-api.binance.vision/api/v3/klines",
                    params={"symbol": "BTCUSDT", "interval": "1h", "limit": 100}).json()
# each row is a list: [0] open time, [1] open, [2] high, [3] low, [4] close,
# [5] volume, [6] close time, ... (OHLCV values are strings; the two
# timestamps and the trade count are numbers)
closes = [float(r[4]) for r in rows]

Gotchas: the response is an array of arrays, not objects — index positions matter. limit is capped at 1000; for more history, page with startTime/endTime (interpreted in UTC) instead of raising the limit. And the most recent row is a still-forming candle — using it as a closed candle silently corrupts backtests. Drop the last row, or use the WebSocket kline stream where the k.x field says “is this kline closed?”. Testnet: works. Reference: klines

Checking your Binance balance in Python

A hand placing coins into one side of a two-compartment wallet, with a second stack of coins behind a clear flap

Docs say: GET /api/v3/account returns the spot wallet — every asset with free and locked amounts, plus account flags and commission rates. Signed request. Permission: [Enable Reading] is enough — the right endpoint for a read-only monitoring key.

account = signed_get("/v3/account", omitZeroBalances="true")
for b in account["balances"]:
    print(b["asset"], b["free"], b["locked"])

Gotchas: free is what you can spend; locked is reserved by your own open orders — size orders off free, not the total, or you’ll hit “Account has insufficient balance”. If a deposit “isn’t showing up”, it is usually in another wallet: this endpoint is spot only, and funds in the funding or futures wallets are invisible here (see the transfer recipe). A -1021 timestamp error means your machine’s clock drifts outside recvWindow, the validity window on a signed request (5,000 ms by default) — sync your system clock (NTP) rather than widening the window. Testnet: works, and test accounts come pre-funded with virtual assets. Reference: account information

Placing a market order with the Binance API

A hand feeding a coin into a machine that joins two puzzle pieces together

Docs say: POST /api/v3/order with type=MARKET buys or sells immediately. You give either quantity (amount of the base asset, e.g. BTC) or quoteOrderQty (amount of the quote asset to spend, e.g. USDT) — exactly one of the two. Permission: [Enable Spot & Margin Trading] — and remember the unrestricted-IP rule from the setup section.

# spend exactly 20 USDT at market price
order = signed_post("/v3/order", symbol="BTCUSDT", side="BUY",
                    type="MARKET", quoteOrderQty="20")
print(order["orderId"], order["status"])

Gotchas: quoteOrderQty is the beginner-friendly form — the docs state it does not break the LOT_SIZE filter, so “spend a round 20 USDT” cannot be rejected for quantity-step reasons the way a hand-computed BTC quantity can. Keep the value above the symbol’s minimum notional (about 5 USDT on BTC/USDT). To validate an order without executing it, send the same parameters to POST /api/v3/order/test. If every order fails with -2015 even though trading “looks enabled”, re-read the key rules above — that is the read-only trap, not your code. Testnet: works (fills against the testnet’s own order book, so prices differ from production). Reference: new order

Placing a limit order with the Binance API (and where stop-limit fits)

Docs say: type=LIMIT requires price, quantity and timeInForce. Spot supports exactly three time-in-force values: GTC (rests until canceled), IOC (fill what you can, cancel the rest), FOK (fill fully or not at all). Permission: [Enable Spot & Margin Trading].

order = signed_post("/v3/order", symbol="BTCUSDT", side="BUY",
                    type="LIMIT", timeInForce="GTC",
                    quantity="0.0002", price="60000.00")

Gotchas: price must sit on the symbol’s tickSize grid and quantity on its stepSize grid (from GET /api/v3/exchangeInfo), and price × quantity must clear minNotional — the three filter rejections behind most -1013 errors. timeInForce belongs to LIMIT orders only; sending it on a MARKET order fails. A post-only order — one that is rejected rather than filling as a taker — is its own type on spot (LIMIT_MAKER), not a time-in-force. The stop-loss / take-profit variants (STOP_LOSS_LIMIT, TAKE_PROFIT_LIMIT) take the LIMIT shape above plus a stopPrice; check the order types reference before using them. If you read about GTX or GTD somewhere, that was the futures tree — they do not exist on spot. Testnet: works — a resting limit order is ideal practice material for the next recipe. Reference: new order

Canceling orders and listing open Binance orders

Docs say: GET /api/v3/openOrders lists working orders; DELETE /api/v3/order cancels one by orderId; DELETE /api/v3/openOrders cancels everything on a symbol. Permission: listing needs only reading; canceling needs trading — a read-only monitor key can see open orders but not pull them.

open_orders = signed_get("/v3/openOrders", symbol="BTCUSDT")
for o in open_orders:
    signed_delete("/v3/order", symbol="BTCUSDT", orderId=o["orderId"])

Gotchas: always pass symbol to openOrders — without it the call costs weight 80 instead of 6 and scans every symbol. Canceling something that already filled returns -2011 or -2013 Order does not exist; in automation, treat both as benign and re-query state instead of retrying. If you only want to cancel when nothing has executed yet, send cancelRestrictions=ONLY_NEW and treat its rejection as information, not failure. Testnet: works. Reference: cancel order

Checking Binance order status and trade history

Docs say: GET /api/v3/order fetches one order by id; GET /api/v3/allOrders pages through past orders; GET /api/v3/myTrades returns your actual executions with commission fields. For the two history endpoints, startTime to endTime cannot span more than 24 hours. Permission: reading, for all three.

trades = signed_get("/v3/myTrades", symbol="BTCUSDT", limit=20)
for t in trades:
    print(t["price"], t["qty"], t["commission"], t["commissionAsset"], t["isMaker"])

Gotchas: orders tell you what you asked for; myTrades tells you what actually happened — fills, prices and fees. Any profit-and-loss or fee accounting must come from myTrades. The 24-hour window limit on history queries is documented and hard (-1127 when exceeded) — page in daily windows. This endpoint family is also your safety net: when an order request times out (-1007) or returns a 5XX, the execution status is unknown, so query the order state before any retry — blind retries double-buy. Testnet: works, but the testnet wipes history in its roughly monthly resets. Reference: account trade list

Getting a Binance deposit address (and deposit history)

A person carrying a coin across a small bridge from a safe toward two open pouches

Docs say: GET /sapi/v1/capital/deposit/address returns a deposit address for a coin on a chosen network; GET /sapi/v1/capital/deposit/hisrec lists deposits (status 0 pending, 1 success). The list of coins and their networks comes from GET /sapi/v1/capital/config/getall. Permission: reading — notably not withdrawal permission. Fetching an address is a read; don’t enable withdrawals “to handle deposits”.

addr = sapi_get("/v1/capital/deposit/address", coin="USDT", network="BSC")
print(addr["address"], addr.get("tag", ""))

Gotchas: always pass network explicitly. If you omit it, you get the coin’s default network — and funds sent on a mismatched chain are the classic irreversible mistake. Enumerate valid networks from capital/config/getall rather than hardcoding. Errors like -4044 (region) or -4042 are account or asset conditions, not bugs in your request. Testnet: not available — /sapi is not served on the Spot Test Network at all. This recipe runs on production only. Reference: deposit address

Withdrawing funds with the Binance API

Docs say: POST /sapi/v1/capital/withdraw/apply submits a withdrawal (coin, address, amount, plus network). The response contains only an id — completion is confirmed by polling GET /sapi/v1/capital/withdraw/history, never by the response itself. Permission: [Enable Withdrawals], which Binance only allows on a key with mandatory IP restriction.

A safety frame before any code: withdrawal is the one permission that lets a leaked key move funds out of your account. A trading bot never needs it — keep it off the bot’s key (this is the same rule as in our bot guide). If you automate withdrawals at all, use a dedicated key, IP-restricted, against a whitelisted address, and start with the smallest amount the asset allows.

result = sapi_post("/v1/capital/withdraw/apply",
                   coin="USDT", network="BSC",
                   address="0xYourWhitelistedAddress", amount="10")
print(result)   # {"id": "..."} — now poll withdraw/history for completion

Gotchas: there is no sandbox for this/sapi does not exist on the testnet, so the first real run is on production with real assets; that is why the smallest-amount rule is not optional. -4035 means the address is not on your account’s withdrawal whitelist (an account setting, not an API parameter); -4013/-4014 are 2FA and recent-login conditions; none of these are fixed by changing code. Withdrawal fees vary by network — check them in capital/config/getall before choosing a chain. Testnet: not available. Reference: withdraw

Moving funds between Binance wallets (spot, funding, futures)

Docs say: POST /sapi/v1/asset/transfer moves an asset between your own wallets. The type parameter encodes direction as FROM_TO: MAIN_UMFUTURE is spot → USDⓈ-M futures, FUNDING_MAIN is funding → spot, and so on (MAIN = spot, UMFUTURE/CMFUTURE = futures, FUNDING = funding wallet, MARGIN = cross margin). Permission: the key needs Permits Universal Transfer — a separate toggle from trading. Funds stay inside your account, so this is far less dangerous than withdrawal, but it is still fund movement: leave it off a pure trading key.

moved = sapi_post("/v1/asset/transfer",
                  type="FUNDING_MAIN", asset="USDT", amount="50")
print(moved)   # {"tranId": ...}

Gotchas: this endpoint answers the eternal “why does my bot see a zero balance” — the money is sitting in the funding or futures wallet while your code reads spot. FUNDING_MAIN / MAIN_FUNDING is the pair most people actually need. Reversed direction shows up as -5003 You don't have this asset. -5012 means the transfer is pending, not failed — check the history side (GET /sapi/v1/asset/transfer) before retrying, or you may move funds twice. Testnet: not available. Reference: universal transfer

Binance WebSocket streams: live prices in Python

A faucet pouring a continuous stream of colorful data droplets into an open laptop while a person watches

Docs say: market streams live at wss://stream.binance.com:9443/ws/ + stream name — for example btcusdt@kline_1m for candles or btcusdt@bookTicker for best bid/ask. No API key needed. Stream names are lowercase. Permission: none for market data.

# pip install websocket-client
import json, websocket

def on_message(ws, msg):
    k = json.loads(msg)["k"]
    if k["x"]:                      # candle is closed — safe to act on
        print("closed 1m candle:", k["c"])

ws = websocket.WebSocketApp(
    "wss://stream.binance.com:9443/ws/btcusdt@kline_1m",
    on_message=on_message)
ws.run_forever()

Gotchas: lowercase here, uppercase on REST — the single most confusing asymmetry in the whole API. The k.x boolean (“is this kline closed?”) is the only correct trigger for candle-close logic. Connections are terminated at the 24-hour mark by design and the server pings every 20 seconds, so production code must answer pings and reconnect automatically.

One correction that will save you an afternoon: the listenKey method for user data streams is dead. Older tutorials (and pre-2026 SDK samples) teach POST /api/v3/userDataStream → connect with the returned listenKey → keepalive every 30 minutes. Those endpoints were retired on 2026-02-20 and now return a bare HTTP 410 with no JSON error — verified live while writing this guide. The current flow is a userDataStream.subscribe request on the WebSocket API (wss://ws-api.binance.com/ws-api/v3) — the documented flow authenticates with an Ed25519 key, after which balance and order events (executionReport, outboundAccountPosition) arrive on the same connection. Testnet: market streams and the new subscribe flow both work (wss://stream.testnet.binance.vision/ws). Reference: WebSocket streams · user data stream

Binance API rate limits and common errors

A dial-operated gate with a striped barrier releasing balls one at a time into an evenly spaced line

The spot tree budgets 6,000 request weight per minute per IP (older articles still say 1,200 — outdated). Every response reports your usage in the X-MBX-USED-WEIGHT-1M header. Exceeding the budget returns HTTP 429; ignoring 429s earns an automatic IP ban (418) that scales from 2 minutes to 3 days. Wallet (/sapi) endpoints keep separate, per-endpoint budgets — another reason not to treat the two trees as one API. The errors this guide’s recipes mention most:

CodeMeaningUsual fix
-1021timestamp outside recvWindowsync your clock (NTP) or offset from GET /api/v3/time
-1022invalid signatureverify your helper against the official test vector offline
-2015key/IP/permission rejectedthe read-only trap; check apiRestrictions on production
-1013 / -2010filter failure / order rejectedround to tickSize/stepSize, clear minNotional, check balance
-1100illegal characters in a parameteruppercase, unseparated symbols for REST (BTCUSDT); stream names are lowercase
-1121invalid symbolthe pair does not exist or is delisted — check exchangeInfo
-1127history window too bigpage allOrders/myTrades in 24-hour windows
HTTP 410retired endpoint (listenKey flow)use userDataStream.subscribe over the WebSocket API

Running your Binance API code as a bot

The recipes above are the pieces; a bot is what runs them unattended — a loop of read → decide → order, with reconnect logic, careful retries and a kill switch. That infrastructure, from key setup through the testnet to the first automated order, is the subject of our step-by-step Binance trading bot guide.

Conclusion and next steps

The pattern repeats across every operation: find the endpoint’s tree (spot /api or wallet /sapi), check what permission the key really needs, keep the request minimal, and read the error table before assuming your code is wrong. Everything on the spot tree can be rehearsed risk-free on the Spot Test Network; everything that moves funds cannot — which is exactly where the smallest-amount rule and the permission hygiene in this guide matter most. And whichever route you take, the orders and transfers your code submits are your own responsibility — Binance’s own SDK disclaimer says as much in so many words. If you don’t have a Binance account yet, our Binance review covers registration, fees and features in detail.

All endpoints, parameters, weights and error texts verified against the official Binance documentation on 2026-08-17, with live checks where the endpoint permits. Specifications change — the reference links in each section point at the current official source.

Frequently asked questions

What is the Binance API?

An interface that lets your own programs talk to Binance directly: read prices, check balances, place orders and move funds with code instead of the app. Binance runs many product-specific APIs; this guide covers the spot API and the wallet endpoints a typical user needs.

Is the Binance API free to use?

There is no separate charge for API access. Orders placed through the API pay the same spot trading fees as orders placed in the app, and public market data requires no account at all.

Can I test Binance API code without real money?

Yes, for everything except moving funds. The Spot Test Network (testnet.binance.vision) serves all /api endpoints — prices, orders, balances, streams — with virtual funds and a GitHub login. Deposit, withdrawal and transfer endpoints (/sapi) have no sandbox and only exist on production.

Does this guide apply to Binance.US?

No. Binance.US is a separate company with its own API and its own documentation (docs.binance.us). The endpoints, hosts and values in this guide are for the global exchange.

Why does my tutorial's listenKey code return HTTP 410?

The listenKey flow for user data streams was retired on 2026-02-20 and the endpoints now return 410 Gone. The current method is a userDataStream.subscribe request on the WebSocket API, which the documented flow authenticates with an Ed25519 key. Most older tutorials and SDK samples still show the retired flow.

Which Python library should I use for the Binance API?

The official SDK for spot is binance-sdk-spot (the older binance-connector is its previous generation, and python-binance is a popular community project). The examples here use plain requests so they work anywhere and show exactly what any SDK does underneath.