# How Binance API rate limits are counted per IP address

> Binance Spot API gives every IP address 6,000 request weight per minute — not the 1,200 that many libraries still quote. Order limits are counted separately, per account. Here's how the weight math works, and what triggers 429 and 418.

- Source: https://papaproxy.net/blog/binance-api-rate-limits.php
- Published: 2026-07-31 (updated 2026-08-06)
- Author: Alex Young
- Category: Limits & performance · PapaProxy.net Blog

---

## Key takeaways

- The Spot API budget is 6,000 request weight per minute per IP — raised from 1,200 on August 25, 2023; libraries like python-binance still quote the old number.
- Binance counts weight, not requests, across multiple fixed intervals: a short window can reset while a longer one stays exhausted, so track every limit `exchangeInfo` returns.
- Weight is tracked per IP; order limits are tracked per account — a 429 can mean either, and the daily order cap counts unfilled orders only.
- Route public data through the official offload paths first: WebSocket streams cost zero weight, and `data-api.binance.vision` is Binance's recommended host for market-data-only services.
- Never hardcode limits: reconcile against `X-MBX-USED-WEIGHT-*` headers and refresh ceilings from `/api/v3/exchangeInfo` at runtime.
- Workers sharing one IP need a central limiter — atomic weight reservation in shared storage before each request; a per-worker throttler like ccxt's can't see its neighbors.
- Our own benchmark confirmed the budget in practice: 94% of the 6,000 weight/min on 16 connections with zero errors, first 429 at 24 connections; three addresses in parallel reached 17,066 weight/min with no 429s — per-address budgets are independent and add up linearly.

## How request weight is counted per IP

Binance doesn't count your requests — it counts their weight. Every endpoint has a price attached to it, and the request weight system adds those prices into a single bucket tied to your IP address. A light call, like fetching the price of one symbol, costs 1 or 2 weight. A heavy call that returns data for every symbol on the exchange can cost dozens of times more. The weight limit per minute applies to that shared bucket, which means every script, bot, and connection running on your address is drawing from the same budget.

This changes how you should think about throttling. Counting "requests per minute" gives you the wrong picture, because a hundred heavy calls can exhaust a budget that three thousand light ones would barely touch. It also explains why creating extra API keys doesn't help: the limit follows the IP, not the key, so every key on the same server still drinks from one bucket.

You don't have to fly blind, though: every response includes an `X-MBX-USED-WEIGHT-1M` header showing how much of the current window you've already spent. One caveat about how that window works. Binance evaluates multiple fixed rate-limit intervals where applicable — the counter switches at the interval boundary rather than rolling continuously, several intervals can apply at once, and a shorter window can reset while a longer one remains exhausted. So inspect all the limits returned for the API you use, not only the `1M` header. A bot that reconciles its throttle against these values substantially reduces its chances of hitting 429 under a steady workload; a bot that counts its own requests against a number from a tutorial is the one that usually ends up collecting them.

## Why the 1,200 weight per minute figure is out of date

The short answer is that Binance raised the limit years ago, and half the internet never noticed. On August 25, 2023, the per-IP budget on the Spot API grew fivefold, from the old figure to 6,000 weight per minute. This wasn't a quiet change — Binance announced it officially, and the current number is confirmed in every source that matters: the `exchangeInfo` samples in the official docs show 6,000, and even the text of the `-1003` error now says "current limit is 6000 request weight per 1 MINUTE."

So why does the old number keep coming up? Because it survives in exactly the places developers actually read. The python-binance documentation still lists the old per-minute value on its overview page, and the GitHub rate-limit discussions that rank well in search were written against the pre-2023 ceiling. The practical cost of trusting them is real: a throttler tuned to the old number leaves 80% of your actual budget unused, and a tutorial that tells you to panic at 1,100 weight is teaching you the rules of 2023.

The reliable fix isn't swapping one hardcoded number for another — it's not hardcoding the number at all. Query `/api/v3/exchangeInfo` when your bot starts: the `rateLimits` array in the response is the live source of truth, and it's the first thing Binance updates whenever limits change again.

## Request weight and order count are two different limits

Hitting one of these limits tells you nothing about the other, because they aren't even counted on the same axis. The weight-based rate limit per minute is tracked per IP address and covers everything you send. Order limits work differently: they're tracked per account. The current `exchangeInfo` sample in Binance's docs shows what that means in numbers — an order rate limit per second gives you a window of 50 orders per 10 seconds, and on top of that sits a daily cap of 160,000.

There's a subtlety in the daily cap that trips up trading bots, and it works in your favor. The counter tracks *unfilled* orders, and Binance states this plainly: if your orders are consistently filled by trades, you can keep placing them through the API without limit. In other words, the cap exists to punish order spam that never trades, not active strategies that do. You can watch your standing in real time, too — every successful order returns an `X-MBX-ORDER-COUNT-*` header, and `GET /api/v3/rateLimit/order` shows the same numbers on demand.

The takeaway for debugging: when you get a 429, first figure out which of the two buckets you actually emptied. Throttling your market-data polling won't help at all if what you exhausted was the order window.

## Which endpoints cost the most weight

The general rule comes straight from the official rate limit documentation: the heavier endpoints are the ones doing operations on multiple symbols at once. Public endpoints for market data follow a second pattern on top of that — their price grows with how much data you request in a single call. Order book depth shows how this works in practice. The same endpoint costs 5 weight when you ask for up to 100 levels, 25 weight at up to 500, and 50 weight at the full 1,000 — those are the numbers from Binance Academy's own worked example. The same logic makes omitting the `symbol` parameter on ticker-style calls the classic budget killer: you're asking one request to answer for the entire exchange.

Candles sit at the opposite end of this scale. The klines endpoint weight is a flat 2 per call under the current endpoint table, regardless of the timeframe you request. That price gap explains a pattern you'll see across real projects: strategies built on candle data rarely hit the ceiling under typical loads, while order-book crawlers run into it constantly. We measured this on our own bench: klines weight doesn't depend on page size either, so always request `limit=1000` — the same cost buys a thousand times the data (details in [our test section](#our-test-where-a-single-ips-budget-actually-ends) below).

Two official levers exist for cutting weight spend on public data, and both belong in your architecture before any talk of scaling addresses. The first: market data WebSocket streams don't consume request weight at all — the text of Binance's own 429 error tells you to switch from polling to WebSocket. Mind one distinction when comparing REST and WebSocket rate limits, though: only the *streams* are free. The request-response WebSocket API draws from the same weight pool as REST (opening a connection alone costs 2 weight), and connections are capped at 300 per 5 minutes per IP. The second lever: for services that use only public market data, Binance explicitly recommends the market-data-only base endpoint `data-api.binance.vision` — same paths, no account endpoints. Point your public pipelines there instead of at the main API host.

## How spot and futures limits differ

Spot and futures are separate systems with separate budgets, so exhausting one doesn't touch the other. Spot REST rate limits give each IP 6,000 weight per minute on `api.binance.com`. Futures API rate limits live on a different host, `fapi.binance.com`, and keep their own books: for USDⓈ-M contracts, the futures weight per minute is 2,400 per IP, according to the futures documentation. The bucket is smaller, but it's priced for an API where most data calls are lighter than their spot equivalents.

For a bot that trades both markets from one server, this cuts both ways. You get two independent budgets, which is convenient. You also get two independent ways to burn the same IP address, which is less convenient — because a 418 ban is an IP-level event and doesn't care which API earned it. And the anti-hardcoding rule from earlier applies here with double force: each futures flavor (USDⓈ-M and COIN-M) publishes its own `exchangeInfo`, and their numbers have never moved in sync with spot. Always query the API you actually trade on.

## Staying within limits when running a bot

In practice, the rate limit rules for trading come down to four habits, and none of them involve memorizing numbers.

The first habit is to read the headers instead of the docs. Drive your throttle from the `X-MBX-USED-WEIGHT-1M` value in live responses, and refresh your ceilings from `exchangeInfo` at startup. Bots built this way sailed through the 2023 limit change without a single line of edits, because they never trusted a constant in the first place. Both checks take a couple of lines. To pull your live ceilings, no API key needed:

BashCopy code

```bash
curl -s "https://api.binance.com/api/v3/exchangeInfo?symbol=BTCUSDT" | jq '.rateLimits'
```

In the output, look for the objects where `rateLimitType` is `REQUEST_WEIGHT` and `ORDERS`. The `limit` field next to each `interval` is the number your throttle should trust — and note there can be several intervals per type. Watching your spend in real time is just as short:

PythonCopy code

```python
import requests

r = requests.get("https://api.binance.com/api/v3/ticker/price",
                 params={"symbol": "BTCUSDT"})
print(r.headers["X-MBX-USED-WEIGHT-1M"])   # weight spent in the current window
```

Treat a check like "sleep past 90%" as a demonstration, not a production limiter. A real budget calculation looks closer to this:

CodeCopy code

```
effective_remaining = configured_limit
                    - observed_used_weight
                    - reserved_weight_for_inflight_requests
                    - safety_margin
```

What that takes with several workers is its own topic — the next section covers it.

The second habit is to treat a 429 as a hard stop, not a suggestion. The response carries a `Retry-After` header with the wait time in seconds, and honoring it matters: pushing through repeated 429s is the documented path to a 418, and IP bans escalate for repeat offenders from 2 minutes up to 3 days.

The third is to move public data onto the official offload paths first — streams and `data-api.binance.vision`, as covered above. Anything your bot polls on a timer belongs there; save the REST budget on the main host for what genuinely needs it, meaning orders and account state.

The fourth habit is architectural: know exactly which processes share an egress IP. The 6,000 budget belongs to the address, and everything behind that address shares it. This is why trading bot rate limits so often look mysterious — three processes on one server drain a single bucket, and the "random" 429s are simply siblings competing for the same budget. It's also why frameworks can mislead you here: ccxt rate limits come with a built-in throttler, but a shared IP quietly breaks its math, because the library can only see its own traffic and knows nothing about the neighbors. From here you have two honest options: coordinate the workers behind one address, or isolate them on separate addresses. The next two sections take them in turn.

## How to coordinate several workers behind one IP

If your workers must share an address, the fix is a central limiter: one budget keeper that every process consults before sending. The pattern is short to describe — keep the counter in shared storage (Redis is the usual choice), reserve the endpoint's weight *atomically before* the request goes out, and reconcile your local state against the `X-MBX-USED-WEIGHT-*` headers that come back, because the server's number is the truth and yours is an estimate. A production-oriented sketch:

PythonCopy code

```python
# Sketch, not a library: central weight limiter for workers sharing one egress IP
import time, random, logging, redis, requests

r = redis.Redis()
BASE = "https://api.binance.com"
SAFETY = 0.10        # conservative for a worker fleet; a single collector can drop to 0.05
SPANS = {"SECOND": 1, "MINUTE": 60, "DAY": 86400}
WEIGHTS = {"/api/v3/klines": 2,
           "/api/v3/depth": 50}   # 50 = worst case (1,000 levels); up to 100 levels costs 5, up to 500 costs 25

def load_limits():
    info = requests.get(f"{BASE}/api/v3/exchangeInfo",
                        params={"symbol": "BTCUSDT"}).json()
    limits = {}
    for l in info["rateLimits"]:
        if l["rateLimitType"] != "REQUEST_WEIGHT":
            continue
        if l["interval"] not in SPANS:                   # new interval type: skip it, don't crash the hot path
            logging.warning("unknown interval %s skipped", l["interval"])
            continue
        limits[(l["interval"], l["intervalNum"])] = l["limit"]
    return limits

LIMITS = load_limits()

def window_key(interval, num):
    span = SPANS[interval] * num
    return f"w:{interval}{num}:{int(time.time()) // span}", span

def reserve(weight):
    acquired = []
    for (interval, num), limit in LIMITS.items():        # every interval, not just 1M
        key, span = window_key(interval, num)
        used = r.incrby(key, weight)                     # atomic reserve BEFORE sending
        r.expire(key, span)
        acquired.append(key)
        if used > limit * (1 - SAFETY):
            for k in acquired:                           # roll back EVERY interval reserved so far
                r.decrby(k, weight)
            return False
    return True

def call(path, params):
    weight = WEIGHTS[path]
    while not reserve(weight):
        time.sleep(0.2 + random.random())                # jitter: no thundering herd
    resp = requests.get(BASE + path, params=params)
    used = resp.headers.get("X-MBX-USED-WEIGHT-1M")
    if used:                                             # reconcile: server truth wins
        key, span = window_key("MINUTE", 1)
        r.set(key, max(int(used), int(r.get(key) or 0)), ex=span)   # ex: don't strip the window key's TTL
    if resp.status_code == 429:
        time.sleep(int(resp.headers.get("Retry-After", "1")))
    elif resp.status_code == 418:
        raise RuntimeError("IP banned: stop the whole fleet, don't retry")
    return resp
```

What to look at when you run it. The `reserve` loop walks *all* intervals from `exchangeInfo` — which is what saves you when a short window has reset but a longer one is still exhausted — and on failure it rolls back the reservation on every interval it already claimed, not just the last one: otherwise each rejected attempt would inflate the short window, and the limiter would end up blocking itself under zero real load. The reconcile step pulls your Redis counter up to the server's header whenever they disagree — always with `ex=span`, because a bare `SET` strips the TTL and leaves window keys in Redis forever. Tune `SAFETY` to the job: 10% stops reservations at 5,400 of the 6,000 weight — sensible headroom for a fleet of workers, but a single collector aiming at the 90–94% operating point from our test below can drop it to 5%. And to be explicit about what a real deployment still needs on top of this sketch: a circuit breaker that pauses the fleet after repeated 429s, releasing reservations for requests that failed before reaching Binance, a refresh cycle for the `WEIGHTS` table — ideally with depth weight as a function of the requested levels rather than a worst-case constant — and one Redis namespace *per egress IP*: all keys and workers behind the same address must share one budget keeper.

Coordination also deserves eyes. A minimal metrics set that makes "random" limit behavior stop looking random:

CodeCopy code

```
binance_used_weight_ratio
binance_order_count_ratio
binance_http_429_total
binance_http_418_total
binance_retry_after_seconds
binance_request_weight_by_endpoint
binance_websocket_reconnect_total
```

If `used_weight_ratio` spikes while `request_weight_by_endpoint` shows nothing new from your worker, something else behind the same address is spending your budget. The cleanest fix is isolating workloads on separate addresses — and how independent those per-address budgets really are is exactly what we measured next.

## Our test: where a single IP's budget actually ends

**What we checked.** The article states above that Binance grants each egress IP an independent budget of 6,000 REQUEST_WEIGHT per minute. We spent two days verifying this on our own infrastructure: dedicated PapaProxy.net HTTP proxies with German egress, the public `GET /api/v3/klines` endpoint on `api.binance.com`, BTCUSDT, 1m interval.

**First, what a request costs.** Measured over a direct connection, 8 samples per value, by reading the increment of the `X-MBX-USED-WEIGHT-1M` header:

| `limit` | Request weight | Candles returned | Response size |
| --- | --- | --- | --- |
| 1 | 2 | 1 | 167 B |
| 100 | 2 | 100 | 16.9 KB |
| 500 | 2 | 500 | 84.8 KB |
| 1000 | 2 | 1000 | 169.1 KB |

Weight does not scale with page size. The practical takeaway is simple: **always request `limit=1000`** — identical cost, a thousand times the data. A request for one candle costs exactly as much as a request for a thousand.

**Concurrency ladder.** One dedicated address, `limit=1000`, 120-second stages, no artificial pacing between requests:

Interactive benchmark

### Where concurrency reaches the Binance limit

Switch metrics and focus or hover over a point for the measured value. The table below remains the complete source data.

Weight/min
Requests/s
Throughput

*Binance concurrency benchmark.*

One German dedicated HTTP proxy · BTCUSDT 1m klines · limit=1000 · 120 seconds per stage.

| Connections | Requests/s | weight/min | % of 6,000 budget | Mbit/s | HTTP 429 |
| --- | --- | --- | --- | --- | --- |
| 1 | 3.09 | 370 | 6.2% | 4.2 | no |
| 2 | 5.65 | 678 | 11.3% | 7.6 | no |
| 4 | 11.44 | 1,373 | 22.9% | 15.4 | no |
| 6 | 17.22 | 2,067 | 34.4% | 23.2 | no |
| 8 | 23.62 | 2,834 | 47.2% | 31.9 | no |
| 12 | 35.27 | 4,233 | 70.6% | 47.6 | no |
| 16 | 47.02 | 5,642 | 94.0% | 63.4 | no |
| 24 | 71.19 | 8,543 | 142.4% | 95.6 | **yes** |

The first 429 arrived at 24 connections, on request #4910, carrying a `Retry-After` header.

**What this means.** The 6,000 weight/min budget is real and reachable. Exhausting it at `limit=1000` requires 50 requests per second at 169 KB each — roughly 68 Mbit/s sustained. Our address delivered 95.6 Mbit/s, so **the Binance budget binds before the proxy's bandwidth does**. And every address in the pool sustains that speed independently: run dozens or hundreds of IPs in parallel and the bottleneck becomes your own server's or laptop's uplink, not the proxies. At 16 connections we reached 94% of the budget with zero errors; the boundary sits between 16 and 24.

Our recommended operating point is **14–16 concurrent connections per address** — 90–94% of budget with room for jitter. Chasing 100% is not worth it: a single upstream delay shifts the window and produces a 429.

**Are per-IP budgets independent?** Three dedicated addresses running simultaneously at 14 connections each, 120 seconds:

| Address | Peak `X-MBX-USED-WEIGHT-1M` |
| --- | --- |
| address 1 | 5,666 |
| address 2 | 5,654 |
| address 3 | 5,746 |
| **Total** | **17,066** |

17,066 weight per minute in aggregate — nearly three times a single address's budget — with zero 429 responses. Had the addresses shared an egress, the limit would have fired almost immediately. **Budgets are independent**, and parallel addresses add up linearly: every additional IP raises the total ceiling by another 6,000 weight per minute.

**Pool reliability over a long run.** Separately, ahead of the main series, we ran an extended test on a shared pool of 1,000 addresses: even round-robin across the whole pool, the public klines endpoint, roughly four and a half hours in total.

| Metric | Value |
| --- | --- |
| Addresses used | 1,000 |
| Total requests | 197,527 |
| Responses with valid content | 197,204 (99.84%) |
| Connection setup failures | 323 (0.16%) |

One methodological detail matters here: that loader opened a fresh connection per request instead of reusing sessions. So 0.16% is the failure rate across 197,500 *connection establishments*, not across a couple of hundred long-lived sessions. As a reliability measure, that is the stricter scenario. This run applied low load to each individual address and is *not* a test of Binance rate limits — no conclusions about 429s can be drawn from it. It answers only how steadily the pool holds connections under sustained, even work.

**Confirmed on a second address.** We reached the 6,000 weight/min boundary twice, independently: on a dedicated address in the main series, and earlier on a different address from the shared pool, where the 429 arrived at 16 connections with `limit=1`. The boundary landing in the same place across two different addresses and two different page sizes indicates the limit is counted per egress IP rather than per route or traffic type.

**Geography matters.** Dedicated US addresses returned HTTP 451 from `api.binance.com` while German addresses returned 200 — and that's not a glitch but a boundary between two different companies. Binance.US is a separate organization with its own endpoints (`api.binance.us`), so working with the US exchange requires US addresses specifically, while the global exchange's endpoints accept addresses from any served country except the US. Pick the egress country before you start, or you'll be measuring a block rather than a limit.

**What this means for backfilling history.** The full 1-minute history of BTCUSDT is roughly 4.7 million candles, about 4,710 pages of 1,000. That is **9,420 weight units — around a minute and a half of one address's budget** — and roughly 796 MB of transfer. Binance rate limits do not constrain a full single-pair backfill at all; data transfer does. The two figures below are derived from the table above rather than separately measured:

- sequential paging on one connection (3.09 req/s) — about **25 minutes**;
- 16 connections on a single address (47.02 req/s) — about **100 seconds**.

That gap is entirely about concurrency, not proxies. If your loader pages sequentially, adding addresses will not help until you partition the time range and fetch pages in parallel.

**Test conditions.** Dedicated PapaProxy.net HTTP proxies, DE egress, IP-whitelist authorization. Target: the public `api.binance.com`, no keys or signing. 120-second stages, a 65-second pause between stages to reset the one-minute window. The ladder stops at the first 429 and never climbs past it. Run dated August 5, 2026.

The practical bottom line for planning: the operating point is 14–16 connections per address, per-address budgets add up linearly, and from there it's plain arithmetic on your volume. Our customers run this exchange on one of two setups, and the choice comes down to volume: packages of 5,000–10,000 IPs from the shared pool for bulk data collection, or dedicated addresses when an IP needs to be exclusively yours. Both work — this test exercised each of them from a different angle — and both are available on our [proxies for Binance](/target/binance-proxy-server/) page: dedicated addresses starting from a single IP, shared-pool packages, access protected with IP whitelisting, and HTTP, HTTPS, SOCKS4, and SOCKS5 on every address.

## FAQ

**Is there a free tier for the Binance API?**

There are no paid tiers at all — the API is free for every verified account. The limits you hit are technical, not commercial: 6,000 request weight per minute per IP and per-account order caps apply to everyone equally, and no payment unlocks higher ones. You scale by using weight efficiently and by adding IP addresses, not by upgrading a plan.

**What are the free usage limits in practice?**

The same numbers everyone gets: 6,000 weight per minute for each IP address, and order limits per account — 50 orders per 10 seconds and 160,000 unfilled orders per day, per the current `exchangeInfo` sample in the official docs. Filled orders don't choke the daily counter. Check live values via `/api/v3/exchangeInfo`, since Binance updates them there first.
