How this tool works

letthebots.in is free with no paid upsell built into the tool, so the only thing left to earn is trust. This page is the complete methodology: every request we make, every detection rule, and every point value, including the parts we deliberately don't check.

The pipeline

A scan makes six network requests, all in parallel, none allowed to block the others. The whole thing runs against public data only: no login, no JavaScript execution, no browser.

01
Normalize & guard

Reject private/loopback hostnames, coerce to http/https, rate-limit to 10 scans per IP per 60s.

02
Fetch, in parallel
GET /robots.txt 6s GET page as OAI-SearchBot UA, 8s GET page as default UA, 8s GET /sitemap.xml 6s GET /llms.txt 6s Wikidata search 3s

All six run via Promise.allSettled, so one slow or failing request never blocks the others.

03
Parse

robots.txt groups → per-bot verdicts · JSON-LD extraction · heading/meta structure · bot-visible text extraction · sameAs entity matching

04
Score & explain

Six category scores sum to 0–100 → a band. Every deduction emits a named finding with its own fix.

Site-level files (robots.txt, sitemap.xml, llms.txt) are always fetched from the domain's origin. Page-level analysis (structured data, headings, readability) runs against the exact URL you submitted, so scanning an article URL doesn't silently fall back to homepage signals.

The three-class crawler model

Every AI crawler falls into one of three intents, and the intent determines what blocking it actually costs you. This is the distinction most robots.txt advice misses: "block AI bots" is not one decision, it's at least three.

Training

blocking is fine, an IP/privacy choice

  • GPTBot OpenAI · ChatGPT
  • ClaudeBot Anthropic · Claude
  • Google-Extended Google · Gemini
  • Applebot-Extended Apple · Apple Intelligence
  • CCBot Common Crawl
  • Meta-ExternalAgent Meta · Meta AI
  • Bytespider ByteDance
  • AI2Bot Allen AI
  • Diffbot Diffbot
  • cohere-training-data-crawler Cohere
  • Webzio-Extended Webz.io

User-triggered

blocking breaks "fetch this page for me"

  • ChatGPT-User OpenAI · ChatGPT
  • Claude-User Anthropic · Claude
  • Perplexity-User Perplexity · Perplexity
  • Meta-ExternalFetcher Meta · Meta AI

Training crawlers feed model pre-training. Blocking them is a legitimate IP or privacy decision and this tool does not penalize it. Search / retrieval crawlers power live citations. Blocking one of these is the single highest-severity finding the scan can produce. User-triggered crawlers fetch a page only when a person explicitly asks an assistant to open or summarize that URL; blocking them doesn't reduce your general visibility, but it does mean a user pasting your link into ChatGPT or Claude gets a dead end. A few bots (noted individually) assert they don't honor robots.txt at all, so the scan flags those separately rather than reporting a "blocked" verdict robots.txt can't actually enforce.

robots.txt parsing

Rules are grouped by User-agent: line. A crawler's own named group (e.g. User-agent: OAI-SearchBot) always overrides a wildcard User-agent: * group, even if the wildcard group appears first in the file. Within whichever group applies, the longest matching path wins: not rule order, not Allow-before-Disallow. That single rule is why "Disallow everything, then Allow a subfolder" reliably does what site owners intend, and why forgetting it produces false "blocked" reports.

Request /blog/public/x Disallow: /blog/ match length 6 Allow: /blog/public/ match length 13 wins Allowed 13 > 6

Both rules match the request path. robots.txt precedence goes to the longer matching path, not the order rules appear in, so the nested Allow wins even though Disallow: /blog/ comes first in the file.

The scan also flags patterns beyond a simple allow/block verdict: a wildcard Disallow: / that blocks every AI search bot at once (the signature of an SEO plugin's "block AI crawlers" toggle left on by default), robots.txt that explicitly names training bots but has no rules at all for search/retrieval bots (the most common silent loss: nothing is technically "blocked," the site owner just never opted in), and Crawl-delay values of an hour or more aimed at AI bots specifically, which throttle indexing as effectively as a block would.

28 pts max

Access

The gate. If a crawler can't reach your content, nothing else on this page matters, which is why Access is worth more than any other category.

PointsWhat earns them
20 ptsShare of search/retrieval-class bots that are allowed or unspecified in robots.txt (unspecified = default-allowed), scaled linearly. All blocked → 0 of these points; all reachable → full 20.
4 ptsSame calculation, scaled to 4 points, for user-triggered bots (ChatGPT-User, Claude-User, Perplexity-User).
3 ptsFull credit unless the request made with an AI crawler's user-agent came back with a 401/403, or a page that looks like a CAPTCHA/bot-challenge screen, while the same request with a normal browser user-agent succeeded. This catches WAF and bot-management rules that robots.txt can't see, because the request is refused before robots.txt is ever consulted.
1 pt/sitemap.xml returns 200.
-3 ptsA penalty, not a missing bonus. Applies if the scanned page carries a noindex directive, via either a <meta name="robots"> tag or an X-Robots-Tag response header.

A missing robots.txt (non-200 response) is treated as "everything allowed by default," per the robots.txt spec, but the scan reports it as a hygiene gap regardless, since having no explicit rules also means you have no way to opt specific bots in or out later.

If the homepage request itself is refused (401/403/429/451, or a 200 that's actually a bot-verification challenge page), none of the above runs: there's no score, and no claim about your H1, meta description, or structured data, because we never received the HTML to check it in. See "We are not a verified crawler" below for why that refusal doesn't mean AI crawlers can't reach you either — and Common Crawl for how we recover a content-only read in that case.

14 pts max

Readability

Being "allowed" doesn't help if there's nothing to read. AI crawlers fetch raw HTML. They do not run your JavaScript bundle, so anything that only exists after client-side rendering is invisible to them, full stop.

RuleEffect
JS-only shell detected×0.3
Paywall language detectedcapped at 5
Login gate, no paywallcapped at 6

"JS-only" is detected by two signals together: a known SPA root element (#root, #app, #__next, #__nuxt) and fewer than 200 characters of visible text in the raw HTML body. Either signal alone isn't enough: plenty of non-SPA pages happen to have a div id="app" somewhere, and plenty of legitimately short pages aren't JS shells. Paywall detection looks for common phrasing ("subscribe to read," "members only," "sign in to view") rather than a specific paywall vendor, so it catches custom implementations too. A login gate is a password input combined with under 600 characters of visible content, on the assumption that if there's a login form and almost nothing else, the real content is behind it.

16 pts max

Structured data

Structured data is how AI systems identify what your site is and who runs it with confidence, instead of guessing from prose. JSON-LD is the only format that earns meaningful points. It's the format every major AI vendor's documentation recommends, because it's unambiguous and separated from display markup.

PointsWhat earns them
4 ptsAt least one valid <script type="application/ld+json"> block parses successfully.
1 ptNo JSON-LD, but microdata (itemscope) or RDFa (typeof=) is present: partial credit for having structured data in a format AI parsers trust less.
+3 pts@type: Organization present anywhere in your JSON-LD (including nested inside a @graph).
+2 pts@type: WebSite present anywhere in your JSON-LD (including nested inside a @graph).
+2 pts@type: FAQPage present anywhere in your JSON-LD (including nested inside a @graph).
+2 pts@type: Article present anywhere in your JSON-LD (including nested inside a @graph).
+2 pts@type: TechArticle present anywhere in your JSON-LD (including nested inside a @graph).
+1 pt@type: BreadcrumbList present anywhere in your JSON-LD (including nested inside a @graph).
+1 pt@type: DefinedTerm present anywhere in your JSON-LD (including nested inside a @graph).
+1 pt@type: ProfilePage present anywhere in your JSON-LD (including nested inside a @graph).

Type points stack. A page with Organization, WebSite, and FAQPage schema earns all three. There's no cap below the category max, but the six-plus types above already sum past 16, so realistic coverage tops out at the category ceiling regardless.

18 pts max

Authority & entity

This is the category AI systems weigh most heavily when deciding whether to trust and attribute a source. Structured data tells a crawler what a page says; authority signals tell it who's saying it.

PointsWhat earns them
7 ptssameAs links to 3 or more of: Wikipedia, Wikidata, LinkedIn company/profile, Crunchbase, X/Twitter, GitHub.
4 pts1–2 of the same sources (mutually exclusive with the 3+ tier above: you get one or the other, not both).
4 ptsA live search against Wikidata's API, using your domain-derived brand name, returns a match.
6 ptsBoth a Person/ProfilePage schema type and an HTML author byline are present.
4 ptsPerson schema only, no HTML byline.
2 ptsHTML byline only (an element with class/itemprop="author" or rel="author"), no Person schema.
1 ptA link to /about or /about-us exists anywhere on the page.

The Wikidata check is the one live third-party lookup in the whole pipeline. Everything else is derived purely from what your own site returns. It runs with its own 3-second budget and never blocks or fails the rest of the scan if it times out.

14 pts max

Extractability

Reachable, readable, and identifiable still isn't the same as quotable. This category measures whether your content is structured the way AI systems actually lift text from. A page can pass every check above and still be one giant unbroken paragraph that's hard to extract a clean answer from.

PointsWhat earns them
3 ptsExactly one <h1> present.
-1 ptPenalty for more than one <h1>: ambiguous about what the page is primarily about.
2 ptsAt least one <h2> alongside the <h1>: a real heading hierarchy, not just a title.
3 pts<title> present and between 10 and 70 characters.
1 pt<title> present but outside that length range (partial credit, better than nothing).
2 ptsA <meta name="description"> tag with content is present.
2 pts each, capped at 4Per quotable-format signal detected: question-style headings, lists (<ul>/<ol>), tables, or blockquotes.
10 pts max

Freshness & hygiene

The smallest category, deliberately: these are real signals, but none of them individually determine whether AI engines can find or cite you the way Access does.

PointsWhat earns them
2 pts/llms.txt returns 200.
2 ptsA recency signal is present: dateModified/datePublished in JSON-LD, an article:modified_time meta tag, or a <time datetime> element.
2 ptsA <link rel="canonical"> tag is present.
1 ptSite is served over HTTPS.
1 ptTime to first byte on the page fetch is under 3 seconds.
2 ptsThe sitemap includes <lastmod> timestamps.

llms.txt is weighted deliberately low. There's little public evidence today that any major AI vendor's retrieval pipeline reads it, and no vendor has committed to supporting it as a ranking or citation input, so this tool reports it as hygiene, not as something that will move your citation rate. If that changes, the weight changes; nothing on this page or the score card overstates it in the meantime.

Score composition

The six category scores sum directly to your 0–100 total. There's no additional weighting or normalization step.

28
18
16
14
14
10
Access · 28 pts
Authority & entity · 18 pts
Structured data · 16 pts
Readability · 14 pts
Extractability · 14 pts
Freshness & hygiene · 10 pts

Bands

ScoreBand
0–39Invisible
40–64At risk
65–84Solid
85–100Citation-ready

Here's one finding traced end to end, from what the scanner detected to the fix it generated:

Detected

Homepage HTML has no <script type="application/ld+json"> block, and no Organization @type.

Finding

missing-organization-schema (severity high): "Missing Organization schema"

Deduction

Structured data scores 0 of the 3 points Organization is worth, out of 16 max for the category.

Generated fix

A ready-to-paste <script type="application/ld+json"> block with @type: Organization, your domain-derived name, and a sameAs array to fill in.

Bot view vs. browser view

Every scan fetches the same URL twice, once with a normal browser user-agent and once with an AI crawler's user-agent, and strips both responses down to plain text the same way an extractor would (scripts, styles, comments, and markup removed; block-level tags become line breaks so headings and paragraphs stay separable).

Browser (Chrome UA)
1,840 words
AI crawler (OAI-SearchBot UA)
410 words

Same URL, two requests, two user-agents. When the crawler's share drops below half the browser's (and the browser count is large enough to mean something), that's cloaking suspected: the fix that made the site "readable" in a browser tab never reached the bot.

This catches two distinct problems. A large gap between the two word counts, when the browser count is substantial, flags user-agent-based cloaking: a CDN or WAF rule serving different content by user-agent, intentionally or not. A very low bot-view word count regardless of the browser count flags JS-only rendering: there was never a cloaking rule, the content genuinely doesn't exist until the browser runs your JavaScript.

Common Crawl

Common Crawl is a public web archive that a large share of LLM training pipelines draw from, directly or indirectly. This section is historical context, not a live citation signal. It tells you whether CCBot was allowed to crawl you in the past, and whether it actually captured your page when it tried.

now (live probe) Jan robots.txt blocks CCBot →

Bar color = whether CCBot was allowed in that crawl's robots.txt. Bar height = whether the page was actually captured. The dashed bar is a live probe run right now, not archived history. CCBot can be re-allowed today even after months of being blocked.

The scan also runs a live probe against your current robots.txt (using CCBot's user-agent, not its IP; a UA-only check can't perfectly replicate what CCBot's actual crawler sees, so this is reported as indicative, not definitive) and, where detectable, names the likely source of a block: certain hosting platforms and CDNs ship a managed robots.txt template that blocks CCBot by default.

When our own homepage request is refused (see Access), we separately check whether Common Crawl already holds a capture of the page — CCBot is a verified crawler at most WAFs, so it's often let through where we aren't. If one exists, we run the same content analysis (structured data, headings, meta description, author signals) against that archived HTML and show it as its own section, dated to the capture, with no score attached. Common Crawl stores raw HTML, which is exactly the non-JavaScript view a real AI crawler gets, so this isn't a workaround for the refusal, it's the same underlying question — "is this page structured for AI citation" — answered from a copy we didn't have to fetch ourselves. It says nothing about whether OAI-SearchBot or ClaudeBot can reach you today; that's still unknown, and stays unknown.

Limits

A methodology page that doesn't say what it can't do is marketing, not methodology. Here's what this tool structurally cannot tell you:

  • It doesn't measure actual AI citations. Nothing here confirms ChatGPT, Claude, or Perplexity have ever cited you, or will. It checks the technical preconditions (reachability, readability, structure, identity) that citation depends on, not the outcome itself.
  • It checks one page, not your whole site. The homepage (or the specific URL you paste) plus robots.txt, sitemap.xml, and llms.txt at the domain root. A deep article three clicks in might have completely different structured data or heading structure, and this scan won't see it.
  • It never executes JavaScript. Every fetch is a raw HTTP request read as text. If your content requires client-side rendering to appear, the scan sees exactly what an AI crawler sees: nothing, or close to it. That's the point, but it also means the scan can't distinguish "broken" from "renders fine in a real browser, just not here."
  • Some bots ignore robots.txt regardless of what it says. Where a crawler is known to not honor robots.txt rules (Perplexity-User is the current example, per its own published documentation), the scan reports that explicitly instead of a misleading "blocked" verdict robots.txt can't actually enforce.
  • llms.txt is treated as hygiene, not a citation driver. See the Freshness & hygiene section above for why.
  • We are not a verified crawler, and can't offer IP allowlisting. This scan runs on Cloudflare Workers, sending requests as LetTheBotsIn-Checker/1.0 (+https://letthebots.in/about) from Cloudflare's large, shared, dynamic edge IP pool, not a small fixed range. Enterprise bot managers (Akamai Bot Manager, Cloudflare Bot Management, Imperva, DataDome) verify real AI crawlers by source IP against each vendor's published ranges, and treat everything else, us included, as unverified traffic. So when your site refuses us, that refusal is evidence about our standing with your WAF, not about whether OAI-SearchBot, ClaudeBot, or PerplexityBot get through. The scan reports that distinction explicitly rather than scoring your site down for it, and the fix it suggests is to check your own server or CDN logs for those crawlers' published user-agents, since that's the only vantage point that can actually answer the question.

Stack

Astro, deployed on Cloudflare Workers. Stateless by default: a scan is computed fresh on every request and nothing about your site is stored. The two exceptions are an email address if you explicitly ask to be monitored for regressions, and a cached Common Crawl API response (shared across users, keyed by domain, to avoid re-querying Common Crawl's own rate-limited API on every visit). Both live in Cloudflare KV.