Skip to main content

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 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.

Install

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.

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.

Limits, stated plainly

  • A True means 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 None rather 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.

Metadata

Release files for ai-crawler-verify 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ai-crawler-verify 1.0.1
File Size Uploaded
ai_crawler_verify-1.0.1.tar.gz 30.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ai-crawler-verify 1.0.1
File Interpreter ABI Platform
ai_crawler_verify-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 58.6 kB

Release files / ai_crawler_verify-1.0.1.tar.gz

Download URL ai_crawler_verify-1.0.1.tar.gz
Size 30.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2494240676a9e63b41812a895205bb0befdbb6a9da21237a4f6d958d58e1bfc3
BLAKE2b-256 checksum
How to use checksums
c75f491ef6246fb082d8a78036aca22fc7004a793b860fcf6b75ca5e44bbba3d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.2

Release files / ai_crawler_verify-1.0.1-py3-none-any.whl

Download URL ai_crawler_verify-1.0.1-py3-none-any.whl
Size 28.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11ac3efe05e112811bd1538d2b76a4d536fd51a1a167ef2bcdfd531dc0e2138d
BLAKE2b-256 checksum
How to use checksums
016a89da381c96ec2ead5a9faae63e2d895a97616e6b6b8baa2333345df42af0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.2

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page