Skip to content
Proxy · Use case

Fix 403 Forbidden and 429 Too Many Requests in your scraper

Scraping 403 Forbidden and 429 Too Many Requests errors look like the same problem, but they rarely have the same cause or the same fix. One is an antibot wall judging your IP and fingerprint, one is a site throttling repeat traffic, and one can be your own scraping quota. This page sorts them by where they come from, so you stop retrying the wrong way and your scraper only waits when waiting actually helps.

The problem

Your scraper is blocked, and the status code alone does not say why

A 403 from a protected site usually means an antibot service scored the request as automated: a datacenter IP, a header set that does not match a real browser, a TLS handshake that gives the client away. A 429 means the site is rate limiting the address the requests come from. Both arrive as a failed fetch, and a scraper that logs only the status code treats them as the same flaky error.

The reflex fixes make it worse. Retrying a 403 immediately, or with a new user agent on the same IP, sends the same signals again and looks more suspicious each time; a real browser profile is the whole header set, not one string. Retrying a 429 in a tight loop extends the throttle. And if you call a scraping API, its own 429 for an exhausted quota is a third case that no proxy will fix.

On Pro plans the antibot 403 is handled before it reaches you: automatic proxy resolution escalates through proxy tiers up to residential IPs and remembers per domain which one worked. The free endpoint names the same situation with EPROXYNEEDED instead of a raw 403. A Microlink 429 is ERATE, your quota, with a reset time. Caching cuts the repeat hits that trigger throttling.

How it works

How to fix 403 and 429 errors when scraping

Classify the failure by error code, then reduce how often you hit the target. The troubleshooting guide lists every plan and auth error alongside these.

1 · Tell the three blocks apart
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const read = async url => {
  try {
    return { url, page: await microlink.metadata(url, { retry: 3 }) }
  } catch (error) {
    if (error.code === 'ERATE') return { url, blocked: 'quota' }
    if (error.code === 'EPROXYNEEDED') return { url, blocked: 'antibot' }
    throw error
  }
}

ERATE is the API’s own 429: your plan quota ran out. EPROXYNEEDED is an antibot wall hit on the free endpoint. With a Pro key the antibot case is resolved through the proxy, so it stops showing up here.

2 · Hit the target less often
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const page = await microlink.metadata('https://example.com/pricing', {
  ttl: '1d',
  staleTtl: 0
})

ttl keeps the response for a day and staleTtl: 0 serves the cached copy while a fresh one is fetched in the background. Cache hits never reach the target site and never count toward your quota.

3 · The same request as a URL
curl 'https://pro.microlink.io/?url=https%3A%2F%2Fexample.com%2Fpricing&ttl=1d&staleTtl=0&retry=3' \
  -H 'x-api-key: $MICROLINK_API_KEY'

Sent to the Pro endpoint with your x-api-key header, the request needs no proxy parameter. The payload includes statusCode, the HTTP status the target answered with.

Parameters used
  • retry Server-side retries with exponential backoff instead of your own tight loop. Default 2.
  • ttl Cache lifetime from 1 minute to 31 days, so repeat reads skip the target. Pro plans.
  • staleTtl Serves the cached copy while revalidating in the background. Pro plans.
  • proxy Automatic on Pro. Pass it only to pin a country or to use your own proxy server.

When the quota is the limit, the rate limit docs explain the reset window. When a site keeps answering 403 and you want to know who is behind it, detect the antibot system first.

Why it works

Why sorting 403 and 429 errors beats retrying them

Every retry of a blocked request costs time and makes the next one look worse. Knowing which of the three blocks you hit tells you whether to route, wait or cache.

01 · Antibot 403
Routed through proxy tiers on Pro.
When a 403 antibot wall answers, the API escalates through its proxy tiers, with residential IPs as the last and slowest step. The winning tier is remembered per domain, so the second request to a protected site does not pay for the search again.

For the Cloudflare case specifically, see scraping Cloudflare-protected sites.

02 · Your own 429
ERATE is quota, not the target.
The free endpoint allows 25 requests a day and Pro starts at 46,000 a month. There is no throttling within the quota, so parallel requests are fine; once it is exhausted, only the reset or a bigger plan helps.

The pricing page lists every plan, and automatic proxy resolution comes with all of the paid ones.

03 · The site’s 429
Fewer hits, not faster retries.
A site that throttles by address responds to volume. Caching with ttl and staleTtl means the same URL is fetched once per cache lifetime, however many times your code asks for it.

When not to: a 403 from a login page or a paywall is an access decision, not a bot score. Forward your own session with private pages patterns where you are allowed to, and leave it alone otherwise.

FAQ

Why does my scraper get 403 Forbidden on some sites?

An antibot service in front of the site scored the request as automated, most often because of a datacenter IP, an inconsistent header set or a headless TLS fingerprint. The same code works on unprotected sites, which is why it only happens on some. Through the Pro endpoint those requests are escalated through the proxy tiers automatically.

How do I fix 429 Too Many Requests when scraping?

First check where it comes from. If the target site sent it, fetch each URL less often: cache responses with ttl and staleTtl so repeat reads never reach the site. If the Microlink API sent it with ERATE, you have used your plan quota and need to wait for the reset or upgrade.

Does changing the user agent fix a 403 when scraping?

Rarely. Antibot systems check the whole header set, the TLS handshake and the IP reputation, so a new user agent on the same datacenter IP sends the same signals with one string changed. Retrying the same way can make the next request look more suspicious, not less.

What does EPROXYNEEDED mean in a scraping response?

The free endpoint detected antibot protection on the target: “The URL provided uses antibot protection. Upgrade to a Pro plan.” It is the signal to upgrade, not to add a parameter. With a Pro key, the same request is routed through the proxy automatically.

Is a 429 from the scraping API the same as a 429 from the site?

No. A 429 with the ERATE code comes from the API and means your quota is used up; the production patterns guide shows how to wait for the reset. A 429 from the target shows up as the statusCode of the page you asked for.
Related use cases

Solve the next problem with the same API

Scrape Cloudflare-protected websites

Get the real page instead of “Just a moment”: on Pro plans, blocked requests escalate through proxy tiers up to residential IPs.

Detect antibot protection

Name the provider behind a block, from Cloudflare to DataDome to reCAPTCHA, with the open-source is-antibot library.

Rotating proxy alternative

Skip the proxy pool: the API escalates to residential IPs only when a site blocks you and remembers what works per domain.

Link previews for bot-protected sites

Unfurl links to sites behind Cloudflare or DataDome: on a Pro key the built-in proxy resolves automatically.

Markdown from bot-protected pages

Convert pages behind Cloudflare, DataDome or Akamai: on a Pro key the built-in proxy resolves automatically.

PDFs of bot-protected pages

Print the real page, not the challenge screen: on Pro plans the PDF request reaches protected sites through the built-in proxy.

Ready to stop retrying blocked requests?

Pro plans route antibot 403s through the proxy automatically and cache the result, so blocked targets stop being a retry loop.