Observed since 21 Oct, 10:20 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Verify a claimed AI-crawler identity: check the client IP against the operator's published ranges or forward-confirmed reverse DNS. 15 operator lists, 3052 prefixes bundled, zero dependencies.
A user-agent is a claim, not evidence. Anyone can send
Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot). The
operators that mean it publish a way to check — an IP-range file, or a
forward-confirmed reverse-DNS suffix. This package runs that check.
from ai_crawler_verify import verify
verify("132.196.86.1", ua="Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)")
# {'verified': True,
# 'method': 'published-ranges',
# 'operator': 'OpenAI',
# 'matched_prefix': '132.196.86.0/24',
# 'reason': "address is inside 132.196.86.0/24, published by OpenAI at https://openai.com/gptbot.json", ...}
verify("203.0.113.9", ua="GPTBot/1.2")
# {'verified': False, 'method': 'published-ranges',
# 'reason': 'address is in no prefix OpenAI publishes for GPTBot', ...}
Three verdicts, and they are deliberately different things:
verified | meaning |
|---|---|
True | the operator publishes a check and this client passes it |
False | the operator publishes a check and this client fails it — the claim is forged |
None | nobody publishes a check for this claim, so no verdict is possible and none is invented |
That third row is the point. Of the 150 crawlers in the table,
35 are verifiable from published IP ranges and 24 by
reverse DNS; for the rest there is nothing to check, and a package that returned
False for them would be lying about what it knows.
pip install ai-crawler-verify
Python >= 3.8. Zero dependencies, standard library only. No network at import: 3052 prefixes from 15 operator lists ship inside the wheel (107.0 KB), snapshot taken 2026-09-02T14:41:10+00:00.
ai-crawler-verify 132.196.86.1 --ua "GPTBot/1.2" # JSON verdict on stdout
ai-crawler-verify 66.249.66.1 --ua "Googlebot/2.1" # reverse DNS, forward-confirmed
ai-crawler-verify --sources # which operator lists are bundled
ai-crawler-verify <ip> --no-dns # never touch DNS: fully offline
ai-crawler-verify <ip> --refresh # fetch today's prefixes first
Exit status: 0 verified, 1 not verified, 2 no verdict possible, 3 usage.
That makes it usable straight from a log pipeline or a fail2ban action.
| Function | Returns |
|---|---|
verify(ip, ua=None, allow_dns=True) | the verdict dict above; never raises |
lookup(ip) | every operator prefix list that address falls inside |
reverse_dns(ip) | {hostname, forward_confirmed, error} — PTR plus the confirming forward lookup |
claimed_crawler(ua) | what the user-agent claims to be — a claim, never a verdict |
sources() / crawlers() | the operator lists, and every crawler with how it is verifiable |
refresh(url=None) | optional, the only network path — today's prefixes, cached 6 h on disk |
allow_dns=False guarantees the call makes no DNS query at all, which is what
you want inside a request handler; the range check is pure CPU either way.
Operators publish their ranges at a dozen different URLs in three different
shapes, some of them CORS-blocked and some of them slow. The
AI Crawler Index re-reads every one of them every six hours and
publishes the union in one document — that is what refresh() fetches and what
this snapshot was cut from. Every list is also linked back to the operator's own
endpoint in sources(), so you can check the mirror against the source.
ai-crawler-indexTrue means this address is in the list that operator publishes. If an
operator's list is stale or wrong, so is the answer.None rather than guessing.refresh() (it caches for six hours) or re-install.Independent, non-commercial, run by automation. Not affiliated with OpenAI, Google, Microsoft, Apple, Perplexity, DuckDuckGo or any other operator named in the table; every prefix list belongs to the operator that publishes it.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 1.0.1 | 2 Sept 2026 | 0 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 10:20 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:dfdc5d25c751720069e9793e9ab923e071e5c150dbda58b495bde3fea6001b1f