Skip to content

JSON API (/collections/<slug>/?fo=json) returns a Cloudflare challenge on the first request #88

Description

The documented JSON API returns an unconditional Cloudflare challenge to programmatic clients, on the very first request of a session. This appears to contradict the Library's own published guidance, and it makes the "Working within limits" documentation unreadable to the machine clients it was written for.

Reproduction
GET https://www.loc.gov/collections/national-screening-room/?q=train&fo=json&c=5
User-Agent: TubePilot/0.1 (single-operator studio)
HTTP/2 403
server: cloudflare
cf-mitigated: challenge
cf-ray: a37f1b9c8ec2b8a4-ARN

<title>Just a moment...</title> Observed 2026-09-08T15:50:16Z from a residential connection in Sweden (ARN edge). Reproduces on the first request of a session, from a cold client, with no prior traffic — so it is not rate-driven. It reproduces identically with a plain HTTP client and with a real headless browser, and adding ?fo=json makes no difference.

https://www.loc.gov/robots.txt is itself behind the same challenge, so a well-behaved client cannot read the crawl policy it is trying to obey.

Why this looks like a bug rather than policy
The API is documented as open. The LoC APIs page states the API is "accessible to the public with no API key or authentication required." There is no key programme, allowlist or partner tier to apply to, so a challenge is the only gate and it cannot be satisfied.

robots.txt permits this path. /search and /pictures/search are disallowed; /collections/ and /item/ are not. The blocked request above is to an explicitly permitted path.

The published rate limits are not being exceeded. The documented limits are 20/min for JSON and 150/min for Image/storage services, with /legal/ recommending no more than 10 requests per minute site-wide. The client above is spaced at 6 seconds — exactly 10/min — and clears the Crawl-Delay: 5 in robots.txt. The challenge arrives on request one regardless.

The documentation says challenges are load-driven. The guidance describes CAPTCHAs as appearing "when the API is experiencing especially heavy traffic load." This is not that: it is a single cold request, and it is consistent across days and times.

Other hosts behave correctly, which suggests the WAF rule is broader than intended. tile.loc.gov media paths, cdn.loc.gov, data.labs.loc.gov and id.loc.gov all answer normally to the same client. Only the JSON API on www.loc.gov challenges.

Context
I maintain a small single-operator tool that sources public-domain archival footage and stills for video production, and credits the Library as the source in a per-asset ledger. It reads /collections//?fo=json to find items, then downloads from tile.loc.gov. The download half works unattended and has never been challenged; only discovery is blocked.

The current workaround is to route search requests through a browser the operator has to keep open, because that browser holds a challenge-clearance cookie from a human solving it. That is worse for the Library than a plain rate-limited client: it is harder to identify, harder to throttle, and it requires a person to sit there.

What would help, in increasing order of effort for you
Confirm the intent. Are tile.loc.gov, cdn.loc.gov and data.labs.loc.gov deliberately exempt from the challenge, and will they stay that way? A lot of downstream tooling quietly depends on this and right now it is an observation rather than a commitment.

Publish moving-image inventory as a bulk metadata package on data.labs.loc.gov, beside the existing eleven. The free-to-use package already proves the shape works: it is fetched once, searched offline, and generates no ongoing load at all. This needs no WAF change and would remove most of my API traffic permanently.

Relax the challenge for /collections//?fo=json and /item//?fo=json for identified clients staying inside the published limits. This is the one that resolves the issue as filed.

Happy to supply more cf-ray values, test from other networks, or adjust my client in whatever way is most useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions