429 Too Many Requests
The target, reacting to your pace. Your credentials are fine and the proxy delivered the request.
- Usually from
- The target, about your request rate
- Try first
- Read Retry-After and wait that long
What a 429 means through a proxy
Rate limits count requests against a key: an IP address, an API key, a session cookie, an account, or some mix. Which key the site counts decides whether a proxy changes anything.
Over HTTPS the 429 you read comes from inside the tunnel, so the site sent it. If your tool reports a 429 as a proxy or tunnel error instead, it was the answer to CONNECT and came from the proxy.
Some sites send Retry-After as a number of seconds, some as an HTTP date, and some leave it out. A few use 403 or 503 for the same job.
Your credentials, the proxy, or the target?
The target, reacting to your pace. Your credentials are fine and the proxy delivered the request.
Your credentials
Fine. The request got through the proxy and the site answered it.
The proxy
Only in that one static IP collects every request against one address. Rotation spreads them, which helps with limits counted per IP and nothing else.
The target site
The source, and it is telling you its limit.
How to tell
- A
Retry-Afterheader on the 429, orX-RateLimit-Remainingstyle headers counting down on the responses before it. - The 429s start after a burst and stop when you pause. That is a rate limit doing its job.
- A new residential address does not stop them. The limit is counted on your API key, cookie or account.
- A 429 inside a
ProxyErrororTunnelErrorcame from the proxy during CONNECT. Paste that one into the Discord.
Fixes, in the order to try them
- Costs nothing
Honour Retry-After
Wait the number of seconds it gives, or until the date it gives. Retrying sooner tends to extend the block.
- Costs nothing
Cap concurrency per domain
Most 429s come from parallel workers all hitting one host. Limit how many requests run against the same domain at once; two to four is plenty for most sites.
- Costs nothing
Back off exponentially, with jitter
After a 429 with no Retry-After, wait 1, 2, 4, then 8 seconds plus a random fraction, so parallel workers do not retry in lockstep. Give up after a few tries and log the URL for later.
- Costs nothing
Read the site’s own limits
APIs publish their rate limits, and
robots.txtmay carry aCrawl-delay. Staying under a published limit costs nothing and keeps the pool usable for everyone on it. - Costs some time
Stop fetching what you already have
Cache pages between runs. Conditional requests with
If-Modified-SinceorIf-None-Matchlet the site answer 304 with no body, which counts less against most limits and moves fewer bytes. - Costs money
Spread a per-IP limit with rotating residential
If the limit is per address and your overall pace is reasonable, residential sends each request from a different address. An ISP or datacenter IP is one address, and one static IP gets rate-limited eventually.
See the real status and headers
Each sample reads the limit headers and waits the way the site asked, which is the whole fix most of the time.
curl
# the status and any rate-limit headers
curl -s -o /dev/null -D - -x http://USER:[email protected]:8000 https://api.example.com/items \
| grep -iE '^(HTTP|retry-after|x-ratelimit)'
# --retry retries a 429 and waits as long as Retry-After says
curl --retry 3 --retry-max-time 120 -x http://USER:[email protected]:8000 https://api.example.com/itemsPython Requests
import random
import time
from email.utils import parsedate_to_datetime
import requests
PROXY = "http://USER:[email protected]:8000"
def pause_for(r, attempt):
header = r.headers.get("Retry-After")
if header is None:
return 2 ** attempt + random.random()
if header.isdigit():
return int(header)
return max(0, parsedate_to_datetime(header).timestamp() - time.time())
def get(url, tries=4):
for attempt in range(tries):
r = requests.get(url, proxies={"http": PROXY, "https": PROXY}, timeout=30)
if r.status_code != 429:
return r
pause = pause_for(r, attempt)
print(f"429 on {url}, waiting {pause:.1f}s")
time.sleep(pause)
return rScrapy
ROBOTSTXT_OBEY = True
CONCURRENT_REQUESTS_PER_DOMAIN = 2
DOWNLOAD_DELAY = 1.0
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
RETRY_HTTP_CODES = [429, 500, 502, 503, 504]
RETRY_TIMES = 3Scrapy’s retry middleware retries a 429 without reading Retry-After, so the delay and AutoThrottle settings are what keep you under the limit.
Playwright
from playwright.sync_api import Error, sync_playwright
PROXY = {"server": "http://resi.proxymonkey.io:8000", "username": "USER", "password": "PASS"}
URLS = ["https://example.com/a", "https://example.com/b"]
def log_limits(response):
if response.status == 429:
print("429 on", response.url, "retry-after:", response.headers.get("retry-after"))
with sync_playwright() as p:
browser = p.chromium.launch(proxy=PROXY)
page = browser.new_page()
page.on("response", log_limits)
for url in URLS:
page.goto(url)
page.wait_for_timeout(2000)
browser.close()One page load is dozens of requests, and scripts or API calls behind the page can hit the limit before the HTML does. The listener catches all of them.
Node.js
import { setTimeout as sleep } from "node:timers/promises";
import { fetch, ProxyAgent } from "undici";
const dispatcher = new ProxyAgent("http://USER:[email protected]:8000");
async function get(url, tries = 4) {
for (let attempt = 0; attempt < tries; attempt++) {
const res = await fetch(url, { dispatcher });
if (res.status !== 429) return res;
const after = Number(res.headers.get("retry-after"));
const pause = after > 0 ? after * 1000 : 2 ** attempt * 1000 + Math.random() * 1000;
await res.body?.cancel();
await sleep(pause);
}
throw new Error("still 429 after " + tries + " tries: " + url);
}The samples use the residential gateway, resi.proxymonkey.io:8000. For an ISP or datacenter IP, use USER:PASS@IP:PORT for the address you rented. Your dashboard lists the host and port for every order, and where it differs from this page, the dashboard is right.
Will a different proxy line fix it?
When switching helps
When the site limits per IP and your overall pace is polite, rotating residential spreads requests over many addresses. Our ISP page says so too: one static IP gets rate-limited eventually.
When it will not
Limits counted per API key, login, cookie or account ignore the IP. And using rotation to push a site harder than it can take is overloading a target, which the acceptable use policy does not allow.
Is a failed request billed?
We bill for request bytes and response bytes, including headers and protocol overhead on the tunnelled connection. Connections that fail before transferring data are not billed. Retries that you initiate are billed and appear as separate entries in your usage log.
A 429 is a response that travelled back through the proxy, and the terms bill request and response bytes with no exception for status. They also bill every retry you initiate as its own entry in the usage log, so a loop that ignores Retry-After costs money as well as time.
Residential is billed per GB of that traffic. ISP and datacenter addresses are charged per IP for their term, and where a plan includes a traffic allowance, traffic past it is billed per GB under the same rule. The usage log in your dashboard has one row per request with bytes in, bytes out and cost, so you can look up the failed request yourself.
Questions people ask about a 429
Will rotating proxies stop 429 errors?
Only when the site limits per IP address. Many limits count per API key, login or cookie, and a new address changes nothing there. Even when rotation helps, keep your total rate to something the site can handle.
Is a 429 the proxy limiting me?
A 429 you read as a response over HTTPS came from the site, because the proxy cannot write inside the tunnel. If your tool reports the 429 as a proxy or tunnel error, it came from the gateway during CONNECT. Paste that one into the Discord.
How long should I wait after a 429?
As long as Retry-After says. Without the header, back off exponentially from a second or two and add a little randomness so parallel workers do not all retry at the same moment.
Do retries after a 429 cost traffic?
Yes. The terms say retries you initiate are billed and appear as separate entries in your usage log. A retry sent before Retry-After has passed usually gets another 429, so it is the most wasteful kind.
Errors that travel with this one
- 403
403 Forbidden
The website refused you. Through a proxy that is usually about the IP range, your headers or your pace.
Work it out → - 503
503 Service Unavailable
Somebody is overloaded, down for maintenance, or turning you away. The body and Retry-After tell you which.
Work it out → - TIMEOUT
Connection timeout
Nothing came back in time. Find out which leg stalled before you raise the limit.
Work it out →
Still stuck on a 429?
Paste your error in the Discord: the full message plus the command or the few lines that set up the proxy, with the password taken out. Someone there has seen it before.
Join the Discord4,200+monkeys in the Discord
Help from humans
Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.
A status bot that tells on us
Pool health, incidents and maintenance posted automatically. Including the bad days.
Deals and free traffic
Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.