Binance API Error 1015: Rate Limit Explained for Traders
Hit Binance API error 1015? Learn what rate limits mean, why they trigger, and how to code around them without getting your IP banned.
Hit Binance API error 1015? Learn what rate limits mean, why they trigger, and how to code around them without getting your IP banned.
You're running a bot, everything looks good, and then — boom — your requests start failing with HTTP 429 or a JSON body containing code -1015. Your IP might even get auto-banned. If this sounds familiar, you've hit Binance's rate limiting system. It's not a bug in your code. It's a hard wall that every trader hitting the API eventually runs into, and learning how to work with it is the difference between a stable bot and one that randomly dies at 3am.
Error code -1015 from Binance means 'Too many new orders.' It's specifically tied to order placement rate limits, not general request limits. Binance enforces several distinct rate limit buckets simultaneously: requests per minute (IP-based), orders per second, and orders per day. The -1015 error fires when you exceed the order frequency limit — by default, 10 orders per second or 100,000 orders per 24 hours on a single account.
This is separate from the general HTTP 429 Too Many Requests error, which covers all API endpoints. With -1015, the issue is specifically how fast your bot is trying to place, cancel, or modify orders. Even if your request rate looks fine, hammering the order endpoints triggers this error fast — especially during volatile markets when your strategy tries to rebalance every few hundred milliseconds.
If you receive a 418 status code (not 429), your IP has been auto-banned. Bans start at 2 minutes and escalate to days for repeat violations. The only fix is to wait it out — there's no API call to lift a ban.
Binance uses a weight-based system rather than a simple requests-per-minute counter. Every endpoint has a weight value, and your rolling window accumulates these weights. Heavy endpoints like depth snapshots can cost 50 weight, while a simple ping costs 1. The first thing any serious Binance bot should do at startup is call GET /api/v3/exchangeInfo to inspect the current limits programmatically — don't hardcode them, because Binance adjusts limits over time.
| Limit Type | Default Value | Window |
|---|---|---|
| Request Weight | 6000 | 1 minute |
| Orders (RAW) | 100 | 10 seconds |
| Orders (RAW) | 200,000 | 24 hours |
| New Orders (-1015) | 10 | 1 second |
| IP Ban Trigger | Repeated 429s | Rolling |
Every API response from Binance includes headers that tell you exactly where you stand: X-MBX-USED-WEIGHT-1M shows your current weight usage for the rolling minute window. Reading these headers on every response is the only reliable way to stay ahead of a ban. Platforms like Bybit and OKX have similar systems but with different weight values and window sizes — if you're trading across multiple venues, never assume one exchange's limits apply to another.
The most important habit you can build is inspecting response headers on every call. Here's a minimal setup that logs your weight usage and backs off automatically:
import time
import requests
import hmac
import hashlib
from urllib.parse import urlencode
API_KEY = 'your_api_key'
SECRET_KEY = 'your_secret_key'
BASE_URL = 'https://api.binance.com'
def signed_request(method, path, params=None):
params = params or {}
params['timestamp'] = int(time.time() * 1000)
query_string = urlencode(params)
signature = hmac.new(
SECRET_KEY.encode('utf-8'),
query_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
params['signature'] = signature
headers = {'X-MBX-APIKEY': API_KEY}
url = f'{BASE_URL}{path}'
response = requests.request(method, url, headers=headers, params=params)
# Always inspect rate limit headers
used_weight = int(response.headers.get('X-MBX-USED-WEIGHT-1M', 0))
print(f'[Rate] Used weight this minute: {used_weight}/6000')
if used_weight > 5000:
print('[Rate] Approaching limit — sleeping 10s')
time.sleep(10)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f'[Rate] 429 received — waiting {retry_after}s')
time.sleep(retry_after)
return None
if response.status_code == 418:
print('[Rate] IP banned — do not retry, wait for ban to expire')
raise Exception('IP banned by Binance')
response.raise_for_status()
return response.json()
# Example: fetch open orders
orders = signed_request('GET', '/api/v3/openOrders', {'symbol': 'BTCUSDT'})
print(orders)
When your bot gets -1015, the worst thing it can do is immediately retry. That just triggers more errors and accelerates toward an IP ban. The correct pattern is exponential backoff with jitter — each failed attempt doubles the wait time, with a small random component to prevent thundering herd problems if you're running multiple bot instances.
import random
import time
import requests
def place_order_with_backoff(symbol, side, quantity, price, max_retries=5):
"""
Place a limit order with exponential backoff on rate limit errors.
"""
base_delay = 1.0 # seconds
for attempt in range(max_retries):
try:
params = {
'symbol': symbol,
'side': side, # 'BUY' or 'SELL'
'type': 'LIMIT',
'timeInForce': 'GTC',
'quantity': quantity,
'price': price,
}
result = signed_request('POST', '/api/v3/order', params)
if result is None:
# 429 already handled inside signed_request
continue
# Check for -1015 in the response body
if isinstance(result, dict) and result.get('code') == -1015:
wait = base_delay * (2 ** attempt) + random.uniform(0, 0.5)
print(f'[Order] -1015 rate limit hit, retrying in {wait:.1f}s (attempt {attempt+1}/{max_retries})')
time.sleep(wait)
continue
return result # success
except requests.exceptions.HTTPError as e:
if e.response.status_code in (429, 418):
wait = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f'[Order] HTTP {e.response.status_code} — waiting {wait:.1f}s')
time.sleep(wait)
else:
raise
raise Exception(f'Failed to place order after {max_retries} retries')
# Usage
result = place_order_with_backoff('ETHUSDT', 'BUY', '0.1', '2500.00')
if result:
print(f"Order placed: {result['orderId']}")
Never cancel and re-place orders in a tight loop to 'refresh' your position. Every cancel counts toward your order rate limit just like a new order. Use modify order (PATCH /api/v3/order) on Binance Spot API v3 instead — it counts as one operation.
Reactive error handling (catching -1015 after it fires) is only half the solution. Professional algo traders build rate limiting into the architecture from the start. The key concepts are: a token bucket for order rate limiting, request queuing, and a shared weight tracker across all threads or async tasks.
import asyncio
import time
from collections import deque
class RateLimiter:
"""
Simple token bucket for Binance order rate limits.
Default: 10 orders per second, 100000 orders per 24h.
"""
def __init__(self, orders_per_second=8, orders_per_day=90000):
# Use 80% of actual limits as safety margin
self.ops = orders_per_second
self.opd = orders_per_day
self._second_window = deque()
self._day_window = deque()
def _prune(self, window, max_age_seconds):
now = time.monotonic()
while window and now - window[0] > max_age_seconds:
window.popleft()
async def acquire(self):
while True:
self._prune(self._second_window, 1.0)
self._prune(self._day_window, 86400.0)
if len(self._second_window) < self.ops and len(self._day_window) < self.opd:
now = time.monotonic()
self._second_window.append(now)
self._day_window.append(now)
return # cleared to send
# Wait a bit and retry
await asyncio.sleep(0.05)
# Usage in an async bot
rate_limiter = RateLimiter()
async def safe_place_order(symbol, side, qty, price):
await rate_limiter.acquire() # blocks until within limits
return place_order_with_backoff(symbol, side, qty, price)
async def main():
# Fire multiple orders without worrying about -1015
tasks = [
safe_place_order('BTCUSDT', 'BUY', '0.001', '65000'),
safe_place_order('ETHUSDT', 'BUY', '0.1', '2500'),
safe_place_order('SOLUSDT', 'BUY', '1', '150'),
]
results = await asyncio.gather(*tasks)
for r in results:
print(r)
asyncio.run(main())
This architecture works equally well if you're trading on KuCoin or Gate.io alongside Binance — instantiate a separate RateLimiter per exchange with that exchange's specific limits. KuCoin, for instance, uses a different weight system for its private endpoints and has stricter per-second limits on order placement than Binance does.
A huge portion of rate limit problems come from polling: bots that call GET /api/v3/ticker/price every 500ms to track price, or spam GET /api/v3/openOrders to monitor fills. Every one of those REST calls burns weight. The fix is switching to WebSocket streams, which push data to you rather than requiring repeated polling.
Binance's WebSocket streams are free and don't count against your API weight budget. For real-time price data, order book updates, and your own account's order fills, WebSockets are strictly better than REST polling. Tools like VoiceOfChain use this approach to monitor signals across multiple pairs simultaneously — subscribing to live streams rather than hitting REST endpoints thousands of times per hour. If your bot is burning through rate limits on price-check calls, switching to WebSocket is usually a 10x improvement with minimal code changes.
For your account's order updates specifically — fills, cancels, new order confirmations — use the User Data Stream via a listen key. This eliminates polling GET /api/v3/openOrders entirely, which at weight 3 per call adds up fast on a busy bot. Coinbase Advanced Trade API uses a similar WebSocket-first model, and traders familiar with that API will find Binance's streams conceptually identical.
Binance API error -1015 is a design constraint, not a failure mode. Every serious algo trader hits it eventually, and the ones who handle it gracefully do so by building rate awareness into their architecture from day one: read the response headers, use a token bucket, switch polling to WebSockets, and back off exponentially when you do hit limits. The code patterns above give you a working foundation — adapt the RateLimiter class to whichever exchange you're on, whether that's Binance, Bybit, or Gate.io, by plugging in that exchange's specific limits from their documentation. If you're monitoring multiple pairs for entry signals, tools like VoiceOfChain handle the market data layer so your bot can focus purely on execution — that alone cuts your API weight consumption significantly. Get the rate limiting right once, and you'll never think about it again.