The 403 that isn't an outage — and why AI agents get it too
A bot shield returning 403 tells you nothing about whether a service works for humans. We built our whole scoring engine around that distinction. The same rule that blocks our prober now blocks the AI agents your customers are using to reach you — and unlike us, they don't file a report.
We probe a few hundred services on a schedule. A meaningful share of those probes come back 401, 403, or 429 — and none of those responses mean the service is down.
They mean a bot shield looked at our request and declined to answer. The service behind it is almost always fine. Users are sailing through. We just aren't a user.
This distinction is load-bearing for us: a monitoring tool that reports "Cloudflare said 403" as "the service is down" is a tool that cries wolf, and a tool that cries wolf gets ignored precisely when it matters. So our scoring engine neutralises those three status codes entirely — they're recorded as probe-blocked, never counted toward an outage score.
Why a 403 is not an outage
An HTTP status code answers a specific question, and it's important to be precise about which one.
500,502,503,504— the server tried and failed. This is an availability signal.401,403— the server understood you and refused. This is an authorisation signal.429— the server understood you and is throttling you. This is a rate signal.
Only the first group says anything about whether the service works. The other two say something about your relationship with the edge, and an automated client's relationship with the edge is not the same as a browser's.
Anti-bot systems make this determination from a bundle of signals: the user-agent string, TLS fingerprint, IP reputation and ASN, header ordering, whether you execute JavaScript, and how quickly you request things. A server-side monitor fails several of those tests by construction. It runs from a datacenter IP, it doesn't render JavaScript, and it announces itself honestly in the user-agent.
That last one is worth sitting with. Being honest about who you are is itself a bot signal. A well-behaved crawler that identifies itself is easier to block than one that pretends to be Chrome.
The same rule blocks AI agents
Here's the part that has changed recently.
For years the traffic hitting those bot rules was search crawlers, monitors, scrapers, and attackers. The calculus was simple: allow Googlebot, block most of the rest, don't think about it again.
Now a fourth category shows up in the same bucket — AI crawlers and agents fetching pages on behalf of a user who asked a question. They tend to arrive with identifiable user-agents:
GPTBot— OpenAI's crawlerChatGPT-User— fetches made when a ChatGPT user's request requires browsingClaudeBot— Anthropic's crawlerPerplexityBot— Perplexity's crawlerGoogle-Extended— a robots.txt token governing Google's AI usage rather than a distinct fetcher
Most default bot-management configurations were written before these existed. If your rule is "allow known search engines, challenge everything else," these agents get challenged — which for a non-browser client means blocked.
And the failure mode is completely silent. A blocked monitor retries and eventually pages someone. A blocked agent does neither. The user asked a question, your site returned a challenge page, and the answer got assembled from whoever did respond. You never see an error, because from your edge's perspective nothing went wrong — the rule worked exactly as configured.
Test it yourself in a minute
You don't need a tool to get a first read. Compare a browser-shaped request against an agent-shaped one:
# Browser-shaped
curl -sS -o /dev/null -w '%{http_code}\n' \
-A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36' \
https://example.com/
# Agent-shaped
curl -sS -o /dev/null -w '%{http_code}\n' \
-A 'ChatGPT-User/1.0 (+https://openai.com/bot)' \
https://example.com/
# Crawler-shaped
curl -sS -o /dev/null -w '%{http_code}\n' \
-A 'GPTBot/1.0 (+https://openai.com/gptbot)' \
https://example.com/If the first returns 200 and the others return 403, 429, or a 503 challenge, you have your answer.
Three follow-ups worth running:
- Check
robots.txtbefore blaming the WAF. A strayUser-agent: GPTBot/Disallow: /— sometimes added years ago during an AI-scraping panic, sometimes inherited from a template — is the most common cause and the easiest fix. - Fetch without JavaScript.
curla product or article URL and read the raw HTML. If the meaningful content only appears after hydration, an agent that doesn't execute JS sees an empty shell even when it gets a clean200. That's a different failure than a block, with the same outcome. - Test from a datacenter IP, not your laptop. Residential IPs have far better reputation. Running the same curl from a cheap VPS often reproduces a block you can't reproduce at home.
What to actually fix
In rough order of effort-to-payoff:
- Audit
robots.txtfor AI tokens and decide each one on purpose. Blocking training crawlers while allowing user-initiated fetchers likeChatGPT-Useris a coherent position; blocking both by accident is not. - Allowlist user-initiated agents in bot management if you want to be reachable in AI answers. These fetch a page because a human asked for it — closer to a visitor than a scraper.
- Serve meaningful HTML without JavaScript on pages that matter. Server-render the content, price, and availability. This helps agents, search crawlers, and users on bad connections simultaneously.
- Keep structured data accurate. Agents lean on it heavily precisely because it's unambiguous.
- Return honest status codes. If you're throttling,
429with aRetry-Afteris enormously more useful than a200containing a challenge page — which is indistinguishable from real content to anything that isn't a browser.
The connection back to status
We keep coming back to the same principle from opposite directions. A monitor that reads 403 as "down" is wrong. A site that returns 403 to a legitimate agent is also producing a wrong answer — it's telling a system "you may not look" and the system reports back "I couldn't find it."
Both are failures of interpretation at the edge, and both are invisible unless you go looking.
Frequently asked
Is blocking AI crawlers always the wrong call?
No. If your business is selling content access, blocking training crawlers is a perfectly rational decision, and plenty of publishers make it deliberately. The distinction worth drawing is between crawlers that ingest for training and fetchers that retrieve because a specific user asked a question right now. Those have different implications for your traffic, and a single blanket rule can't express that.
Why does my site return 200 to curl but agents still miss my content?
Almost always client-side rendering. A 200 with an empty <div id="root"> is a success as far as HTTP is concerned and useless to anything that doesn't run JavaScript. Fetch the URL with curl and search the raw HTML for a sentence you can see in the browser — if it isn't there, neither is it there for the agent.
Doesn't Cloudflare have a setting for this?
Cloudflare and the other major providers have added AI-crawler controls, and they're worth configuring deliberately rather than leaving at default. But the setting only governs the agents the provider recognises, and the list moves. Verification by request is still the only way to know what a given agent actually receives.
How does this relate to StatusDetector's own probing?
Directly. We probe from datacenter IPs with an honest, identifying user-agent — the exact profile bot rules are tuned to catch. That's why probe-blocked is a first-class state in our scoring rather than an error, and why a service showing "probe blocked" on our status pages is not a service we're claiming is down.