← All posts

HTTP 429 Error: What It Means and How to Fix It

Quick answer: HTTP 429 "Too Many Requests" means the server is rate-limiting you: you (or your app) sent more requests in a given window than the server allows, and it's telling you to slow down. It is temporary. The fix is to wait, then retry more slowly: check the Retry-After response header for how long, and add exponential backoff to any code that makes repeated requests.

A 429 is not a bug, a ban, or a broken server. It's a deliberate traffic signal, and once you understand what triggered it, it's one of the easiest HTTP errors to handle well.

What's Actually Happening

Servers protect themselves with rate limits: rules like "100 requests per minute per IP" or "10,000 per day per API key." Cross the threshold and instead of your data you get:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{"error": "rate limit exceeded"}

The status code was standardized in RFC 6585 specifically so clients could distinguish "you're going too fast" from real failures like 500s. That distinction matters: a 500 might warrant an immediate retry, but immediately retrying a 429 is exactly the wrong move: it keeps the counter pegged and can extend your timeout.

Rate limits are usually keyed by IP address, API key, or account. That's why one misbehaving script can get your whole office 429'd on a shared IP.

If You're Just Browsing

Occasionally you'll hit a 429 as a regular user by refreshing too fast, or on a site with aggressive limits. Fixes, in order:

  1. Wait a minute or two. Most windows reset in 60 seconds.
  2. Stop refreshing. Every retry can restart the clock.
  3. Check your extensions. Auto-refreshers and some privacy tools spam requests.
  4. On a shared network? A VPN or your phone's hotspot gets you a fresh IP, which usually clears an IP-based limit instantly.

If You're a Developer: Handle It Properly

The correct pattern has three parts: respect Retry-After, back off exponentially, and add jitter. Here it is in Python:

import time, random, requests

def fetch_with_backoff(url, max_retries=5):
    for attempt in range(max_retries):
        resp = requests.get(url)
        if resp.status_code != 429:
            return resp

        # Prefer the server's explicit instruction
        retry_after = resp.headers.get("Retry-After")
        if retry_after:
            wait = float(retry_after)
        else:
            # Exponential backoff with jitter: 1s, 2s, 4s, 8s...
            wait = (2 ** attempt) + random.uniform(0, 1)

        time.sleep(wait)
    raise RuntimeError(f"Still rate limited after {max_retries} retries")

The jitter (that random.uniform(0, 1)) looks cosmetic but isn't. If fifty of your workers all get 429'd at the same moment and all retry exactly 2 seconds later, they arrive together and get 429'd together, forever. Randomizing the wait breaks the synchronization.

Two more habits that prevent 429s instead of just surviving them:

  • Read the rate-limit headers before you hit the wall. Most APIs send X-RateLimit-Remaining (or the newer RateLimit-Remaining) on every response. When it approaches zero, slow down preemptively.
  • Cache. The cheapest request is the one you don't make. If you're re-fetching data that changes hourly, cache it for an hour.

If You're Web Scraping

Scrapers meet 429s constantly, because scraping is precisely the "many requests, fast" pattern rate limits exist to catch. Beyond backoff, three things actually work:

Throttle to a human-ish pace. Concurrency of 2–3 with a small delay between requests clears most sites' radar. Going from 50 requests/second to 1/second feels painful but finishing slowly beats being blocked halfway.

Distribute across IPs. Since most limits are per-IP, spreading load across addresses raises your effective ceiling. This is legitimate for public data at respectful volume, and it's the main thing scraping APIs do under the hood.

Or let a service absorb the problem. A managed fetch layer like link.sc handles retry logic, pacing, and IP rotation for you: you send one API call per URL and 429 handling stops being your code's concern. If you're feeding scraped content to an LLM anyway, you also get the HTML-to-Markdown conversion for free.

One thing that doesn't work: hammering through a 429 with instant retries. Sites escalate: today's 429 becomes tomorrow's 403 or a permanent IP block.

429 vs. Its Neighbors

Code Meaning Retry?
429 Too many requests (rate limited) Yes, after waiting
403 Forbidden (blocked or unauthorized) Not without changing something
503 Server overloaded or down for maintenance Yes, with backoff
420 Nonstandard; old Twitter's version of 429 Treat like 429

If your 429s ever turn into 403s, that's the site telling you it now considers you hostile rather than just fast. Back way off.

The Bottom Line

A 429 is the server saying "slower, please," and nothing worse. Wait it out as a user; as a developer, honor Retry-After, back off exponentially with jitter, watch the rate-limit headers, and cache aggressively. Do those four things and 429s become a non-event in your logs instead of a 3 a.m. page.


Tired of babysitting rate limits in your scraping pipeline? Try link.sc free: clean web data from one API call, retries and pacing included.