A reader-style objection to our own data: when the tracker marks a PerplexityBot hit “unverified,” maybe that is our failure to recognise legitimate traffic - a stale IP manifest, or vendor stealth-crawling from undisclosed IPs. Perplexity has a documented history of exactly that. So we went looking for our mistake.

We did not find our mistake. We found a costume rack.

How verification works here

For Perplexity and OpenAI the tracker checks the source IP against the vendor’s own published manifest (perplexity.ai/perplexitybot.json, OpenAI’s ranges). For Google, Bing and Apple it does reverse-DNS with forward confirmation. verified = 1 means the IP is in the vendor’s published infrastructure; verified = 0 means it is not.

Over the 80-day window, the spoof rates for the classes that can be checked:

class total verified fake % fake
google-extended 6 0 6 100%
perplexitybot 30 3 27 90%
chatgpt-user 27 3 24 89%
oai-searchbot 218 189 29 13%
applebot 152 133 19 12%
gptbot 666 647 19 3%
bingbot 856 841 15 2%
google-extendedperplexitybotchatgpt-useroai-searchbotapplebotgptbotbingbot 100% 90% 89% 13% 12% 3% 2%
Figure 1 - Share of each class that failed IP-range / rDNS verification. The classes whose vendors publish verifiable ranges (GPTBot, Bingbot) sit at 2-3%. The ones people most want to measure (Perplexity, ChatGPT-User) sit at ~90%.

The objection says: maybe that 90% is our blind spot. Two behavioural signals, independent of the IP check, close that door.

Proof one: impossible identity

The fake AI-fetcher hits trace to a pool of nine cheap-VPS IPs, each rotating up to twelve distinct crawler identities. One address, 45.87.41.224 (SpectraIP, Netherlands), presented as all of these:

45.87.41.224 SpectraIP, NL googlebot gptbot claudebot oai-searchbot perplexitybot applebot amazonbot meta-external bytespider google-extended ccbot (+ browser + cf-internal UAs)
Figure 2 - A single IP presenting as eleven mutually-exclusive corporate crawlers. No address is simultaneously Google, OpenAI, Anthropic, Apple, Meta, Amazon and ByteDance infrastructure.

Proof two: intent

The same IPs are not indexing. They hunt for secrets: /.env, /.env.backup, /gcp-credentials.json, /service-account.json, /serviceAccountKey.json, /docker-compose.yml, /graphql/console. No legitimate AI crawler scans for service-account keys. This is credential scanning wearing a bot costume.

Share of each class’s fakes that traces to this nine-IP pool: perplexitybot 27/27, chatgpt-user 24/24, oai-searchbot 29/29, gptbot 19/19, google-extended 6/6, applebot 18/19. In this dataset the entire apparent Perplexity / ChatGPT-User / OpenAI-search presence is one small impersonation pool.

The real ones are cleanly separable

Every verified = 1 Perplexity hit came from Amazon / AWS (Perplexity’s real infrastructure), fetched /robots.txt and content normally, and matched the published perplexitybot.json manifest. The manifest verified those AWS IPs correctly, so the “fakes” are not a broken-manifest false negative. Real Perplexity showed up a handful of times and behaved. The costume rack did the rest.

Cloudflare only vets Googlebot

One more asymmetry. Of the fake Googlebot hits, 436 of 453 got a 403 - Cloudflare’s managed reverse-DNS verification catches and blocks them. The fake Perplexity, ChatGPT-User, OAI-SearchBot, GPTBot and Applebot hits got zero 403s. They passed through as ordinary 200s. Cloudflare hard-verifies Google; the rest are on you.

(The Googlebot fakes are a separate event from the nine-IP pool: 435 of 455 came in a single day, 2026-07-31, from one Dutch hosting ASN flooding / with ~3,000 requests. Different actor, same lesson.)

What this means for site owners

Counting “AI crawler visits” by user-agent is wrong, and wrong in a specific direction. For Perplexity and ChatGPT-User the raw UA count is roughly ten times the verified count. A dashboard reporting “Perplexity crawled you 300 times” is very likely showing a scanner in a Perplexity costume for most of them. GPTBot and Bingbot, which publish verifiable ranges, are only 2-3% inflated - trust those.

Three checks, any one strong, all three dispositive:

  1. IP-range / rDNS against the vendor’s published manifest.
  2. Behaviour - anything scanning for .env or service-account keys is not a real AI crawler.
  3. Per-IP UA rotation - group by source IP, count distinct vendor identities. One IP claiming to be Google and OpenAI and Anthropic is your fastest smoking gun in a client log.

What this does not prove

  • The pool is small in absolute terms (~198 hits) on a low-traffic cold-start domain. The ratio and the behaviour are the finding; absolute counts differ on a bigger site.
  • It does not identify who runs the pool. The ASNs (Hostodo, Infraly, SpectraIP, Advin, Contabo) are the packet origin, not the actor.
  • It does not rule out genuine vendor stealth-crawling in general - only in this window, where no clean-behaving unverified fetch exists to support that reading.

Method notes

Raw IP is retained only for verified = 0 rows (for forensics) and nulled for verified bots (anonymised) - so the per-IP costume analysis is possible precisely because these failed verification. UA classification is regex; verification is IP-range (ip-range method) or reverse-DNS (rdns). The nine-IP pool is defined as distinct source IPs presenting two or more vendor bot classes.


Data availability: methodology, tracker code and classifier are public at github.com/Qbeczek1/jsseo-dev.

Bias disclosure: I run JS SEO Lab as an independent technical SEO research project, and do paid technical SEO and AI-visibility audits through FratreSEO. No crawler vendor, search engine, or AI company funds this work.