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? Email 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, then runs three small probe phases — and nothing else. Every request stays on the audited site.
| Phase | Budget | What 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. |
| Rule on every request | Limit |
|---|---|
| Protocols and ports | http/https only, standard web ports only, no credentials in URLs |
| Address guard | Every 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. |
| Redirects | at most 10 hops per fetch |
| Response body | at most 5 MB read per response |
| Time per fetch | at most 15 s wall clock, redirects included |
| Identification | every 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.
| Severity | Deduction |
|---|---|
| Critical | 35 points |
| Major | 15 points |
| Minor | 5 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.
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.
| Pillar | Weight in the overall score |
|---|---|
| SEO | 25% |
| Speed | 15% |
| Trust | 20% |
| Content | 15% |
| AI citability | 25% |
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.
| Check | Code | Severity | Fires when |
|---|---|---|---|
| Homepage title | SEO_MISSING_TITLE | Critical | The homepage has no <title> tag. |
| Title length | SEO_TITLE_LENGTH | Minor | The homepage title is outside 15–65 characters. |
| noindex | SEO_NOINDEX_HOMEPAGE | Critical | A robots meta tag with noindex tells engines to drop the homepage from their index. |
| Meta description | SEO_MISSING_META_DESCRIPTION | Major | The homepage has no meta description. |
| Description length | SEO_META_DESCRIPTION_LENGTH | Minor | The meta description is outside 50–165 characters. |
| H1 heading | SEO_MISSING_H1 | Major | The homepage has no H1. |
| Multiple H1 | SEO_MULTIPLE_H1 | Minor | The homepage carries more than one H1. |
| Subheadings | SEO_NO_SUBHEADINGS | Minor | No H2/H3 subheadings on the homepage. |
| Canonical link | SEO_MISSING_CANONICAL | Minor | No rel="canonical" link tag on the homepage. |
| Image alt text | SEO_IMG_ALT_MISSING | Major / Minor | Under 50% of sampled homepage images carry alt text (major); 50–80% (minor). Sample: the first 30 images. |
| robots.txt blocks all | SEO_ROBOTS_BLOCKS_ALL | Critical | robots.txt disallows the whole site for every user-agent. |
| robots.txt is HTML | SEO_ROBOTS_NOT_TEXT | Minor | /robots.txt answers with an HTML page instead of a plain-text file. |
| robots.txt missing | SEO_MISSING_ROBOTS | Minor | /robots.txt answers 404. |
| Sitemap invalid | SEO_SITEMAP_INVALID | Minor | A sitemap responds but is not valid XML. |
| Sitemap missing | SEO_MISSING_SITEMAP | Minor | The sitemap answers 404. |
| Pages without titles | SEO_PAGES_MISSING_TITLE | Major | Crawled inner pages with no <title>, each path named. |
| Page title lengths | SEO_PAGES_TITLE_LENGTH | Minor | Inner-page titles outside 15–65 characters. |
| Pages without descriptions | SEO_PAGES_MISSING_META_DESCRIPTION | Minor | Crawled inner pages with no meta description. |
| Pages without H1 | SEO_PAGES_MISSING_H1 | Minor | Crawled inner pages with no H1. |
| Pages with multiple H1 | SEO_PAGES_MULTIPLE_H1 | Minor | Crawled inner pages with more than one H1. |
| Duplicate titles | SEO_DUPLICATE_TITLES | Major | The same title on two or more crawled pages (post-redirect duplicates of the same document are excluded). |
| Duplicate descriptions | SEO_DUPLICATE_META_DESCRIPTIONS | Minor | The same meta description shared by two or more crawled pages. |
| Broken internal links | SEO_BROKEN_INTERNAL_LINKS | Major | Probed 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 URLs | SEO_SITEMAP_DEAD_URLS | Major | Sampled sitemap URLs answering HTTP 404/410 — same only-what-was-checked rule. |
| Graded signal | Code | Weight | Scored against | Basis |
|---|---|---|---|---|
| Title length | SEO_GRADE_TITLE | 1.0 | missing → 0; 30–60 characters → 1.0; else within 15–65 → 0.75; outside → 0.4 | Google truncates result titles around 60 characters (published guidance); the partial-credit steps are our judgment. |
| Meta description length | SEO_GRADE_META_DESCRIPTION | 1.0 | missing → 0; 70–155 characters → 1.0; else within 50–165 → 0.75; outside → 0.4 | Google cuts descriptions around 155–160 characters on desktop results; steps our judgment. |
| Heading structure | SEO_GRADE_HEADINGS | 1.0 | one H1 with ordered levels → 1.0; −0.25 for no H2/H3; −0.25 for skipped levels; multiple H1 → 0.5; no H1 → 0.2 | Our judgment: an ordered outline is what crawlers and screen readers parse best. |
| Canonical link | SEO_GRADE_CANONICAL | 0.5 | present → 1.0; absent → 0 | Plain presence check. |
| Internal links from the homepage | SEO_GRADE_INTERNAL_LINKS | 1.0 | ≥ 8 path(s) → 1.0; ≥ 4 path(s) → 0.85; ≥ 1 path(s) → 0.6; below every band → 0 | Our judgment: a normal navigation plus footer links eight or more distinct pages. |
Pillar · weight 15%
Speed
Server response time, page weight, compression and mobile setup — measured from the actual fetches, never estimated from a database.
| Check | Code | Severity | Fires when |
|---|---|---|---|
| Server response time | SPEED_SLOW_RESPONSE | Major / Minor | The homepage takes 2.0 s or more to start responding (major); 0.8 s or more (minor). |
| Homepage HTML weight | SPEED_HEAVY_HTML | Major / Minor | The homepage HTML alone is 3072 KB or more (major); 1536 KB or more (minor). |
| Text compression | SPEED_NO_COMPRESSION | Major | The homepage response carries no gzip/brotli/zstd/deflate Content-Encoding header. |
| Mobile viewport | SPEED_NO_VIEWPORT | Major | No viewport meta tag — the site renders as a shrunken desktop page on phones. |
| Slow inner pages | SPEED_SLOW_PAGES | Minor | The median response time across three or more crawled inner pages is 2.0 s or more. |
| Redirect chain | SPEED_REDIRECT_CHAIN | Minor | The homepage settles only after more than 2 redirects; one hop (http to https, or apex to www) is normal. |
| Oversized assets | SPEED_HEAVY_ASSET | Minor | A sampled homepage asset transfers 1024 KB (1 MB) or more, named with its measured size. Assets the budget never sized are not judged. |
| Graded signal | Code | Weight | Scored against | Basis |
|---|---|---|---|---|
| Server response time | SPEED_GRADE_TTFB | 1.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 → 0 | Lighthouse'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 weight | SPEED_GRADE_HTML_WEIGHT | 1.0 | ≤ 50 KB → 1.0; ≤ 100 KB → 0.85; ≤ 200 KB → 0.6; ≤ 500 KB → 0.35; ≤ 1024 KB → 0.15; past every band → 0 | HTTP Archive Web Almanac: the median HTML document transfers around 30 KB. |
| Inline CSS/JS in the HTML | SPEED_GRADE_INLINE_BLOAT | 1.0 | ≤ 15 KB → 1.0; ≤ 40 KB → 0.75; ≤ 100 KB → 0.5; ≤ 250 KB → 0.25; past every band → 0 | Critical-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 compression | SPEED_GRADE_COMPRESSION | 0.5 | compressed → 1.0; none → 0 | Plain presence check on the Content-Encoding header. |
| Homepage image count | SPEED_GRADE_IMG_COUNT | 0.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.15 | HTTP Archive Web Almanac media chapter: the median page carries around 20–25 images. |
| Estimated page weight with assets | SPEED_GRADE_PAGE_WEIGHT | 1.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.1 | HTTP 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.
| Check | Code | Severity | Fires when |
|---|---|---|---|
| TLS certificate | TRUST_TLS_ERROR | Critical | HTTPS failed with a certificate or TLS error and the site was only reachable over insecure HTTP. |
| HTTPS | TRUST_NO_HTTPS | Critical | The site is served over plain HTTP — it never answered over HTTPS, or the homepage lands on an http:// URL. |
| Homepage status | TRUST_HOMEPAGE_HTTP_ERROR | Critical | The homepage answers an HTTP error status (400 or higher). |
| HSTS | TRUST_MISSING_HSTS | Minor | An HTTPS site without a Strict-Transport-Security header. |
| Security headers | TRUST_MISSING_SECURITY_HEADERS | Minor | X-Content-Type-Options, or X-Frame-Options/Content-Security-Policy, missing from the homepage response. |
| Mixed content | TRUST_MIXED_CONTENT | Major | Scripts, images, iframes, stylesheets or form targets loading over plain http:// on HTTPS pages — every crawled page checked, affected pages named. |
| Contact routes | TRUST_NO_CONTACT_INFO | Major | No phone number, email address or contact link found on the homepage. |
| Call to action | TRUST_NO_CTA | Major | No book/call/quote/buy/contact wording in any homepage link or button. |
| Legal pages | TRUST_NO_LEGAL_PAGES | Minor | No privacy policy link, or no terms link. |
| Broken pages | TRUST_BROKEN_PAGES | Major | Crawled pages answering HTTP errors (the robots.txt/llms.txt/sitemap probes are reported by their own pillars, not here). |
| Graded signal | Code | Weight | Scored against | Basis |
|---|---|---|---|---|
| Trust-signal completeness | TRUST_GRADE_SIGNALS | 2.0 | signals present ÷ 8, across: phone, email, contact link, call to action, privacy link, terms link, a social profile link, Open Graph tags | A plain ratio; the eight-signal list is our judgment. The report names exactly which signals are missing. |
| Security headers | TRUST_GRADE_SECURITY_HEADERS | 1.0 | headers 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.
| Check | Code | Severity | Fires when |
|---|---|---|---|
| Homepage copy depth | CONTENT_THIN_HOMEPAGE | Major / Minor | Under 120 words of visible text on the homepage (major); under 300 (minor). |
| Thin inner pages | CONTENT_THIN_PAGES | Minor | Most crawled inner pages carry under 120 words. |
| Site footprint | CONTENT_FEW_PAGES | Minor | The sitemap lists fewer than 5 pages. |
| Copyright freshness | CONTENT_STALE_COPYRIGHT | Minor | The newest copyright year on the homepage is two or more years old. No year on the page: no finding. |
| Open Graph tags | CONTENT_MISSING_OG | Minor | og:title is missing, or present without og:description or og:image — shared links render bare. |
| Graded signal | Code | Weight | Scored against | Basis |
|---|---|---|---|---|
| Homepage copy depth | CONTENT_GRADE_HOME_WORDS | 1.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.2 | Our judgment, anchored on the 120/300-word binary thresholds; full credit needs a genuinely substantial page. |
| Site copy depth | CONTENT_GRADE_SITE_WORDS | 1.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.2 | Our judgment — the same anchoring, scaled to the handful of pages one audit fetches. |
| Image alt coverage | CONTENT_GRADE_ALT_COVERAGE | 1.0 | images with alt text ÷ sampled images (first 30 per page) | A plain ratio. No images on the crawled pages: not measured, no judgment. |
| Copyright-year freshness | CONTENT_GRADE_FRESHNESS | 0.5 | current or last year → 1.0; two years old → 0.5; older → 0.2 | Our judgment (a January page still saying last year is normal). |
Pillar · weight 25%
AI citability
Whether AI assistants can read, parse and cite the site. The AI crawlers checked in robots.txt: GPTBot, ClaudeBot, anthropic-ai, PerplexityBot, Google-Extended.
| Check | Code | Severity | Fires when |
|---|---|---|---|
| llms.txt | GEO_MISSING_LLMS_TXT | Major | /llms.txt answers with an error — the file that gives assistants a curated guide to the business does not exist. |
| llms.txt structure | GEO_LLMS_TXT_THIN | Minor | An llms.txt exists but lacks a markdown H1 title, or has neither links nor 200+ characters of substance. |
| Structured data | GEO_MISSING_JSONLD | Major | No schema.org JSON-LD found on any crawled page. |
| JSON-LD validity | GEO_JSONLD_INVALID | Minor | JSON-LD blocks exist but none parse as valid JSON. |
| Business identity schema | GEO_NO_ORG_SCHEMA | Minor | JSON-LD parses but no entity identifies the business itself (Organization, LocalBusiness, WebSite or similar). |
| All AI crawlers blocked | GEO_ALL_AI_BOTS_BLOCKED | Critical | robots.txt blocks every one of the checked AI crawlers — the site cannot appear in AI-assistant answers. The finding says plainly this may be intentional. |
| Some AI crawlers blocked | GEO_AI_BOTS_BLOCKED | Major | robots.txt blocks some of the checked AI crawlers, each named. |
| Extractable content | GEO_LOW_TEXT_RATIO | Major / Minor | The 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 headings | GEO_NO_QUESTION_HEADINGS | Minor | No H2/H3 on any crawled page is phrased as a question. |
| Answer structure | GEO_NO_ANSWER_STRUCTURE | Minor | The homepage carries 150+ words but no list or table — nothing structured for an assistant to quote. |
| Graded signal | Code | Weight | Scored against | Basis |
|---|---|---|---|---|
| llms.txt quality | GEO_GRADE_LLMS_QUALITY | 1.0 | H1 title 0.3 + markdown links 0.3 + "##" sections 0.2 + 200+ characters 0.2 | The llms.txt spec shape; the mark weights are our judgment. Absent or unfetched file: not measured here (the binary finding prices absence). |
| Structured-data completeness | GEO_GRADE_JSONLD_COMPLETENESS | 1.0 | identity entity 0.4 + name 0.2 + url 0.2 + a contact field 0.2 | Our judgment on which schema.org fields assistants actually lift. |
| Question-shaped headings | GEO_GRADE_QUESTION_HEADINGS | 1.0 | ≥ 4 question(s) → 1.0; ≥ 2 question(s) → 0.75; ≥ 1 question(s) → 0.5; below every band → 0 | Our judgment: one lone question heading is a token gesture; a working FAQ carries four or more. |
| Lists and tables | GEO_GRADE_ANSWER_STRUCTURE | 1.0 | ≥ 3 element(s) → 1.0; ≥ 1 element(s) → 0.7; below every band → 0 | Our judgment: structured fragments are what AI engines lift verbatim. |
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.
Subject line "AUDIT ACCURACY" is pre-filled. 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.