Text Handling
Source retrieval

Fetching sources that refuse

Every endpoint that reads a URL goes through one fetch path. A large share of publishers, repositories, and government hosts refuse an ordinary server-side request while serving the same file to a browser. This is what the service recognises, what it does about it, and — the part that costs the most time to learn — what does not work.

01A refusal is not a missing file

The failure that matters is the one that looks like success. A shielded host answers 200 or 202 with Content-Type: text/html and either no body or a challenge page. Trusting the status line or the content type reports a live document as a paywall, a landing page, or an unparseable file. Body evidence outranks headers, and these signals escalate rather than resolve:

Signals treated as a refusal
SignalWhat it actually meansSeen on
202, empty bodyRequest accepted, file withheld pending a challenge the client did not runAWS WAF (bepress / Digital Commons repositories)
401 · 403 · 429 · 451Refused outright — often on agent string alone, not identityPublishers, journals, some .gov hosts
200, Content-Length: 0A refusal wearing a success codeCDN edge rules
Challenge markers in the first bytesAn interstitial served in place of the file: awsWafCookieDomainList, gokuProps, challenge-platform, _Incapsula_Resource, and similarAWS WAF, Cloudflare, Imperva, DataDome, PerimeterX
%PDF- in the first bytesNot a refusal — this wins over any status or content typeHosts that serve real files under a 403

02Three rungs, cheapest first

An ordinary response never escalates; only a refusal does. The rung that produced the bytes is reported as fetch_path (and X-Source-Fetch-Path), because a document that could only be retrieved through a proxy or a browser is not ordinarily reachable, and a caller should be able to say so.

The three-rung source fetch ladder A direct request is checked for refusal. If it was refused, the fetch retries through a residential exit, then through a real browser. Any rung that returns the file exits immediately; if all three are refused, the origin's own status is returned. direct browser-realistic headers residential Oxylabs, sticky per host browser Playwright runs the challenge refused origin status refused refused refused bytes + fetch_path one 50s budget for all three — nginx cuts a synchronous request at 60s a rung that cannot finish inside what is left is skipped and named in the error
Each rung exits on success. Only a refusal moves right.

03What does not work

These are the dead ends, kept here because each one costs a day to rediscover.

Approaches that fail, and why
ApproachOutcomeWhy
Residential IP against a JavaScript challenge Still refused AWS WAF and Cloudflare challenges are proof-of-browser, not proof-of-IP. A residential exit returns the same 202, only from a nicer address.
Navigating a browser straight at the file No document Chromium treats a PDF URL as a download, not a page, so there is nothing to read. The challenge is bound to the origin anyway — load that first, then fetch in-page.
A commercial rendering proxy Works, priced out It does return the file, at ~25 credits per request against a small monthly pool. Self-hosted Playwright returns byte-identical results for free.
Peeking at a streamed body to detect a challenge Corrupts the download Reading the first bytes consumes them. An ordinary stream is therefore never peeked; when suspect headers force a peek and the body turns out to be the real file, the bytes are put back before the caller sees it.
Escalating without a wall-clock budget Gateway timeout Three rungs in sequence outlive nginx's 60s read timeout, and the caller gets an HTML 504 instead of a reason. The ladder shares a 50s budget and names any rung it had to skip.
Escalating every result on a list Unaffordable Proxy traffic is metered and the browser rung costs ~20s. Escalation is per document, on request — a page of ten links uses ten cheap direct probes.

04When every rung is refused

The origin's own status and message are returned. A block is never smoothed into an empty document, a partial extraction, or a generic error — a caller that cannot tell "this host refuses us" from "this file is not there" will present one as the other.