What one scan does,
request by request.
A public score is worth nothing if the measurement is a black box. This page is the whole procedure: the URLs requested, the user agents used, how robots.txt is read, how a block is classified, every point in the scoring table, and the cases where this method is known to be wrong.
The requests nine per scan
Five well-known URLs are requested in parallel, then the home page is requested four more times, once under each probed crawler's user agent. Nine requests, one origin, no crawl. No links are followed and no subresources are fetched.
| URL | User agent | Timeout | Body cap | What it is for |
|---|---|---|---|---|
| / | Browser | 10 s | 1,500,000 B | Baseline. The HTML a normal visitor gets, used as the control for every probe and for all readability and structure checks. |
| /robots.txt | CrawlCensusBot | 7 s | 400,000 B | Parsed per RFC 9309. Non-200, empty, or an HTML error page served at this path all count as absent. |
| /llms.txt | CrawlCensusBot | 7 s | 300,000 B | Validated against the llmstxt.org structure: an H1 title, a blockquote summary, at least one section, at least one link list item. |
| /ai.txt | CrawlCensusBot | 5 s | 60,000 B | Presence only. The format has no specification, so nothing is inferred from its contents. |
| /sitemap.xml | CrawlCensusBot | 7 s | 400,000 B | Counts loc elements. Falls back to the first Sitemap line in robots.txt when this path is absent. |
The baseline user agent is a current desktop Chrome string, because the question the report answers is what a crawler receives compared with what a person receives. Measuring both with a bot user agent would hide exactly the discrimination we are looking for.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
The well-known files are requested under our own identity, with a contact URL, so the hit is attributable in your logs.
Mozilla/5.0 (compatible; CrawlCensusBot/1.0; +https://crawlcensus.com/bot)
Bodies are read up to the cap and then the read stops; a truncated body is scored as truncated rather than as an error. Every request is sent with caching disabled at our edge, so a report reflects the origin at scan time and not a copy from hours earlier.
How robots.txt is interpreted rfc 9309
The parser follows RFC 9309, which is the specification the major crawler operators say they implement. The rules that decide a verdict, in the order they apply:
- Group selection by longest matching agent token. Consecutive
User-agentlines form one group with shared rules. For a given crawler the group whose token is the longest case-insensitive prefix match of the crawler's product token wins.Googlebot-Newsis governed by agooglebot-newsgroup even when agooglebotgroup also exists. - Wildcard fallback. With no explicit token match, the
*group applies. With no*group either, the crawler is unrestricted, and the report records that the outcome came from a default rather than from a rule. - Longest path wins, allow breaks ties. Inside the selected group every rule is matched against the path and the rule with the longest pattern decides. When an
Allowand aDisallowmatch with equal length,Allowwins. Rules in other groups are never consulted, which is why adding a permissive*group does not soften a specificDisallow. - Wildcards.
*matches any sequence of characters and a trailing$anchors the pattern to the end of the path, soDisallow: /*.pdf$blocks/a/b.pdfand not/a/b.pdf?x=1. Patterns with more than 20 wildcards degrade to a literal prefix match rather than compiling a pathological expression. - Edge cases that trip people up. An empty
Disallow:is a valid line that matches nothing, so it permits everything in its group.%2Fis normalized to one case before comparison so encoded slashes compare equal. Unknown or malformed fields do not abort the parse; they are recorded and shown in the report, because a typo in a field name silently disables the rule you thought you wrote.
Verdicts are evaluated at the site root, /. All 39 registry tokens are evaluated against the same document, so a report tells you which crawlers a rule actually covers rather than which ones you meant it to cover.
How a live block is classified probes
robots.txt is a request. The edge is the enforcement. A permissive file means nothing if a managed firewall rule returns 403 to any user agent containing "bot", so the home page is requested again under four real crawler user agents and compared against the baseline.
| Probe | Registry id | Why this one |
|---|---|---|
| GPTBot | gptbot | OpenAI training crawler |
| OAI-SearchBot | oai-searchbot | The fetcher behind ChatGPT search citations |
| PerplexityBot | perplexitybot | Perplexity's index crawler |
| ClaudeBot | claudebot | Anthropic's training crawler |
A probe response is classified as blocked when any of the following holds, tested in this order:
- a
cf-mitigatedresponse header, which is an explicit statement that a challenge was served - status
403,401,429or451 - a
5xxstatus where the baseline browser request did not produce one, so the failure tracks the user agent and not the server - a known interstitial body signature: a Cloudflare "just a moment" challenge, an "enable JavaScript and cookies to continue" page, a short "access denied" page, or a CAPTCHA page under 9 KB
- any
4xxwhere the baseline succeeded
A probe that gets no response at all is recorded as no response, not as a block. The distinction matters: an unreachable origin is not a policy.
403 is a lead. Where a probe and robots.txt disagree, the report shows both rather than reconciling them.The scoring table 27 checks, 100 points
Every check below is scored on every scan. Weights are fixed, published, and identical for every domain: there is no curve, no industry adjustment and no manual override. A check that cannot be evaluated is marked not-applicable and its weight is removed from both sides of the fraction rather than scored as a failure.
Reach
40 of 100 points · 9 checksWhether AI crawlers and answer-engine fetchers are permitted to request the page at all, in robots.txt, in robots directives, and at the edge.
| Check id | Condition | Weight | Why an answer engine cares |
|---|---|---|---|
| robots-missing | robots.txt is present and fetchable | 3 | Every compliant crawler requests /robots.txt before any other path. A 5xx or connection error there is treated as a temporary site-wide disallow by Googlebot and OpenAI's fetchers, so the whole origin stops being crawled until it recovers. |
| robots-blocks-training | Training crawler access in robots.txt | 8 | GPTBot, ClaudeBot, and CCBot collect pages into pretraining corpora, and Google-Extended and Applebot-Extended are opt-out tokens that govern whether already-crawled pages may be used for Gemini and Apple Intelligence. Disallowing them removes your text from the corpora models generalise from, which is a policy choice, not a bug. |
| robots-blocks-answer-engines | Answer-engine fetcher access in robots.txt | 12 | Retrieval fetchers (OAI-SearchBot, ChatGPT-User, Claude-User, Claude-SearchBot, PerplexityBot, Perplexity-User) load a page at answer time to quote and link it. When robots.txt disallows them the assistant drops the URL from its candidate set, so the citation goes to a competitor while your training exposure stays exactly the same. |
| robots-blocks-all | Blanket disallow for all user agents | 5 | A `User-agent: *` group with `Disallow: /` is the fallback rule for every crawler that has no group of its own, which today includes most answer-engine fetchers and every new one that ships. One line therefore removes the page from retrieval for agents you never named. |
| robots-crawl-delay | Crawl-delay directive in robots.txt | 2 | `Crawl-delay` is not in RFC 9309. Googlebot and OpenAI's fetchers ignore it, while some retrieval agents that do honour it will space requests seconds apart, which can exceed an answer engine's fetch timeout and leave the page uncited. |
| ua-block-detected | Edge or WAF blocking of AI user agents | 6 | A permissive robots.txt is irrelevant if the edge answers an AI user agent with 403, 429, or a JavaScript challenge. Retrieval fetchers do not solve interstitials, so the assistant records a fetch failure and answers from another source. |
| meta-noindex | Robots meta noindex on the page | 2 | `<meta name="robots" content="noindex">` tells crawlers to drop the URL from the index that answer engines query for candidates. Because the tag is only visible after the page is fetched, the URL is crawled and then discarded, so it can never surface as a citation. |
| meta-nosnippet | nosnippet or snippet length limits | 1 | `nosnippet`, `max-snippet:0`, and `data-nosnippet` forbid a search or answer surface from showing text extracted from the page. The URL can still be indexed, but the assistant has no quotable excerpt, which suppresses it in generated answers that require attributable text. |
| x-robots-tag | Restrictive X-Robots-Tag response header | 1 | `X-Robots-Tag` carries the same directives as the robots meta tag and is combined with it, with the most restrictive value winning. A header added by a proxy or framework default therefore overrides clean HTML without any visible trace in the markup. |
| Dimension total | 40 | Budget for Reach: 40 points. | |
Readability
25 of 100 points · 7 checksWhether a fetcher that does not execute JavaScript receives the actual content, in markup an extractor can segment.
| Check id | Condition | Weight | Why an answer engine cares |
|---|---|---|---|
| ssr-content-thin | Server-rendered body text volume | 9 | Most AI fetchers, including OAI-SearchBot, ChatGPT-User, Claude-User, and PerplexityBot, take the raw HTTP response and do not run JavaScript. A client-rendered page therefore arrives as an empty root element plus script tags, and the model has nothing to quote or summarise. |
| text-ratio-low | Ratio of visible text to HTML bytes | 4 | Extractors strip scripts, styles, and wrapper markup before passing text to a model, and a page where content is a small fraction of the payload loses more of it to boilerplate removal. Inline JSON state blobs and deeply nested wrappers also push real text past the truncation limit of a retrieval context. |
| main-landmark-missing | Primary content landmark element | 3 | Boilerplate removers use `<main>` and `<article>` to decide which subtree is the content and which is navigation, promo, and footer. Without a landmark the extractor guesses by text density and often keeps the nav while dropping part of the body. |
| heading-structure | Heading hierarchy of the document | 3 | Chunkers split long pages on heading boundaries and carry the nearest heading into each chunk's metadata. A page with no `h1`, several competing `h1`s, or levels that skip from `h2` to `h4` produces chunks whose topic labels do not match their text, so retrieval matches the wrong passage. |
| title-quality | Title element length and specificity | 3 | The `<title>` is the first text an indexer stores for the URL and is frequently reused verbatim as the link label in a generated answer. An empty, duplicated, or purely brand-name title gives the ranker no lexical signal for the page's actual topic. |
| description-missing | Meta description presence | 2 | The meta description is a short, author-written summary that retrieval systems index alongside the body and often surface as the preview line beside a citation. Without it the surface generates a summary from whatever fragment it extracted, which may be navigation text. |
| lang-missing | Document language declaration | 1 | `<html lang>` tells tokenizers and language filters which language the text is in. Without it a retrieval pipeline guesses, and a wrong guess drops the page from language-scoped candidate sets and degrades sentence splitting. |
| Dimension total | 25 | Budget for Readability: 25 points. | |
Structure
20 of 100 points · 6 checksMachine-readable markup that states the page's type, entities, canonical URL, and discrete facts instead of leaving them to be inferred.
| Check id | Condition | Weight | Why an answer engine cares |
|---|---|---|---|
| jsonld-missing | JSON-LD structured data presence | 7 | JSON-LD gives an answer engine typed facts (headline, author, dates, price, publisher) without inferring them from prose, and those fields populate the entity record a citation is attached to. With no structured data every attribute has to be guessed from text, and guesses are dropped when confidence is low. |
| jsonld-invalid | JSON-LD parses and uses valid schema.org terms | 3 | A structured data block is parsed as strict JSON in a single pass; one trailing comma, unescaped quote, or unquoted key discards the whole block. Misspelled properties and a missing `@context` or `@type` are silently ignored, so the page scores as having no structured data at all. |
| schema-type-coverage | Structured data type fits the page purpose | 4 | Answer engines route by entity type: a `Product` node supplies price and availability, an `Article` node supplies author and dates, and a `FAQPage` node supplies question and answer pairs. A generic `WebPage` or `WebSite` node on a product or article page carries none of those fields, so the specific facts stay unavailable. |
| canonical-missing | Canonical URL declaration | 3 | Tracking parameters, trailing slashes, and http and https variants make one document reachable at many URLs. Without `rel=canonical` the retrieval index can hold several near-duplicate records, splitting the signals that rank the page and making the cited link unstable. |
| sitemap-missing | XML sitemap declared and reachable | 2 | A sitemap gives crawlers the URL list and `lastmod` timestamps directly, instead of leaving discovery to link traversal that never reaches pages behind search forms or JavaScript routers. Fetchers use `lastmod` to prioritise recrawls, so fresh content is picked up sooner. |
| extractable-blocks | Discrete extractable content blocks | 1 | Lists, tables with header cells, and definition lists survive HTML-to-text conversion as discrete rows a model can lift into an answer verbatim. The same facts written as a paragraph, or laid out with positioned `div`s, lose their row and column relationships during extraction. |
| Dimension total | 20 | Budget for Structure: 20 points. | |
Attribution
15 of 100 points · 5 checksSignals that let an answer engine name the author, date the content, resolve the publisher, and cite it under known terms.
| Check id | Condition | Weight | Why an answer engine cares |
|---|---|---|---|
| llms-txt-missing | llms.txt curated content index | 4 | `/llms.txt` is a markdown file that points an assistant at the canonical pages for a site, so retrieval does not depend on which page a search happened to return. Its format is fixed: one `#` title, a `>` blockquote summary, then `##` sections of markdown links with short notes. |
| author-missing | Machine-readable author attribution | 3 | An `author` property in structured data is what lets an answer engine name a person or organisation as the source and link the byline to a stable profile. A byline that exists only as styled text is not reliably associated with the document during extraction. |
| date-modified-missing | Published and modified date metadata | 3 | Answer engines prefer recent sources for questions about current state and use `dateModified` to decide whether a cached copy needs refetching. With no machine-readable date the page is treated as undated and loses to competitors that publish one. |
| organization-schema-missing | Organization entity with sameAs links | 3 | An `Organization` node with `sameAs` links resolves your site to a single entity across knowledge graphs instead of leaving the publisher name as an ambiguous string. That resolution is what lets an assistant attribute an answer to your brand and reuse your name, logo, and contact details. |
| license-declared | Explicit reuse license or usage terms | 2 | A `license` property, or a linked terms page, states the reuse conditions in a place a crawler can read, rather than leaving them to be inferred. Where terms are unstated, some pipelines default to the more restrictive handling, which reduces how much of the text is quoted. |
| Dimension total | 15 | Budget for Attribution: 15 points. | |
Known limitations read this part
- One page, not a crawl. Every readability and structure check describes the home page. A site with an excellent home page and client-rendered documentation scores well here and still fails a crawler on the pages that matter. Treat the score as a sample, not a site-wide audit.
- No JavaScript, by design. Nothing is executed. This is not a shortcut, it is the measurement: most AI fetchers do not run scripts either, so the HTML we score is close to the HTML they score. It does mean a hydrated single-page app is judged on its shell, which is exactly the finding.
- robots.txt is a declaration, not enforcement. An
Allowproves intent and nothing more. Conversely aDisallowdoes not prove that anyone obeyed it. The registry records what each operator documents about honoring robots.txt, and several agents document that they do not, or that user-initiated fetches are exempt. Those exemptions are noted per crawler and cannot be verified from outside. - Two important tokens cannot be probed at all.
Google-ExtendedandApplebot-Extendedare robots.txt control tokens for AI training use, not crawlers. They send no user agent of their own, so their entries carry an empty user agent string and are scored from robots.txt only. A live probe is impossible, not merely omitted. - llms.txt is scored as a deduction, and that is a judgement call. A missing
/llms.txtloses points here. The file is a proposed convention with no formal adoption commitment from the major operators, and Chrome Lighthouse treats its absence as not-applicable rather than as a fault. Our position is that a machine-readable index of your own content is cheap and the downside is nothing, so it is scored. Reasonable people disagree; the weight is published above so you can subtract it. - Point-in-time. A report describes one moment. Rules change, firewalls learn, CDN configurations get deployed on Fridays. The change log exists because the single number is the least interesting part of the record.
- No relationship with any operator. Nothing here is confirmed by OpenAI, Anthropic, Google, Perplexity or anyone else. Registry entries are compiled from each operator's public documentation, linked on every crawler page so you can check them.
Corrections how to fix a wrong result
Results are automated, so some of them are wrong. The fastest fix is a re-scan, which is free, needs no account, and replaces the stored result immediately: run the domain again from the scan box.
If a re-scan reproduces a result you believe is wrong, mail hello@crawlcensus.com with the domain and what you expected. Useful in the mail: the timestamp of the report, and any edge or WAF rule you think fired. We will re-run it from a different path and either correct the record or tell you what the origin returned and why it was classified the way it was.
When a methodology error is found, the fix is applied to the whole corpus rather than to the domain that complained, and the change is noted on this page. A registry entry that misstates an operator's documented behaviour is corrected on sight. A domain owner can have a report removed from the public census on request, with no requirement to explain.