Methodology

Every check, every threshold, every deduction — published.

The audit is deterministic: the same site on the same day produces the same findings and the same scores. No opinion layer decides what fails, and no language model touches a score. This page lists what the audit fetches, what each check fires on, and exactly how the numbers combine. The limits and threshold bands below are rendered from the same constants the engine imports, so the page cannot drift from the code that produces your report.

Think a finding on your report is wrong? Send the report link — a person re-runs the check and answers.

What we fetch

The crawl contract

One audit fetches up to 20 pages of the submitted site and runs four small probe phases — three after the crawl, and the AI-crawler access test early, before the crawl has had a chance to look like a burst of traffic. Two further requests leave the audited host: the www/non-www counterpart of the homepage, to see whether both serve the same site, and one PageSpeed Insights lookup at Google. Nothing else is fetched.

PhaseBudgetWhat happens
Page crawl up to 20 requests, 45 s Homepage first, then robots.txt, llms.txt and the sitemap. The remaining budget goes to same-site pages — sitemap URLs first (the site's own canonical page list), then navigation links.
Internal-link probes up to 10 requests, 10 s HEAD checks (status and headers only, no body) on same-site link targets the crawl did not fetch. Links the budget never reaches are reported as unchecked — never judged broken.
Sitemap sample up to 5 requests, 6 s HEAD checks on a sample of sitemap URLs the crawl did not reach. The report names how many were listed and how many were actually checked.
Asset sizing up to 8 requests, 8 s Sizes same-site CSS, script and image files from response headers (plus one single-byte ranged request when a server hides the size). Assets on other domains are counted but never fetched; assets that could not be sized are never guessed.
AI-crawler access test up to 8 requests, 20 s Fetches the homepage again once per AI crawler user agent (OAI-SearchBot, PerplexityBot, GPTBot, ClaudeBot, CCBot), skipping any crawler robots.txt already disallows, and compares status and body size with the audit's own fetch. Because a refusal only says something about a crawler once request rate is ruled out, the phase runs after the first four requests of the crawl rather than at its tail, leaves 0.25–0.5 s idle before each request, retries a rate-limited answer once (honouring Retry-After when the wait it asks for is 3 s or less, and reporting rather than sleeping when it is longer), and answers a 401/403/451 with one confirmation fetch using our own normal User-Agent. If that normal fetch is refused too, the refusal is site-wide or rate-based, not a rule about AI crawlers, and it is reported as not judged instead of as a block. A user agent the budget or deadline never reached is reported as unchecked, never judged. This is a user-agent-level test: blocks keyed on a crawler's IP ranges cannot be seen from here.
www / non-www check 1 request, 15 s Fetches the counterpart host of the homepage (www if you submitted the bare domain, and the other way round) alongside the crawl, to see whether both serve the same site with no redirect and no canonical resolving the pair. If that request does not answer, nothing is judged.
PageSpeed Insights 1 request to googleapis.com Asks Google's PageSpeed Insights API (v5, mobile strategy, homepage only) for Core Web Vitals field data from the Chrome User Experience Report and one Lighthouse lab run. The lookup starts before the crawl and runs on its own thread, so most of it overlaps work the audit was doing anyway — but it is not free: on a site the crawl finishes quickly, the audit can end up waiting on Google. That wait is capped twice: the lookup is abandoned 25 s after it started, and the extra wait after the crawl and probe phases is capped at the time those phases themselves took (never less than 22 s). A one-page site that crawled in a fraction of a second therefore waits about 22 s, not 25. Only the homepage URL is sent. A timeout, a quota refusal or a missing field-data record means the signal is not measured — see the speed pillar below.
Rule on every requestLimit
Protocols and portshttp/https only, standard web ports only, no credentials in URLs
Address guardEvery hostname is resolved and every address must be publicly routable — private, internal, loopback and reserved addresses are refused. Every redirect hop is re-validated the same way.
Redirectsat most 10 hops per fetch
Response bodyat most 5 MB read per response
Time per fetchat most 15 s wall clock, redirects included
Identificationevery request sends the User-Agent CorpusPlanBot/1.0 (+https://corpushq.co/plan)

How the numbers combine

The scoring formula, in plain words

Each pillar starts at 100. Two layers take points off — binary findings and graded quality measurements — and both are written out on the report, line by line.

1. Binary findings

Every finding in the tables below deducts a fixed amount from its pillar. Deductions add up without limit (floor 0), so every additional problem costs something.

SeverityDeduction
Critical35 points
Major15 points
Minor5 points

One critical alone caps a pillar at 65.

2. Graded quality curve

Each pillar also measures quality signals scored 0.0–1.0 against the written bands below. The pillar's graded deduction is 40 × (1 − weighted mean of the measured signals). Signals that could not be measured leave the denominator entirely — a pillar with nothing measurable takes no graded deduction. A site whose measured signals all hit their top band deducts nothing and can score 100; no measurement is ever invented to avoid that.

Every graded cost of 0.5 points or more appears on the report as a written note carrying the measured value and the exact point cost — no score moves without a written reason.

Some lines on the report are reference notes, not problems: Google's own Lighthouse SEO score, or an acknowledgement that every noindexed page we found is a legitimate flow step. They deduct nothing, and they are also left out of the report's "N findings" and "N critical" counts — a note whose own fix hint says there is nothing to do must not inflate your problem count. They still appear in the checklist, marked as costing no points.

Pillar score and overall

pillar = 100 − finding deductions − graded deduction, clamped to 0–100 and rounded. The overall score is the weighted sum of the five pillars.

PillarWeight in the overall score
SEO25%
Speed15%
Trust20%
Content15%
AI citability25%

SEO and GEO carry the most weight because search and AI-assistant visibility are what the product is bought for; trust is next because broken TLS or missing contact routes lose visitors you already won; speed and content are real factors but usually the symptom rather than the disease. The weighting is our judgment and is stated here as exactly that.

Pillar · weight 25%

SEO

Classic search fundamentals, checked on the homepage and swept across every crawled page. Each finding names the affected paths as evidence.

CheckCodeSeverityFires when
Homepage titleSEO_MISSING_TITLECriticalThe homepage has no <title> tag.
Title lengthSEO_TITLE_LENGTHMinorThe homepage title is outside 15–65 characters.
noindexSEO_NOINDEX_HOMEPAGECriticalA robots meta tag with noindex tells engines to drop the homepage from their index.
Meta descriptionSEO_MISSING_META_DESCRIPTIONMajorThe homepage has no meta description.
Description lengthSEO_META_DESCRIPTION_LENGTHMinorThe meta description is outside 50–165 characters.
H1 headingSEO_MISSING_H1MajorThe homepage has no H1.
Multiple H1SEO_MULTIPLE_H1MinorThe homepage carries more than one H1.
SubheadingsSEO_NO_SUBHEADINGSMinorNo H2/H3 subheadings on the homepage.
Canonical linkSEO_MISSING_CANONICALMinorNo rel="canonical" link tag on the homepage.
Image alt textSEO_IMG_ALT_MISSINGMajor / MinorUnder 50% of sampled homepage images carry alt text (major); 50–80% (minor). Sample: the first 30 images.
robots.txt blocks allSEO_ROBOTS_BLOCKS_ALLCriticalrobots.txt disallows the whole site for every user-agent.
robots.txt is HTMLSEO_ROBOTS_NOT_TEXTMinor/robots.txt answers with an HTML page instead of a plain-text file.
robots.txt missingSEO_MISSING_ROBOTSMinor/robots.txt answers 404.
Sitemap invalidSEO_SITEMAP_INVALIDMinorA sitemap responds but is not valid XML.
Sitemap missingSEO_MISSING_SITEMAPMinorThe sitemap answers 404.
Pages without titlesSEO_PAGES_MISSING_TITLEMajorCrawled inner pages with no <title>, each path named.
Page title lengthsSEO_PAGES_TITLE_LENGTHMinorInner-page titles outside 15–65 characters.
Pages without descriptionsSEO_PAGES_MISSING_META_DESCRIPTIONMinorCrawled inner pages with no meta description.
Pages without H1SEO_PAGES_MISSING_H1MinorCrawled inner pages with no H1.
Pages with multiple H1SEO_PAGES_MULTIPLE_H1MinorCrawled inner pages with more than one H1.
Duplicate titlesSEO_DUPLICATE_TITLESMajorThe same title on two or more crawled pages (post-redirect duplicates of the same document are excluded).
Duplicate descriptionsSEO_DUPLICATE_META_DESCRIPTIONSMinorThe same meta description shared by two or more crawled pages.
Broken internal linksSEO_BROKEN_INTERNAL_LINKSMajorProbed internal links answering HTTP 404/410. Only links that were actually probed and answered are judged; the finding states how many were checked out of how many exist.
Dead sitemap URLsSEO_SITEMAP_DEAD_URLSMajorSampled sitemap URLs answering HTTP 404/410 — same only-what-was-checked rule.
Pages set to noindexSEO_PAGES_NOINDEXMajorA crawled page carries noindex in a robots (or googlebot) meta tag or in the X-Robots-Tag response header — it cannot appear in search results at all. Each page is named with which of the two carries the directive. Pages whose path looks like a flow step (cart, checkout, thank-you, login, account, unsubscribe…) are not charged at all — noindex is the correct setting there — and are listed separately in the finding so the count above is only pages that should be findable.
Noindexed pages, all of them flow stepsSEO_PAGES_NOINDEX_UTILITY_ONLYInformational — 0 pointsEvery noindexed page the crawl found is a utility or confirmation step (cart, checkout, thank-you, login, account, unsubscribe…). That is the correct configuration, so it deducts nothing; the note exists so the pages are named. We judge this from the path alone — if one of them is a page you want found, remove its noindex.
Canonical to another domainSEO_CANONICAL_OFF_HOSTMajorA crawled page's canonical URL resolves to a genuinely different host, handing credit for the content to that domain. The www/non-www counterpart of the audited site is the same site and is never listed here — pointing every page at a preferred host is exactly the fix SEO_DUPLICATE_HOST asks for. The finding names the two cases where an off-host canonical is intended (the page is a syndicated copy of one published on that domain, or the site deliberately consolidates onto another host) so the listed pages get checked rather than changed on sight.
Canonical to httpSEO_CANONICAL_HTTPMajorThe site is served over HTTPS but a canonical names the insecure http:// URL.
Canonical to another pageSEO_CANONICAL_NOT_SELFMinorA page's canonical names a different page on the same site. Correct for deliberate duplicates (print views, paginated series, parameter variants) and wrong elsewhere — the finding says so and names the pages rather than assuming.
www and non-www both liveSEO_DUPLICATE_HOSTMajorBoth hosts answer 2xx with the same homepage (identical title and within 90% the same size), neither redirects to the other, and no canonical in the HTML we fetched resolves the pair — a canonical injected later by JavaScript cannot be seen from here, and the finding says so. Different content on the two hosts, a redirect, a canonical, or a counterpart that did not answer: no finding. The counterpart is only fetched for a host we can resolve to a registered domain, which includes multi-label endings such as .co.uk and .com.au; a host under a second-level suffix we do not list is treated as a subdomain and simply not checked.
Duplicate H1 headingsSEO_DUPLICATE_H1MinorThe same H1 text on two or more crawled pages, each group named with its paths.
Page languageSEO_MISSING_LANGMinorThe homepage's <html> tag carries no lang attribute.
hreflang consistencySEO_HREFLANG_INCONSISTENTMinorThe homepage publishes hreflang alternates but none points back at the homepage itself, or a value is not shaped like a language (or language-region) code. Sites with no hreflang at all are not judged.
Lighthouse SEO scoreSEO_PSI_LIGHTHOUSE_SEOInformational — 0 pointsNot a finding against the site: when the PageSpeed Insights lookup succeeds, the report carries Google's own basic-SEO category score for cross-reference. Our SEO score is measured independently from our crawl and is never derived from it. It deducts nothing whether it is 100 or 40.
Graded signalCodeWeightScored againstBasis
Title lengthSEO_GRADE_TITLE1.0missing → 0; 30–60 characters → 1.0; else within 15–65 → 0.75; outside → 0.4Google truncates result titles around 60 characters (published guidance); the partial-credit steps are our judgment.
Meta description lengthSEO_GRADE_META_DESCRIPTION1.0missing → 0; 70–155 characters → 1.0; else within 50–165 → 0.75; outside → 0.4Google cuts descriptions around 155–160 characters on desktop results; steps our judgment.
Heading structureSEO_GRADE_HEADINGS1.0one H1 with ordered levels → 1.0; −0.25 for no H2/H3; −0.25 for skipped levels; multiple H1 → 0.5; no H1 → 0.2Our judgment: an ordered outline is what crawlers and screen readers parse best.
Canonical linkSEO_GRADE_CANONICAL0.5present → 1.0; absent → 0Plain presence check.
Internal links from the homepageSEO_GRADE_INTERNAL_LINKS1.0≥ 8 path(s) → 1.0; ≥ 4 path(s) → 0.85; ≥ 1 path(s) → 0.6; below every band → 0Our judgment: a normal navigation plus footer links eight or more distinct pages.
Title and H1 agreementSEO_GRADE_TITLE_H1_MATCH0.5mean across crawled pages of (words shared by the title and the H1) ÷ (the title's word count), counting words of three or more characters and dropping common stopwords: ≥ 0.6 → 1.0; ≥ 0.35 → 0.9; ≥ 0.15 → 0.75; below every band → 0.5Our judgment, and deliberately forgiving: a title written for the search result and an H1 written for the visitor should not be identical, so the floor is 0.5 rather than 0. Low agreement is a hint that one of the two is generic, not a defect. The question is how much of what the search result says the page's own heading echoes, so the title's words are the denominator; dividing by the smaller of the two sets inverted that — a one-word brand H1 scored a perfect 1.00 while a descriptive H1 that added words scored 0.20. An H1 with fewer than 2 meaningful words says too little to measure agreement at all and is left out rather than rewarded. Where a site wraps its logo in an H1 inside <header> or <nav>, the first H1 outside those wrappers is used instead. Pages missing a title or an H1 are not measured here — the dedicated checks above report those.

Pillar · weight 15%

Speed

Server response time, page weight, compression and mobile setup — measured from the actual fetches, never estimated from a traffic database.

One signal in this pillar does come from Google: a single PageSpeed Insights lookup for the homepage, run alongside the crawl. When Chrome has enough real traffic to this site, that returns Core Web Vitals field data — what actual visitors experienced — and it is the heaviest-weighted measurement here. When it does not, the Lighthouse lab score stands in at a lower weight, and when the lookup fails or is refused for quota, nothing from Google is scored at all. Absence is never a deduction: a site with no field data cannot score lower than one with good field data.

Known limitation — two speed scores are not always comparable. Everything else in this audit is deterministic: the same site checked twice the same way produces the same number. The PageSpeed Insights signal is the exception. In production today there is no API key, so every lookup is refused for quota and contributes nothing to any score. On the day a key is added, speed scores will shift across every site we have audited, with nothing about those sites having changed. An audit measured with PageSpeed Insights and one without it are therefore not directly comparable, and neither is a before/after pair that straddles that change. A second reason to read the lab number cautiously: Lighthouse's performance score is itself a composite of load-timing signals, several of which this pillar already measures independently from our own fetch, so when it is used the two overlap rather than corroborate.

CheckCodeSeverityFires when
Server response timeSPEED_SLOW_RESPONSEMajor / MinorThe homepage takes 2.0 s or more to start responding (major); 0.8 s or more (minor).
Homepage HTML weightSPEED_HEAVY_HTMLMajor / MinorThe homepage HTML alone is 3072 KB or more (major); 1536 KB or more (minor).
Text compressionSPEED_NO_COMPRESSIONMajorThe homepage response carries no gzip/brotli/zstd/deflate Content-Encoding header.
Mobile viewportSPEED_NO_VIEWPORTMajorNo viewport meta tag — the site renders as a shrunken desktop page on phones.
Slow inner pagesSPEED_SLOW_PAGESMinorThe median response time across three or more crawled inner pages is 2.0 s or more.
Redirect chainSPEED_REDIRECT_CHAINMinorThe homepage settles only after more than 2 redirects; one hop (http to https, or apex to www) is normal.
Oversized assetsSPEED_HEAVY_ASSETMinorA sampled homepage asset transfers 1024 KB (1 MB) or more, named with its measured size. Assets the budget never sized are not judged.
Images reserving no spaceSPEED_IMG_NO_DIMENSIONSMinorAt least 3 homepage <img> elements, and at least 25% of them, reserve no space: no width and height attributes, and no inline style carrying a real height or an aspect-ratio. The inline style is parsed into declarations rather than searched for the words "width" and "height", because max-width:100%;height:auto — the common responsive idiom — reserves nothing at all and used to read as sized. SVG icons, images inside <noscript> and 1x1 tracking pixels are excluded from both the count of unsized images and the total they are measured against. Without reserved space the page reflows as each image arrives (web.dev names this the main cause of layout shift). Read from the HTML only: an image sized entirely in a stylesheet cannot be seen from here, and the finding says so and names the images instead of assuming.
Graded signalCodeWeightScored againstBasis
Server response timeSPEED_GRADE_TTFB1.0≤ 0.2 s → 1.0; ≤ 0.4 s → 0.9; ≤ 0.6 s → 0.75; ≤ 0.8 s → 0.6; ≤ 1.5 s → 0.35; ≤ 2.5 s → 0.15; past every band → 0Lighthouse's server-response-time audit flags responses over 600 ms; the Chrome UX Report "good" threshold is 800 ms. Band steps are our interpolation.
Homepage HTML weightSPEED_GRADE_HTML_WEIGHT1.0≤ 50 KB → 1.0; ≤ 100 KB → 0.85; ≤ 200 KB → 0.6; ≤ 500 KB → 0.35; ≤ 1024 KB → 0.15; past every band → 0HTTP Archive Web Almanac: the median HTML document transfers around 30 KB.
Inline CSS/JS in the HTMLSPEED_GRADE_INLINE_BLOAT1.0≤ 15 KB → 1.0; ≤ 40 KB → 0.75; ≤ 100 KB → 0.5; ≤ 250 KB → 0.25; past every band → 0Critical-CSS guidance keeps inlined assets near the ~14 KB first TCP round trip; band steps our judgment. JSON data blocks (including the JSON-LD the GEO pillar asks for) are excluded.
Text compressionSPEED_GRADE_COMPRESSION0.5compressed → 1.0; none → 0Plain presence check on the Content-Encoding header.
Homepage image countSPEED_GRADE_IMG_COUNT0.5≤ 15 image(s) → 1.0; ≤ 30 image(s) → 0.85; ≤ 50 image(s) → 0.6; ≤ 80 image(s) → 0.35; past every band → 0.15HTTP Archive Web Almanac media chapter: the median page carries around 20–25 images.
Core Web Vitals (real visitors)SPEED_GRADE_CWV2.0 for page-level data; 1.5 when only origin-level data exists, and scaled by n/3 when Google has fewer than three metricsmean over the metrics Google has: each of LCP, INP and CLS scores 1.0 in Google's "good" band, 0.5 in "needs improvement" and 0 in "poor". Thresholds: LCP good at or under 2.5 s, poor over 4 s; INP good at or under 200 ms, poor over 500 ms; CLS good at or under 0.1, poor over 0.25.Google's published Core Web Vitals thresholds (web.dev); the good/half/zero split across the three bands is our judgment. Measured by Chrome on real visits over the last 28 days, which is why page-level data carries the heaviest weight in this pillar. When Google has no field data for the audited page but does have it for the origin, the origin-level record is used instead — that is the aggregate across every page of the domain, so it describes the site's typical experience and not the page we audited. It is weaker evidence, and it is treated that way: the weight drops to the lab-run weight (1.5), and the finding's own title says the numbers are for the whole domain rather than this page. Google has no field data for most small sites — that is normal, and it is then not measured at all: absence leaves the denominator, so a site Google knows nothing about can never score lower here than a site with good numbers.
Lighthouse performance (lab run)SPEED_GRADE_LAB_PERFORMANCE1.5Lighthouse's performance score for the homepage ÷ 100. Used only when there is no field data; field data always wins.Google's own composite lab score, on a simulated mobile connection. One simulated load from Google's machine is weaker evidence than what real visitors measured, so it weighs less than the field-data signal. Not measured when the PageSpeed Insights lookup failed, timed out or was refused for quota.
Modern image formatsSPEED_GRADE_IMG_FORMATS0.5WebP/AVIF ÷ classifiable same-origin homepage images, counted once per image element or <picture> group however many srcset variants it names: ≥ 0.8 share → 1.0; ≥ 0.5 share → 0.85; ≥ 0.25 share → 0.7; below every band → 0.5web.dev's modern-formats guidance: a WebP file runs roughly a quarter to a third smaller than a comparable JPEG. The band steps are our judgment and the floor is 0.5, not 0 — a JPEG is heavier, not broken. A <picture> group offering a modern format in any of its sources counts once as modern; its JPEG fallback is the correct way to write that markup, not a legacy image. Formats are read from the URL, which is evidence about the request and not about the bytes: images on other domains, URLs without an extension, and URLs bearing the marks of a format-negotiating CDN (/cdn-cgi/image/, /_next/image, f_auto, a format= parameter) are unclassifiable — they leave the count entirely and the finding names how many were left out. Under 3 classifiable images, nothing is measured.
Estimated page weight with assetsSPEED_GRADE_PAGE_WEIGHT1.0≤ 512 KB → 1.0; ≤ 1024 KB → 0.9; ≤ 2048 KB → 0.7; ≤ 4096 KB → 0.4; ≤ 8192 KB → 0.2; past every band → 0.1HTTP Archive: the median page transfers about 2–2.5 MB in total; Lighthouse flags multi-MB payloads. Extrapolated from the sampled assets and labeled as an estimate on the report; omitted entirely when assets exist but none could be sized.

Pillar · weight 20%

Trust

TLS, security headers, and the signals buyers look for before they call.

CheckCodeSeverityFires when
TLS certificateTRUST_TLS_ERRORCriticalHTTPS failed with a certificate or TLS error and the site was only reachable over insecure HTTP.
HTTPSTRUST_NO_HTTPSCriticalThe site is served over plain HTTP — it never answered over HTTPS, or the homepage lands on an http:// URL.
Homepage statusTRUST_HOMEPAGE_HTTP_ERRORCriticalThe homepage answers an HTTP error status (400 or higher).
HSTSTRUST_MISSING_HSTSMinorAn HTTPS site without a Strict-Transport-Security header.
Security headersTRUST_MISSING_SECURITY_HEADERSMinorX-Content-Type-Options, or X-Frame-Options/Content-Security-Policy, missing from the homepage response.
Mixed contentTRUST_MIXED_CONTENTMajorScripts, images, iframes, audio or video, or a stylesheet, preload or icon <link>, loading over plain http:// on HTTPS pages — every crawled page checked, affected pages named. Metadata the browser never loads (an og:image tag, canonical or alternate links) is not counted.
Contact routesTRUST_NO_CONTACT_INFOMajorNo phone number, email address or contact link found on the homepage.
Tap-to-call near the top (local businesses)TRUST_NO_TAP_TO_CALL_TOPMinorA local business's homepage shows a phone number but has no tel: link in its header, on a sticky call bar, or in the first 1,200 characters of text (footer links excluded) — our HTML stand-in for the first 390px phone screen. Checked against a headless-browser render of 288 such sites, it agreed on 78%; 12% of the times it fired, the rendered page did show a call link, usually a floating button. A site counts as a local business when its homepage has LocalBusiness-type structured data, a street address with state and ZIP, or a tap-to-call link plus a service word in its title; these three checks never run on other sites.
Rating near the top (local businesses)TRUST_NO_RATING_TOPMinorNo rating with a review count (such as “4.9 from 120 reviews”) in the first 1,200 characters of a local business's homepage text and image alt text. Agreed with the rendered first screen on 306 of 309 sites.
Booking link (local businesses)TRUST_NO_BOOKING_LINKMinorNo link or button on a local business's homepage that says book, schedule, appointment or reserve, or that points at a scheduling service. Quote and estimate buttons do not count. Agreed with the rendered page on 307 of 309 sites.
H1 names a service and a place (local businesses)TRUST_H1_NO_SERVICE_PLACEMinorNone of a local business's homepage H1 headings names both a service (plumbing, HVAC, roofing, dental and so on) and a place: a city or town of 10,000 or more people in the 2023 Census list (1,000 or more for a name of several words), a state, county or named region, or a smaller town the page's own address or structured data names. Word-like town names (Mobile, Union, Center) count only when the address names them. A homepage with no H1 at all is reported by SEO_MISSING_H1 instead. Agreed with the rendered page on 260 of 266 local homepages; of 196 times it fired, 1 was wrong (an H1 capitalised by the stylesheet, not the HTML).
Call to actionTRUST_NO_CTAMajorNo book/call/quote/buy/contact wording in any homepage link or button.
Legal pagesTRUST_NO_LEGAL_PAGESMinorNo privacy policy link, or no terms link.
Broken pagesTRUST_BROKEN_PAGESMajorCrawled pages answering HTTP errors (the robots.txt/llms.txt/sitemap probes are reported by their own pillars, not here).
Graded signalCodeWeightScored againstBasis
Trust-signal completenessTRUST_GRADE_SIGNALS2.0signals present ÷ 8, across: phone, email, contact link, call to action, privacy link, terms link, a social profile link, Open Graph tagsA plain ratio; the eight-signal list is our judgment. The report names exactly which signals are missing.
Security headersTRUST_GRADE_SECURITY_HEADERS1.0headers set ÷ headers expected (X-Content-Type-Options; X-Frame-Options or CSP; plus Strict-Transport-Security on HTTPS)A plain ratio over baseline headers.

Pillar · weight 15%

Content

Depth, freshness and presentation of the actual copy — counted from the fetched pages.

CheckCodeSeverityFires when
Homepage copy depthCONTENT_THIN_HOMEPAGEMajor / MinorUnder 120 words of visible text on the homepage (major); under 300 (minor).
Thin inner pagesCONTENT_THIN_PAGESMinorMost crawled inner pages carry under 120 words.
Cloned location pages (local businesses)CONTENT_CLONED_LOCATION_PAGESMinorTwo or more crawled pages about different named places whose main text is the same once place names are masked: each names its own place more often than the other does, and they share 80% or more of their 5-word phrases. Header, footer and menu text, and lines the homepage shares with half the crawled pages, are left out; a page with under 150 words left is not compared. The finding names the pages. Why 80%: over 439 pairs of pages about different places on 79 sites, the 18 clone pairs (same copy, only the place and a line or two such as the phone number changed) scored 83% to 98%, and every other pair 66% or less.
Cloned location pages, not checkableCONTENT_CLONED_PAGES_UNVERIFIEDInformational — 0 pointsThe crawl reached two or more pages about a named place, but fewer than two had 150 words of readable text to compare (often text drawn by a script). We say we couldn't verify rather than call them unique.
Site footprintCONTENT_FEW_PAGESMinorThe sitemap lists fewer than 5 pages.
Copyright freshnessCONTENT_STALE_COPYRIGHTMinorThe newest copyright year on the homepage is two or more years old. No year on the page: no finding.
Open Graph tagsCONTENT_MISSING_OGMinorog:title is missing, or present without og:description or og:image — shared links render bare.
Graded signalCodeWeightScored againstBasis
Homepage copy depthCONTENT_GRADE_HOME_WORDS1.0≥ 800 word(s) → 1.0; ≥ 500 word(s) → 0.9; ≥ 300 word(s) → 0.75; ≥ 120 word(s) → 0.5; below every band → 0.2Our judgment, anchored on the 120/300-word binary thresholds; full credit needs a genuinely substantial page.
Site copy depthCONTENT_GRADE_SITE_WORDS1.0≥ 2500 word(s) → 1.0; ≥ 1200 word(s) → 0.85; ≥ 600 word(s) → 0.65; ≥ 250 word(s) → 0.4; below every band → 0.2Our judgment — the same anchoring, scaled to the handful of pages one audit fetches.
Image alt coverageCONTENT_GRADE_ALT_COVERAGE1.0images with alt text ÷ sampled images (first 30 per page)A plain ratio. No images on the crawled pages: not measured, no judgment.
Copyright-year freshnessCONTENT_GRADE_FRESHNESS0.5current or last year → 1.0; two years old → 0.5; older → 0.2Our judgment (a January page still saying last year is normal).

Pillar · weight 25%

AI citability

Whether AI assistants can fetch, parse and cite the site. The robots.txt check reads two kinds of AI crawler and prices them differently. Answer crawlers fetch pages live to cite them in AI answers — blocking them is the citability problem: OAI-SearchBot, PerplexityBot, Claude-SearchBot, Applebot. Training crawlers collect text for model training — blocking them is a deliberate choice that does not by itself remove a site from live answers: GPTBot, ClaudeBot, Google-Extended, CCBot, Bytespider, Applebot-Extended. Each crawler's role comes from its operator's published documentation, cited in the pillar source.

The live access test then fetches the homepage as 5 of those crawlers (OAI-SearchBot, PerplexityBot, GPTBot, ClaudeBot, CCBot) and compares the answer with the audit's own fetch. It sees user-agent rules only: a CDN or firewall that blocks a crawler by IP range passes this test, and the report says so.

On llms.txt we say plainly what the evidence shows: an Ahrefs study of 137,210 domains with a valid llms.txt (May 2026) found 97% received zero requests for the file, the top requesters of the rest were SEO audit tools (21.7%) rather than AI crawlers (GPTBot 4.5%, ClaudeBot 0.8%), no major AI provider has committed to the proposed standard, and Google says such files are not needed — so we check it as a no-cost parity item that other tools grade you on, not as a proven visibility lever.

CheckCodeSeverityFires when
llms.txtGEO_MISSING_LLMS_TXTMinor/llms.txt answers with an error — the proposed-standard file does not exist. Minor by the evidence above: most AI crawlers never request it; it stays checked because publishing costs nothing and other audit tools grade it.
llms.txt structureGEO_LLMS_TXT_THINMinorAn llms.txt exists but lacks a markdown H1 title, or has neither links nor 200+ characters of substance.
Structured dataGEO_MISSING_JSONLDMajorNo schema.org JSON-LD found on any crawled page.
JSON-LD validityGEO_JSONLD_INVALIDMinorJSON-LD blocks exist but none parse as valid JSON.
Business identity schemaGEO_NO_ORG_SCHEMAMinorJSON-LD parses but no entity identifies the business itself (Organization, LocalBusiness, WebSite or similar).
All AI answer crawlers blockedGEO_ALL_AI_BOTS_BLOCKEDCriticalrobots.txt disallows every answer crawler (OAI-SearchBot, PerplexityBot, Claude-SearchBot, Applebot) — the site cannot be read into ChatGPT search, Perplexity, Claude search or Siri answers. The finding says plainly this may be intentional.
Some AI answer crawlers blockedGEO_AI_BOTS_BLOCKEDMajorrobots.txt disallows some of the answer crawlers, each named.
AI training crawlers blockedGEO_TRAINING_BOTS_BLOCKEDMinorrobots.txt disallows only training crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot, Bytespider, Applebot-Extended) while the answer crawlers stay allowed. Worded as the deliberate choice it usually is: the site stays citable in live answers; models trained without it may know less about the business.
AI crawlers blocked at the edgeGEO_AI_UA_BLOCKEDMajor / MinorThe live test: fetching the homepage as a crawler answers 401/403/451 or a bot-challenge page while the audit's own fetch got a 2xx, robots.txt allows that crawler, and a confirmation fetch with our own normal User-Agent was served — so the refusal is keyed on who the crawler says it is, and sits in the CDN, firewall or server. A 429 is never this finding (it is a rate limit, about request volume), and neither is a refusal the normal User-Agent also got. Major when an answer crawler is turned away; minor when only training crawlers are. Each finding names the crawlers, the status codes and the user-agent-only scope.
Different page for AI crawlersGEO_AI_UA_DIFFERENT_PAGEMinorA crawler user agent gets a 2xx whose body is under 30% of the regular fetch's — a stripped or substitute page. Not applied when the regular page is under 2048 bytes.
Business name consistencyGEO_NAME_INCONSISTENTMinorThe business name differs across the title tag, og:site_name, JSON-LD name and any H1 that carries the brand — beyond case, punctuation and legal suffixes (LLC, Inc, Ltd…). The variants are listed.
Contact details (NAP) consistencyGEO_NAP_INCONSISTENTMinorA telephone or street address in the JSON-LD appears on no crawled page. Basis: Google's structured-data guidelines ask that marked-up details match what visitors see. Only compared when both sides exist.
Extractable contentGEO_LOW_TEXT_RATIOMajor / MinorThe static HTML exposes under 120 readable words alongside a low text-to-markup ratio — usually JavaScript-rendered content many AI crawlers cannot execute (major); under 250 words (minor).
Question-shaped headingsGEO_NO_QUESTION_HEADINGSMinorNo H2/H3 on any crawled page is phrased as a question and no FAQPage entry was found. Calls to action and section labels ("Questions?", "Ready to get started?", headings of 2 words or fewer) are not counted: a question mark does not make them a question anyone asked, and reporting them as unanswered would be wrong on its face.
Answer structureGEO_NO_ANSWER_STRUCTUREMinorThe homepage carries 150+ words but no list or table — nothing structured for an assistant to quote.
Graded signalCodeWeightScored againstBasis
AI-crawler access (live test)GEO_GRADE_AI_UA_ACCESS1.0Penalty only. Nothing is measured at all unless at least one crawler was turned away, challenged or served a much smaller page. When it does fire: per judged crawler, served like a regular visit → 1.0; a training crawler turned away, or any crawler served a much smaller page → 0.5; an answer crawler turned away → 0. Mean over the crawlers judged; unchecked, unclear and rate-limited answers leave the denominator.Our judgment. Every crawler answering normally is not a measurement of anything — not being blocked is the default state of a web server, and crediting a site for it was inflating sparse pages that pass this for free while failing the signals that need real content. So the component simply does not exist on a site nothing was turned away from. A rate limit (HTTP 429) is not judged either way: it says "too many requests", which is about request rate, not about who asked. The weight is 1.0 — a genuine block is priced like the other signals rather than dominating the pillar on the strength of one live test. Not measured when the audit's own fetch was not a 2xx HTML page or no crawler was judged.
Structured-data depthGEO_GRADE_JSONLD_COMPLETENESS1.5LocalBusiness-type entity: name 0.25 + address 0.25 + telephone 0.2 + url 0.2 + openingHours 0.1 (a PostalAddress without streetAddress earns 60% of the address mark, a plain-text address 40%). Organization-type entity: name 0.25 + url 0.2 + logo 0.15 + sameAs 0.2 + contact 0.2. The best-described entity counts; only a WebSite/Person/Product/Service entity → 0.4.Google's LocalBusiness structured-data documentation (name and PostalAddress required; telephone, url, opening hours recommended) and Organization documentation (name, url, logo, sameAs, contact route recommended); the point split is our judgment. The note names the missing properties.
Entity consistencyGEO_GRADE_ENTITY_CONSISTENCY1.0signals that agree ÷ signals compared, across: JSON-LD name(s), og:site_name, the title-tag segment carrying the brand, an H1 carrying the brand, and each JSON-LD telephone and street address checked against the pagesA plain ratio; case, punctuation and legal suffixes are tolerated (our judgment). Fewer than two comparable signals: not measured.
Direct answers under questionsGEO_GRADE_ANSWERABILITY1.0questions answered directly ÷ questions found (H2/H3 question headings plus FAQPage entries). "Directly" is a first-sentence rule: the text in the question's own section opens with a sentence of 40 words or fewer that is not itself another question. A bare "Yes." or "No." counts, and so does a list, table or definition list opening the section — those are answer forms in their own right. Under 5 words is a label, not an answer; 300 words is a far outer bound on the section.Our judgment: the question-plus-short-answer shape is the fragment assistants quote whole. What makes an answer quotable is that it OPENS with the answer — so the length of the whole paragraph is not the test, only its first sentence. A thorough answer is not a worse answer. No questions anywhere: not measured (the question-heading signal already prices that).
Question-shaped headingsGEO_GRADE_QUESTION_HEADINGS1.0≥ 4 question(s) → 1.0; ≥ 2 question(s) → 0.75; ≥ 1 question(s) → 0.5; below every band → 0Our judgment: one lone question heading is a token gesture; a working FAQ carries four or more.
Lists and tablesGEO_GRADE_ANSWER_STRUCTURE0.75≥ 3 element(s) → 1.0; ≥ 1 element(s) → 0.7; below every band → 0Our judgment: structured fragments are what AI engines lift verbatim, but weaker evidence than a direct answer.
Freshness signalsGEO_GRADE_FRESHNESS0.5age of the newest machine-readable date (JSON-LD dateModified/datePublished, dated meta tags, a content Last-Modified header, sitemap lastmod): ≤ 90 day(s) → 1.0; ≤ 365 day(s) → 0.75; ≤ 730 day(s) → 0.5; past every band → 0.25; no machine-readable date at all → not measured, so the signal leaves the denominator entirelyOur judgment. Scoring the absence of a date was a perverse incentive: publishing nothing scored better than publishing an honest three-year-old date, so a silent site outranked a candid one. Nothing measured now means nothing judged — the report still says plainly that no date signal was found. A Last-Modified or lastmod stamped within 60 s of the response's own Date header is a request-time stamp, not a content date, and is ignored; so is a Last-Modified header on a near-empty page with no in-document date, where it is usually a cache-fill timestamp.
llms.txt qualityGEO_GRADE_LLMS_QUALITY0.5H1 title 0.3 + markdown links 0.3 + "##" sections 0.2 + 200+ characters 0.2The llms.txt spec shape; the mark weights are our judgment. Absent or unfetched file: not measured here (the binary finding prices absence). Lightest weight, per the evidence above.

Why these weights: structured-data depth carries the most because those fields are what assistants lift verbatim; consistency, answerability and question headings are the signals that let an engine merge the pages into one entity and quote it; lists and tables, freshness and llms.txt are real but softer. The live access test is not on that ladder at all: it only ever takes points off, and only when a crawler was actually turned away — a site nothing was refused on is not measured on it and is not credited for it either. The split is our judgment and is stated here as exactly that.

Honesty rules

What we refuse to do

These rules are enforced in the code, not promised in the copy.

Couldn't verify means no judgment

When a check could not gather its evidence, it stays silent instead of guessing — in all three layers. A pillar check whose probe never completed emits no finding. A graded signal that could not be measured leaves the scoring denominator entirely, so an unmeasured signal can neither help nor hurt. A URL the probe budget never reached is reported as unchecked — never counted as broken.

No invented numbers

Every count, size and timing in a finding is measured from the bytes the audit actually fetched, and the finding names its evidence (paths, sizes, character counts). Sampled and extrapolated values are labeled as exactly that. The benchmark median on reports renders only once at least 20 distinct real sites have been audited — below that, nothing renders, because a median of a handful of sites is noise.

The checks never ask a model

Findings and scores come from deterministic code with no language model in the loop. A model only phrases the action plan (and, on Pro, fix hints), grounded solely in the findings it is handed — it cannot add a finding or move a score, and if it fails, a deterministic plan built straight from the findings renders instead.

One model, one moment

The Pro AI-visibility check asks one language model the kinds of questions your customers ask and records whether your business appears. It is labeled as one model at one moment — a snapshot, never a guarantee of rankings or AI mentions. Scores describe what the checks found on the day they ran, nothing more.

Accountability

Report an inaccurate finding

If a finding on your report is wrong, email the report link — a person re-runs the check and answers.

Contact Corpus Plan

Use the subject line "AUDIT ACCURACY". Because the audit is deterministic, the disputed check re-runs against your site exactly as it ran the first time — the answer you get is the re-run's evidence, not an opinion.