Scoring methodology
24 checks · 107 points across 4 categories
The geotool score measures one thing: whether an AI engine can reach, read and extract an answer from a page. Every input is a deterministic measurement of that page's HTTP response and HTML, so the same page always produces the same number — and anyone can recompute it from the rules below.
It does not predict whether engines will cite the page. Citation depends on content quality, authority and competition. None of those are measured here, and a score that implied otherwise would be worth nothing.
Principles
- Deterministic, or it does not ship. No language model participates in scoring. Tools that score AI visibility by sampling engine answers cannot be reproduced — ask twice, get two numbers — which means a difference between two runs carries no information, and comparing your page against someone else's is meaningless. Every check here reads the fetched bytes.
- The arithmetic is public. Each check declares its weight and its pass condition. Nothing is a black box, and no factor is hidden because it is proprietary.
- Subscores, not one number. A single figure averages "no crawler can reach this page" together with "the meta description is long". Categories are scored separately so a failure cannot be diluted by unrelated tidiness.
- Fatal problems cap the grade. Without gates, a page no crawler can fetch still scores in the seventies because everything else is neat. See gate rules.
- Skipped checks are declared. A check that does not apply is excluded from the denominator and named in the report, with the reason. Silently excluding checks lets two pages be scored out of different totals with nothing admitting it.
Categories & weights
| Category | Weight | Checks | Points | What it asks |
|---|---|---|---|---|
| Crawler access | 40% | 4 | 27 | Can a crawler fetch the page at all. Weighted highest because nothing else matters if the answer is no. |
| Extractability | 35% | 11 | 41 | Is there readable text in the server HTML, and can a definite answer be lifted from it. |
| Freshness | 15% | 3 | 20 | Does the page assert a date, and is that date still true. Scored only when the page makes a currency claim or its content is time-sensitive (pricing, promo codes, dated round-ups) — an evergreen page that claims no date is marked not-applicable rather than penalised for lacking one. |
| Structured data | 10% | 6 | 19 | Whether the JSON-LD a page does declare is valid, page-specific and complete. The mere presence of structured data is treated as an enhancement and reported outside the score — Google states no special schema is required for its AI surfaces, so its absence is an opportunity, not a readiness failure. |
Points do not sum across categories. A category's score is the share of its own applicable points that passed, and that percentage is then weighted by the figure in the Weight column. So Extractability having more checks than Crawler Access gives it no extra influence — its ceiling is 35% either way. This is worth stating because reading the Points column as a global total is an easy and reasonable mistake. Crawler access carries the most because it is a precondition: a page an engine cannot fetch has no other properties worth measuring.
The exact formula, so the score can actually be recomputed. For each category, categoryScore = Σ(weight of passed applicable checks) ÷ Σ(weight of all applicable checks) × 100. The overall score is the category scores averaged by the weights above, renormalised over the categories that had at least one applicable check: score = Σ(categoryScore × categoryWeight) ÷ Σ(categoryWeight) across those categories. Two consequences worth stating plainly: (1) because the denominator is only the applicable checks, a single check's real influence is categoryWeight × checkWeight ÷ Σ(applicable check weights in its category) — so on a page where it is the only applicable check in its category, it controls that whole category's weight. The Points column is its share when everything applies, not a fixed global contribution. (2) A category with no applicable checks at all (an evergreen page often has none for Freshness or Structured data) is excluded, not scored zero and not handed a free 100; its weight is redistributed in proportion across the categories that were measured, and the report shows it as N/A.
Gate rules
Some failures make the rest of the report academic. When any of these fails, the overall score is capped at 45 regardless of the other categories. The cap is a ceiling, never a floor — a page already scoring below it is not lifted.
search_crawler_blocked— Search and live-browse crawlers are permitted by robots.txtmeta_noindex— Page is indexable (no noindex directive)nosnippet— Content may be used in snippets and AI answersclient_side_rendered— Primary content is present in the server HTML
The crawler gate is judged per engine, not per bot. Answer engines control their crawlers independently — blocking Perplexity says nothing about OpenAI, Google, Anthropic or Bing. So search_crawler_blocked caps the score only when the page is blocked to every answer engine at once, i.e. it is invisible to all of them. Being blocked to some but not all engines is a real loss but not a fatal one: it is reported as some_search_crawlers_blocked, a non-gate failure in Crawler access that names which engines are lost and which still reach the page.
client_side_rendered is a conservative gate. Most AI fetchers — OAI-SearchBot, PerplexityBot, the user fetchers — read the raw HTML and do not execute JavaScript, so content that only appears after a client render is invisible to them. Googlebot is the exception: it queues pages for rendering and can index script-generated content. The gate reflects the majority that cannot, so a page failing it may still be readable to Google while being unreadable to the rest.
Reports state when a score was capped and which check caused it, alongside the uncapped figure.
Grade bands
| Grade | Score | Label | Meaning |
|---|---|---|---|
| A | 90+ | ready | Nothing material is standing between an engine and this page. |
| B | 75+ | minor gaps | Readable and retrievable, with small omissions worth closing. |
| C | 60+ | needs work | Reachable, but signals an engine relies on are missing. |
| D | 40+ | significant gaps | Substantial parts of the page are not usable by an engine. |
| F | 0+ | major gaps | Most of what an engine relies on is missing or unusable. |
Bands exist because a raw number invites false precision. 87 and 84 are not meaningfully different pages; B and C are.
The 24 checks
Weight is in points. A check may claim more than one finding code when several distinct failures describe the same underlying property — no H1 and three H1s are different advice but one condition, and listing them separately would score the same property twice.
Crawler access
| Pts | Passes when |
|---|---|
| 10 |
Search and live-browse crawlers are permitted by robots.txt gate
search_crawler_blocked some_search_crawlers_blocked |
| 8 |
Page is indexable (no noindex directive) gate
meta_noindex |
| 7 |
Content may be used in snippets and AI answers gate
nosnippet max_snippet_zero |
| 2 |
Snippet length is not restricted below a usable answer
max_snippet_short |
Extractability
| Pts | Passes when |
|---|---|
| 10 |
Primary content is present in the server HTML gate
client_side_rendered |
| 5 |
Page has substantive content
thin_content |
| 5 |
A self-contained answer appears before the first H2
no_direct_answer_blockSkipped when: page is too short for a separate answer block to be meaningful
|
| 5 |
Page has a title
no_title |
| 5 |
Page has exactly one H1
no_h1 multiple_h1 |
| 3 |
Content contains concrete figures
low_evidence_densitySkipped when: page is too short to expect supporting figures
|
| 2 |
Meta description is present and well-sized
no_meta_description meta_description_length |
| 2 |
Content sits inside a <main> landmark
no_semantic_landmarks |
| 2 |
Paragraphs are sized for retrieval chunking
very_long_paragraph |
| 1 |
Title fits without truncation
long_titleSkipped when: page has no title to measure
|
| 1 |
Images carry alt text
no_image_altSkipped when: page contains no images
|
Freshness
| Pts | Passes when |
|---|---|
| 9 |
Date asserted in the title is current
stale_title_date aging_title_dateSkipped when: title asserts no date
|
| 6 |
Modification date is declared and current
no_date_modified stale_date_modifiedSkipped when: page makes no date claim and shows no time-sensitive content
|
| 5 |
Body copy is consistent with the date the page claims
stale_year_stringsSkipped when: page makes no claim about its own currency, so nothing can contradict it
|
Structured data
| Pts | Passes when |
|---|---|
| 5 |
All JSON-LD parses correctly
invalid_json_ldSkipped when: page carries no json-ld to validate
|
| 4 |
Structured data describes this page, not just the site
boilerplate_schema_onlySkipped when: page carries no json-ld to validate
|
| 3 |
FAQ content is declared as FAQPage
missing_faqpageSkipped when: page has no faq content
|
| 3 |
Structured data includes dateModified
schema_missing_datemodifiedSkipped when: page carries no json-ld to validate
|
| 3 |
Organization identity is linked outward with sameAs
no_same_asSkipped when: page declares no organization or person entity
|
| 1 |
Comparison content has a page-level type
untyped_comparison_contentSkipped when: page has no comparison tables
|
Thresholds
Phrases like "substantive content" and "sized for retrieval chunking" are not reproducible descriptions — they are numbers. Publishing the checks without publishing these would make "anyone can recompute the score" a weaker claim than it sounds. Every value below is read from the same module the scorer uses, so the two cannot diverge.
| Threshold | Value | Basis | Why |
|---|---|---|---|
serverRenderedCharsText in the server HTML above which a page is judged server-rendered |
1500 characters | judgement | Enough prose that the content cannot plausibly be arriving from JavaScript. |
clientRenderedCharsText below which a script-heavy page is judged client-rendered |
200 characters | mechanism | Below this there is no passage for a retriever to quote, whatever the page looks like once scripts run. |
clientRenderedScriptBytesScript weight that, combined with near-empty text, indicates runtime assembly |
1000 bytes | mechanism | Proportion rather than framework fingerprints: hand-written JavaScript has no fingerprint but produces the same empty document. |
spaMarkerCharsText below which framework markers plus a high script ratio indicate client rendering |
800 characters | mechanism | Follows from documented crawler or parser behaviour. |
spaMarkerRatioScript-to-text ratio required alongside a framework marker |
3 × text length | judgement | Engineering judgement. Reasoned, not measured — argue with it. |
thinContentCharsText below which a server-rendered page is called thin |
300 characters | judgement | A page with less than a short paragraph rarely contains a quotable claim. |
answerBlockCharsMinimum text before the first H2 to count as a direct answer block |
120 characters | judgement | Roughly two sentences — the shortest span that can state an answer rather than introduce one. |
answerBlockAppliesAbovePage length above which the answer-block check applies |
500 characters | judgement | Below this the whole page is shorter than an answer block, so the check is meaningless. |
titleMaxCharsTitle length above which truncation is likely |
70 characters | judgement | Google states titles have no fixed length limit and truncate by device width; 70 is a practical convention, not a documented cutoff. |
metaDescriptionMaxCharsMeta description length above which truncation is likely |
160 characters | judgement | No fixed limit exists; descriptions truncate by available display space. 160 is a convention. |
paragraphMaxCharsLongest paragraph before chunking is judged to suffer |
1200 characters | judgement | Retrieval splits documents into passages; an oversized block dilutes whichever passage contains it. |
evidenceMinCountMinimum count of numbers, percentages and prices in a long page |
3 figures | judgement | The GEO study (Aggarwal et al., KDD 2024) found statistics can raise citation likelihood, but it measured citation visibility — which this tool explicitly does not predict — and the effect varied by domain. "Three" is a judgement threshold, not a value the study established. |
evidenceAppliesAbovePage length above which the figures check applies |
800 characters | judgement | Engineering judgement. Reasoned, not measured — argue with it. |
landmarkAppliesAbovePage length above which a <main> landmark is expected |
500 characters | judgement | Engineering judgement. Reasoned, not measured — argue with it. |
staleTitleMonthsTitle date drift treated as high severity |
2 months | judgement | One month is called out at medium; two is a claim the page no longer supports. |
staleSchemaMonthsdateModified age treated as stale |
3 months | judgement | Engineering judgement. Reasoned, not measured — argue with it. |
fatalCapCeiling applied when any gate check fails |
45 points | judgement | A ceiling, never a floor: a page already below it is not lifted. |
Basis matters more than the value. experiment — Supported by published controlled experiment. mechanism — Follows from documented crawler or parser behaviour. judgement — Engineering judgement. Reasoned, not measured — argue with it. Most of these are judgement: reasoned, not calibrated against measured citation outcomes. Presenting them at the same evidential weight as a documented crawler behaviour would misrepresent how much is known.
Reproducible is not the same as valid
This methodology solves measurement stability, not measurement validity. A ruler that returns the same reading every time can still be measuring the wrong thing. The weights below are expert judgement about what matters, not coefficients fitted to observed citation behaviour — no ablation study, benchmark or cross-engine correlation has been run, and until one has, the honest position is that this is a well-specified opinion rather than a calibrated model.
The determinism claim should also be read precisely. It holds given the same HTTP response, the same audit date, and the same parser version. A page can vary by country, user-agent, CDN edge, A/B assignment or WAF state, and freshness checks move with the calendar. What is guaranteed is that identical input yields identical output — not that a site returns identical input twice.
Applicability
13 of the 24 checks are conditional. A missing FAQPage is not a failure on a page with no FAQ, and counting it as a silent pass would inflate the score as surely as counting it as a failure would deflate it. Conditional checks are removed from the denominator and listed in the report with the reason they did not run.
| Check | Skipped when |
|---|---|
no_direct_answer_block |
Page is too short for a separate answer block to be meaningful |
long_title |
Page has no title to measure |
low_evidence_density |
Page is too short to expect supporting figures |
no_image_alt |
Page contains no images |
stale_title_date |
Title asserts no date |
stale_year_strings |
Page makes no claim about its own currency, so nothing can contradict it |
no_date_modified |
Page makes no date claim and shows no time-sensitive content |
invalid_json_ld |
Page carries no JSON-LD to validate |
boilerplate_schema_only |
Page carries no JSON-LD to validate |
missing_faqpage |
Page has no FAQ content |
schema_missing_datemodified |
Page carries no JSON-LD to validate |
untyped_comparison_content |
Page has no comparison tables |
no_same_as |
Page declares no Organization or Person entity |
Crawler taxonomy
Two properties decide what a robots.txt rule actually buys you, and an earlier version of this document conflated them — telling sites that disallowing ChatGPT-User would remove them from ChatGPT's answers, when the vendor documents that agent as neither obeying robots.txt nor determining search eligibility. Both properties are now tracked per agent, sourced from vendor documentation.
| Role | Agents | Consequence of blocking |
|---|---|---|
| Search | OAI-SearchBot Claude-SearchBot PerplexityBot Googlebot Bingbot |
Fetches pages to answer a live query, and obeys robots.txt. Blocking one removes the page from that engine's answers. |
| Live browse | ChatGPT-User Claude-User Perplexity-User |
Fetches a page because a user asked about it directly. Most ignore robots.txt, so a rule aimed at them changes nothing — and vendors state they do not govern search eligibility either. |
| Training | GPTBot ClaudeBot Google-Extended meta-externalagent Bytespider Amazonbot CCBot |
Harvests content for model training. Blocking costs nothing in AI search visibility, with one caveat noted below. |
A site that blocks GPTBot but allows OAI-SearchBot is making a deliberate, coherent choice: no training, still answerable. A site that blocks OAI-SearchBot has removed itself from ChatGPT's answers, and usually did not intend to.
Most user-triggered fetchers ignore robots.txt — but not all, so this is tracked per agent, not per role. OpenAI states of ChatGPT-User that "because these actions are initiated by a user, robots.txt rules may not apply", and that it "is not used to determine whether content may appear in Search"; Perplexity says Perplexity-User "generally ignores robots.txt rules". A Disallow for either is reported as an ineffective directive, not a block. Claude-User is the exception: Anthropic states its bots "respect 'do not crawl' signals by honoring industry standard directives in robots.txt", and that disabling Claude-User "may reduce your site's visibility for user-directed web search" — so there, unlike its OpenAI and Perplexity counterparts, a rule both takes effect and costs visibility.
Google-Extended is not training-only. Google describes it as limiting "AI training and grounding in some of Google's other systems". Blocking it does not affect Google Search, AI Overviews or AI Mode, but it can affect grounding in Gemini Apps and Vertex AI — so the cost is real, just not where most people assume.
robots.txt is evaluated per agent using Google's group-selection rules: an exact user-agent group replaces the * group rather than merging with it, path patterns support * and $, and the longest matching rule wins with Allow taking an equal-length tie.
This is the set of agents currently tracked, not the whole ecosystem. The score reasons about the 15 agents in the table above, covering the answer engines with the widest reach — OpenAI, Google, Anthropic, Perplexity and Microsoft. Others exist and are not yet modelled (for example Meta's user-triggered Meta-ExternalFetcher, and Amazon's Amzn-SearchBot and Amzn-User). A page can therefore be reachable or blocked to an engine this tool does not yet list; the crawler-access result should be read as "for the engines below", not "for every AI engine in existence".
What is not measured
- Whether any engine actually cites the page. That is a different measurement — observed visibility — and it requires querying the engines. A single query is not a stable score because the answer moves between runs, but controlled repeated sampling (fixed prompt, model, locale and sample size, reported with a confidence interval) genuinely can measure it. It is simply not what this tool does: this measures technical readiness deterministically, and mixing a sampled metric into a reproducible one would forfeit the reproducibility that is the whole point. The two are complementary, not rivals.
- Backlinks, domain authority, traffic, rankings. Not fetched, not inferred.
- Whether the content is accurate, useful or competitive. No judgement of quality is made or implied.
- JavaScript-rendered content, unless the rendering verdict says otherwise. The audit reads what a crawler that does not execute scripts would read.
- Anything behind authentication. Only what a public crawler could fetch.
Crawler access probes, when enabled, are labelled by confidence rather than folded into the score as fact. Probes originate from this server, not from a vendor's verified IP range, so a bot-verification challenge is the expected response to them and is reported separately from a genuine block.
Disagreements
If a weight looks wrong, a check looks unjustified, or a page scored in a way the rules above do not explain, that is worth hearing: [email protected]. A published methodology is only useful if it can be argued with.