Coinbase API Limits: Everything Traders Need to Know
A practical guide to Coinbase API rate limits, restrictions, and daily sending limits — with Python code examples for authentication, error handling, and staying within bounds.
A practical guide to Coinbase API rate limits, restrictions, and daily sending limits — with Python code examples for authentication, error handling, and staying within bounds.
If you've ever built a trading bot on Coinbase and suddenly started getting 429 errors at 2 AM, you already know why understanding Coinbase API limits matters. Rate limits aren't bureaucratic annoyances — they're a load-balancing mechanism, and knowing exactly where the ceilings are is what separates bots that run for months from bots that crash on launch day. This guide breaks down the current Coinbase API restrictions, the differences between public and private endpoint limits, how Coinbase Advanced API rate limits compare to other exchanges like Binance and OKX, and how to write defensive code that handles them gracefully.
Coinbase API rate limits define how many requests your application can make to the Coinbase REST API within a given time window. Exceed those limits and the API returns HTTP 429 — Too Many Requests — along with a Retry-After header telling you how long to back off. These limits exist at multiple levels: per endpoint, per API key, and sometimes per IP. The Coinbase Advanced API (the successor to Coinbase Pro) organizes limits primarily around request weight and endpoint categories rather than a single global ceiling.
The Coinbase Advanced Trade API replaced Coinbase Pro in 2023 and brought with it updated Coinbase API restrictions. Public endpoints (market data, product listings, candles) are throttled more conservatively than private ones. Private endpoints — placing orders, fetching balances, listing fills — use per-key rate limiting. Here's the current breakdown as documented in the Coinbase developer portal:
| Endpoint Category | Rate Limit | Auth Required |
|---|---|---|
| Public market data (products, candles, tickers) | 10 req/sec | No |
| Private orders, fills, portfolios | 15 req/sec | Yes (API Key + HMAC) |
| Batch order operations | 5 req/sec | Yes |
| WebSocket subscriptions | Up to 15 channels per connection | Yes (for private channels |
| Coinbase limit per day (withdrawals) | Depends on verification tier | Yes |
The Coinbase REST API limits reset on a rolling per-second window, not a fixed clock boundary. A burst of 20 requests in 100ms will still trigger a 429 even if your average over the minute is within limits. Always implement token-bucket or leaky-bucket throttling on your client side, not just a simple counter.
Before you can make private API calls — and before rate limits on private endpoints apply to you — you need proper authentication. Coinbase Advanced Trade API uses HMAC-SHA256 signatures tied to your API key and secret. Here's a clean Python setup that you can drop into any bot:
import hashlib
import hmac
import time
import requests
API_KEY = "your_api_key_here"
API_SECRET = "your_api_secret_here"
BASE_URL = "https://api.coinbase.com"
def build_signature(timestamp: str, method: str, path: str, body: str = "") -> str:
message = f"{timestamp}{method}{path}{body}"
return hmac.new(
API_SECRET.encode("utf-8"),
message.encode("utf-8"),
digestmod=hashlib.sha256
).hexdigest()
def coinbase_request(method: str, endpoint: str, body: str = "") -> requests.Response:
timestamp = str(int(time.time()))
path = f"/api/v3/brokerage{endpoint}"
signature = build_signature(timestamp, method, path, body)
headers = {
"CB-ACCESS-KEY": API_KEY,
"CB-ACCESS-SIGN": signature,
"CB-ACCESS-TIMESTAMP": timestamp,
"Content-Type": "application/json"
}
url = f"{BASE_URL}{path}"
return requests.request(method, url, headers=headers, data=body or None)
Note that the timestamp must be within 30 seconds of Coinbase server time. Clock drift is a common cause of authentication failures that developers initially misdiagnose as API restrictions. Use an NTP-synced clock or fetch server time via the public /time endpoint before making signed requests.
With authentication in place, here's how to actually pull market data and handle the rate limit response correctly. This example fetches the best bid/ask for BTC-USD and includes basic 429 handling with a single retry:
def get_best_bid_ask(product_id: str = "BTC-USD") -> dict | None:
response = coinbase_request("GET", f"/best_bid_ask?product_ids={product_id}")
if response.status_code == 200:
data = response.json()
pricebook = data["pricebooks"][0]
best_ask = pricebook["asks"][0]["price"]
best_bid = pricebook["bids"][0]["price"]
print(f"{product_id} — Ask: {best_ask} | Bid: {best_bid}")
return data
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
print(f"[RATE LIMIT] Waiting {retry_after}s before retry...")
time.sleep(retry_after)
return get_best_bid_ask(product_id)
else:
print(f"[ERROR] {response.status_code}: {response.text}")
return None
# Fetch live pricing
result = get_best_bid_ask("ETH-USD")
if result:
spread = float(result["pricebooks"][0]["asks"][0]["price"]) - float(result["pricebooks"][0]["bids"][0]["price"])
print(f"Spread: {spread:.2f}")
A single retry works for occasional spikes, but production bots need something more robust. The pattern below wraps any Coinbase API call with a retry decorator that uses exponential backoff — doubling the wait time after each failed attempt. This is the same pattern used in institutional trading infrastructure and works well within Coinbase API restrictions without hammering the endpoint repeatedly:
from functools import wraps
from typing import Callable
def with_rate_limit_retry(max_retries: int = 4, base_delay: float = 1.0):
"""Decorator that retries on HTTP 429 with exponential backoff."""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
response = func(*args, **kwargs)
if response.status_code != 429:
return response
wait = base_delay * (2 ** attempt)
retry_header = response.headers.get("Retry-After")
if retry_header:
wait = max(wait, float(retry_header))
print(f"[429] Attempt {attempt + 1}/{max_retries}. Backing off {wait:.1f}s")
time.sleep(wait)
raise RuntimeError("Coinbase API rate limit: max retries exceeded")
return wrapper
return decorator
@with_rate_limit_retry(max_retries=4, base_delay=1.0)
def fetch_open_orders():
return coinbase_request("GET", "/orders/historical/batch?order_status=OPEN")
# Usage
response = fetch_open_orders()
if response.status_code == 200:
orders = response.json().get("orders", [])
print(f"Open orders: {len(orders)}")
Pair this retry decorator with a client-side token bucket that proactively limits outgoing requests to 12/sec (giving 3 req/sec headroom below the 15/sec private limit). Reactive retry after 429 is the safety net — proactive throttling is what keeps you from hitting it in the first place.
Beyond request frequency, Coinbase API restrictions also cover how much value you can move per day. Coinbase limit per day for withdrawals and sends depends on your account verification level. These are separate from rate limits and apply regardless of whether you're using the API or the web interface:
When building automated withdrawal or rebalancing bots, always fetch your current limits via the /accounts endpoint before queuing large moves. A withdrawal that exceeds your daily limit will be rejected at the API level with a 400 error and a descriptive message in the response body — not a 429, which makes it easy to confuse with other errors if you're not parsing response bodies carefully.
If you're running multi-exchange strategies — say, cross-listing arbitrage between Coinbase and Binance, or hedging a spot position on OKX — understanding how Coinbase API rate limits compare to other venues matters for designing a unified request scheduler. Binance uses a weight-based system where each endpoint consumes a different number of 'weight points' per minute (default 1200/min). Bybit allocates limits per endpoint category. OKX throttles market data at 20 requests per 2 seconds. Here's a side-by-side:
| Exchange | Public Endpoints | Private Endpoints | Limit Model |
|---|---|---|---|
| Coinbase Advanced | 10 req/sec | 15 req/sec | Per-second rolling window |
| Binance | 1200 weight/min (~20 req/sec) | 1200 weight/min (weighted) | Weight-based per minute |
| Bybit | 120 req/min public | 600 req/min private | Per-minute buckets |
| OKX | 20 req/2sec (market data) | 60 req/2sec (trading) | Per-2-second window |
Platforms like Bybit and OKX tend to be more generous on private endpoint limits for active traders, which is why high-frequency market makers often treat Coinbase as a pricing reference while executing on venues with looser Coinbase Advanced API rate limits equivalents. For signals-driven trading — where you're reacting to real-time alerts from a service like VoiceOfChain rather than constantly polling — Coinbase's limits are more than adequate. VoiceOfChain delivers pre-processed signals, so your bot only touches the API when there's an actual trade trigger, not every tick.
Coinbase API rate limits are predictable and workable once you understand the boundaries. The core rules: use HMAC authentication for private endpoints, implement exponential backoff with Retry-After header parsing, and build a client-side throttle that keeps you proactively under the 15 req/sec ceiling rather than reactively recovering from 429s. For sending limits, always query your account's current limits before scheduling large withdrawals — a 400 on a withdrawal attempt is a lot more disruptive than a 429 on a market data call. If you're integrating trading signals from a platform like VoiceOfChain, signal-driven execution naturally fits within Coinbase API restrictions since you're only calling the API when signals fire, not polling continuously. Compared to the weight-based complexity of Binance or the multi-bucket system on OKX, Coinbase's rate limit model is actually one of the simpler ones to reason about — as long as you know the numbers going in.