ai-crawler-verify
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 56 crawlers in the table,
15 are verifiable from published IP ranges and 9 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.
Install
pip install ai-crawler-verify
Python >= 3.8. Zero dependencies, standard library only. No network at import: 2949 prefixes from 12 operator lists ship inside the wheel (78.5 KB), snapshot taken 2026-09-01T14:39:48+00:00.
Command line
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.
API
| 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.
Why the prefixes are mirrored
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.
- The index: https://www.pathwren.workers.dev/c/pypi-verify/
- The prefix union this package uses: https://www.pathwren.workers.dev/c/pypi-verify/ip-ranges/all.json
- Companion package, offline user-agent classification:
ai-crawler-index
Limits, stated plainly
- A
Truemeans this address is in the list that operator publishes. If an operator's list is stale or wrong, so is the answer. - Reverse DNS is only as good as the hostname suffix the operator documents.
Where this package has no documented suffix it reports the resolved hostname
and returns
Nonerather than guessing. - Prefix lists rotate. The bundled snapshot is a snapshot: for a long-running
service, call
refresh()(it caches for six hours) or re-install. - Data: CC0-1.0. Code: MIT.
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 files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_crawler_verify-1.0.0.tar.gz.
File metadata
- Download URL: ai_crawler_verify-1.0.0.tar.gz
- Upload date:
- Size: 26.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c575e67ae41a6417885f6cf29650d0fbedfc3b0bc5054c32026a56aef95077d6
|
|
| MD5 |
c9592d5cf720b24ab2d107fe0ca0da34
|
|
| BLAKE2b-256 |
6a5ec2b8e41cc1ef743f906cba06b7f77776a5402d99e9ee0050433aa429350b
|
File details
Details for the file ai_crawler_verify-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ai_crawler_verify-1.0.0-py3-none-any.whl
- Upload date:
- Size: 25.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
451fe400458824613f8eb266e207d7a88b2b7aba0306d7ca7b4eded5dbe59b82
|
|
| MD5 |
abf6e10816f0bbab81e869150f820069
|
|
| BLAKE2b-256 |
dd61b6d7b279d66a94469ef9ab5ce5a8bff31221362d6f85b418f025b0c5daf6
|