Search for a generative engine optimization checker and you get a form. Paste a URL, wait five seconds, read a score out of 100 and a list of fixes. Those tools are useful for a first look. They test one page against a fixed rubric, which is a smaller job than the name implies.
What a GEO checker grades
The free checkers converge on the same rubric, because the public signals are the same for everyone. Whether robots.txt allows the named AI crawlers. Whether an llms.txt file exists at the domain root. Whether the page has a title, a meta description and a heading structure. Whether the page carries any structured data. Whether the visible text runs long enough to quote.
Each of those is a real signal. Graded on one URL they produce a number you cannot act on, because the number does not say which page or which directive caused it.
Where one-page checkers stop
Robots.txt is not the only place access is denied. A site can allow GPTBot in robots.txt and still block it at the edge, because a CDN bot-management toggle labelled something like "block AI bots" sits in front of the origin. Reading robots.txt gives you the declared policy, not the response the crawler receives.
The homepage is also the page most likely to be fine. Marketing sites are hand-tuned at the root and templated everywhere else, so the rendering problem that matters appears on the product or docs template. A one-page check never loads one.
Rendering is not testable by fetching HTML. Many agents read raw HTML without executing JavaScript, so establishing whether content exists means comparing the raw response against the rendered DOM. A five-second form check does not render.
And a score on its own answers the wrong question. What matters between deploys is whether the score moved, which needs a check that runs unattended and exits non-zero on a regression.
The checklist a full GEO check runs
Most of these are agent experience rules, the AX category, which --rule-include ax scopes a run to. The last group sits outside it: the schema, content and cloud AI rules belong to their own categories and only run in a full audit.
Crawler access, per named bot
ax/ai-crawlers classifies AI crawlers into four groups and reports which ones robots.txt blocks at the site root, meaning a Disallow: / that no Allow: / re-permits. The group matters more than the count. Training crawlers such as GPTBot, ClaudeBot and Google-Extended affect one vendor's future model training, and blocking them carries no penalty. AI-search indexers such as OAI-SearchBot, Claude-SearchBot and PerplexityBot decide whether you appear in AI-generated citations. User-action fetchers such as ChatGPT-User, Claude-User and Perplexity-User serve a live request a person made inside an assistant. Blocking those last two groups is usually unintentional, and usually follows from assuming a vendor ships one bot rather than three.
ax/agent-blocking covers what robots.txt cannot show. It requests the homepage as an AI user-agent and compares the response against a browser request, so a WAF or bot-management rule returning a challenge page to Claude-User surfaces here.
ax/pay-per-crawl probes the site as AI user-agents and reports monetized access, distinguishing Cloudflare's Pay Per Crawl headers from an x402 JSON payment body. Charging bulk crawlers is a business decision. Charging a user-action fetcher bills a person who is waiting for an answer.
The raw HTML an agent receives
ax/content-without-js compares the raw HTML against a separately rendered DOM and flags main content that only appears after JavaScript runs. The render side is a cloud service, so the rule skips on a logged-out local audit.
ax/token-weight estimates the token cost of the page HTML and reports the ratio of visible text to markup. It warns below 15 percent. A page at 3 percent spends most of an engine's token budget on wrapper divs, inline scripts and utility classes.
A map an agent can follow
ax/llms-txt checks for /llms.txt and /llms-full.txt at the domain root and validates the basic Markdown format. ax/markdown-response checks whether the site honours Accept: text/markdown or publishes a .md variant, which removes the markup cost rather than reducing it. ax/agents-md looks for an AGENTS.md, the instructions file a coding agent reads when working against the site's repository. ax/well-known-agent detects MCP server cards, A2A agent cards and agent-skills manifests under .well-known, and flags a leftover ai-plugin.json from the ChatGPT plugins era. ax/api-discovery looks for an OpenAPI specification and OAuth discovery documents, which let an agent call your API without a human reading docs first.
Those five are recommendations. None affects your score. They report absence so you know it is absent.
A declared policy that agrees with itself
ax/content-signals parses Content-Signal directives in robots.txt, the per-purpose syntax from contentsignals.org that expresses search=yes, ai-input=yes, ai-train=no in one line instead of listing bots. It warns on invalid syntax, meaning an unknown key or a malformed token, and on a contradiction, meaning a declared ai-train=yes above a robots.txt that fully disallows GPTBot. A crawler reading both has to guess which directive wins.
ax/rsl-license checks for a License: directive pointing at a valid Really Simple Licensing document. Absence passes. A declared reference that does not fetch, or does not parse as RSL, is flagged so you can repair the pointer.
ax/noai-signals reports declared noai and noimageai opt-outs, plus nosnippet and max-snippet:0. These carry no enforcement. They record intent, and the crawler blocks enforce it.
Presence in the archives the models were built from
ax/archive-indexing checks whether the site appears in the Wayback Machine and the Common Crawl index. Those are the shared corpora most training sets draw on, so blocking CCBot or ia_archiver removes you from every downstream model at once rather than one vendor's.
Signals that tell an engine what the page is
Crawler access gets an engine to the page. Structured data and clean prose determine how confidently it quotes what it finds.
schema/json-ld-valid validates the JSON-LD blocks rather than detecting them, because an invalid block is worth the same as no block. schema/organization and schema/article check the two types that carry attribution, publisher and date. content/date-agreement compares the visible date, the schema datePublished and dateModified, and the Last-Modified header. A page that states three different dates gives a reader no basis for trusting any of them.
Three more rules run as cloud audits because they need a model in the loop. ai/llm-parsability scores how cleanly a model extracts the page's claims. ai/page-type-match checks whether the page is the type it presents itself as. ai/site-metadata resolves the site's identity, type and audience, which populates the site profile block in the report.
How to run the check
Install the CLI and point it at a URL. Local audits are free.
curl -fsSL https://install.squirrelscan.com | bash
squirrel audit https://example.comScope a run to the agent experience rules with --rule-include:
squirrel audit https://example.com --rule-include axThe console output opens with the score and the category breakdown, then lists issues grouped by category. --summary prints the top only:
Health Score: 90/100 (A)
Category Breakdown:
──────────────────────────────────────────────────
Agent Experience ██████████████████░░ 90% ✓3 ⚠1 ✗0
Total: 3 passed, 1 warnings, 0 errors
Without --summary, each issue prints under its category with the rule id, the failing check and the pages it affects. --format llm writes the same findings as text a coding agent can read and act on.
Two things change what a run can see. Sign in, because ax/content-without-js, ax/archive-indexing and the three ai/* rules above are cloud-backed and skip on a logged-out run. Then raise the page budget: --coverage quick stops at 25 pages, --coverage full at 500, and --max-pages goes higher, up to the CLI cap of 10,000, with cloud runs clamped to your plan's page ceiling. Page count is what reaches the templates a homepage check never loads.
Avoid --render on this particular check. It forces the crawl itself to be browser-rendered, which makes the raw HTML and the rendered DOM the same document, so ax/content-without-js has nothing to compare and skips. Left off, the audit fetches raw HTML and renders separately, which is what the comparison needs.
What to fix first
- Unblock the user-action fetchers. ChatGPT-User, Claude-User and Perplexity-User carry live requests from real people. Check robots.txt and your edge rules both, because
ax/agent-blockingandax/ai-crawlerscan disagree and the edge wins. - Get main content into the raw HTML. Nothing further down this list applies to a page that renders empty without JavaScript.
- Unblock the AI-search indexers if you want citations. That is a separate decision from training, and the user-agents are separate too.
- Repair invalid structured data before adding more of it.
- Cut the token weight on your content templates.
- Add
llms.txtand the other recommendations once the layers underneath them work.
The recommendation rules sit last because they describe content a crawler has to reach first. An llms.txt on a site that blocks PerplexityBot changes nothing about what PerplexityBot sees.
For the crawler layer, AI crawlers and robots.txt goes bot by bot, and the free llms.txt validator checks the file format without installing anything. The generative engine optimization guide covers the content side, and generative engine optimization tools lists the trackers that measure citations once the technical side is clean.