A

Methodology

How we score, in plain math.

Every check has a severity. Every surface has a weight. Every audit stamps the spec versions it ran against. Below is the exact formula — the same one our scoring engine uses, generated from the live engine registry.

ACP2026-04-17UCP2026-04-08Stripe2026-04-22.previewEngine2.0.0

1 · What every check produces

All 82 checks follow the same shape. Each one targets a single, named signal and produces:

  • a permanent slug (e.g. product-jsonld-present) plus a category display ID derived from its position (e.g. STRUCT-9);
  • a severity — CRITICAL, HIGH, MEDIUM, LOW, or INFO;
  • a check status (`pass`, `partial`, `fail`, `na`, `error`) plus a discrete raw score (`100`, `50`, `0`) for scored states;
  • a per-surface weight — how much the signal matters to each AI surface;
  • rich evidence — what we looked at, what method we used, the raw HTTP / JSON-LD / sitemap artifacts, and per-issue findings with offending and expected snippets.

Each check resolves to one of five states:

  • pass/partial/failScored states. Raw scores are `100` (pass), `50` (partial), `0` (fail); included in aggregation.
  • naCheck does not apply to this audit; excluded from aggregation.
  • errorRuntime error during this check; excluded from aggregation.

2 · Severity weights

Severity weights drive the impact each check has on its surface sub-score.

SeverityWeightWhat it means
CRITICAL10Blocks one or more agent surfaces from transacting or discovering the store
HIGH5Significantly degrades discoverability or trust on at least one surface
MEDIUM3Suboptimal but won't block; agents fall back to inference
LOW1Minor polish; affects relative ranking, not inclusion
INFO0Informational; doesn't affect the score

3 · Surface weights

The five AI shopping surfaces don't contribute equally to the overall score. Weights reflect how much of each surface's behavior is public-crawl-verifiable today — Google UCP and Schema.org carry the heaviest weight because most of their conformance is observable from the public web.

SurfaceWeightSpec pinned to
ChatGPT (via ACP)25ACP 2026-04-17
Google AI Mode (via UCP)30UCP 2026-04-08
Microsoft Copilot15UCP-compatible (inferred)
Perplexity17Google merchant listing + GTIN-mandatory
Meta AI13Meta catalog feed spec
Total100

Weights are configuration, not constants — they're versioned in the scoring engine and change only through a reviewed release, never edited ad hoc.

4 · Per-surface sub-score formula

For each surface S, the sub-score is the weighted average of all applicable checks:

subscore(S) = sum( rawScore_i × severity_i × surfaceAffected_i[S] )
              ─────────────────────────────────────────────────────
              sum( 100 × severity_i × surfaceAffected_i[S] )

  for all checks i where:
    - result is not `na`
    - result is not `error`
    - surfaceAffected_i[S] > 0

Worked example

Perplexity sub-score on a Shopify store with 3 relevant checks:

CheckSeveritysurfaceAffected[perplexity]rawScore
product-gtin-populatedCRITICAL (10)1000
merchant-return-policy-presentHIGH (5)8050
offer-shipping-details-presentMEDIUM (3)100100
numerator   = (0 × 10 × 100) + (50 × 5 × 80) + (100 × 3 × 100)
            = 0 + 20000 + 30000
            = 50000

denominator = (100 × 10 × 100) + (100 × 5 × 80) + (100 × 3 × 100)
            = 100000 + 40000 + 30000
            = 170000

subscore(perplexity) = round(50000 / 170000 × 100) = 29

Perplexity sub-score for this store: 29.

5 · Overall score formula

The overall score is a surface-weight-blended average of the five sub-scores:

overall = sum( subscore(S) × surfaceWeight(S) )
          ──────────────────────────────────────
          sum( surfaceWeight(S) )

  = ( subscore(chatgpt_acp) × 25
    + subscore(google_ucp)  × 30
    + subscore(microsoft)   × 15
    + subscore(perplexity)  × 17
    + subscore(meta)        × 13 )
    / 100

The denominator is 100 by construction (the surface weights sum to 100). The division is shown explicitly for clarity.

Issue-level impact in the report uses this same blend. A check that costs 3 points on ChatGPT contributes 0.75 points to the headline score because ChatGPT carries 25% of the overall weight. In the issue list, hover the impact value (for example `−5`) to see the unweighted per-surface score subtraction for each surface.

6 · Grade bands

The 0–100 overall maps to a letter grade for at-a-glance reading:

A

90–100

B

75–89

C

60–74

D

40–59

F

0–39

Grade colors are semantic and always paired with the letter. The color is never the sole signal.

7 · Check inventory

The full list of 82 active checks, grouped by category. Each row shows the display ID (derived from the check's position in its category), the canonical slug, severity, and which specs the check normatively cites.

Discovery

25 checks · prefix DISC
IDSlugNameSeveritySpecs
DISC-1bingbot-allowedBingbot allowed

Fix: Allow Bingbot in robots.txt

HIGH
DISC-2chatgpt-user-allowedChatGPT-User allowed

Fix: Allow ChatGPT-User in robots.txt (advisory)

LOW
DISC-3googlebot-allowed-on-productsGooglebot allowed on product paths

Fix: Allow Googlebot on product paths

CRITICAL
DISC-4llms-txt-presentllms.txt present (informational)

Fix: Publish an /llms.txt manifest (optional)

INFO
DISC-5openai-search-bot-allowedOAI-SearchBot allowed

Fix: Allow OAI-SearchBot in robots.txt

CRITICAL
DISC-6pdp-not-behind-loginSampled PDPs are not gated behind a login wall (401 / 403)

Fix: Open PDPs to anonymous fetches

HIGH
DISC-7pdp-not-noindexNo sampled PDP returns a noindex directive

Fix: Remove the noindex directive from every PDP

HIGH
DISC-8pdp-single-product-pageEach PDP carries at most one Product JSON-LD node

Fix: Emit a single Product JSON-LD node per PDP

HIGH
DISC-9perplexity-bot-allowedPerplexityBot allowed

Fix: Allow PerplexityBot in robots.txt

HIGH
DISC-10perplexity-user-allowedPerplexity-User allowed

Fix: Allow Perplexity-User in robots.txt (advisory)

LOW
DISC-11products-discoverable-no-jsProduct pages discoverable without JavaScript

Fix: Make product pages discoverable without JavaScript

HIGH
DISC-12products-machine-discoverableProducts are machine-discoverable

Fix: Publish a product feed or a crawlable product sitemap

HIGH
DISC-13robots-content-type-plain/robots.txt is served as text/plain

Fix: Send Content-Type: text/plain on /robots.txt

LOW
DISC-14robots-txt-presentrobots.txt present at root

Fix: Publish a non-empty robots.txt at the site root

HIGH
DISC-15robots-under-500kib/robots.txt is under 500 KiB (RFC 9309 §2.5 parser cap)

Fix: Trim /robots.txt below 500 KiB

LOW
DISC-16robots-utf8/robots.txt is served as UTF-8

Fix: Serve /robots.txt as UTF-8

LOW
DISC-17sitemap-declared-in-robotsSitemap declared in robots.txt

Fix: Add a `Sitemap:` line to robots.txt

MEDIUM
DISC-18sitemap-entries-escapedSitemap <loc> entries are entity-escaped

Fix: Entity-escape `&`, `<`, `>` in every <loc>

MEDIUM
DISC-19sitemap-loc-under-2048Every sitemap <loc> URL is under 2048 characters

Fix: Keep every <loc> URL under 2,048 characters

LOW
DISC-20sitemap-resolvable-with-productsSitemap resolvable and includes at least one product URL

Fix: Publish a sitemap containing product URLs

MEDIUM
DISC-21sitemap-same-hostSitemap entries share the host of the containing sitemap

Fix: Keep every sitemap entry on the sitemap's own host

MEDIUM
DISC-22sitemap-size-limitsSitemap respects 50 MiB / 50,000-URL caps per document

Fix: Split over-cap sitemaps into a sitemap index

LOW
DISC-23sitemap-urlset-namespaceSitemap root declares the sitemaps.org 0.9 namespace

Fix: Add the sitemaps.org 0.9 xmlns to the root element

MEDIUM
DISC-24sitemap-utf8Sitemap is served as UTF-8

Fix: Serve every sitemap document as UTF-8

LOW
DISC-25wildcard-root-disallowNo global wildcard root disallow

Fix: Remove the wildcard `Disallow: /` from robots.txt

CRITICAL

Structured data

12 checks · prefix STRUCT
IDSlugNameSeveritySpecs
STRUCT-1breadcrumb-list-presentBreadcrumbList present on PDPs

Fix: Add a BreadcrumbList JSON-LD block to every PDP

LOW
STRUCT-2offer-availability-schema-urlOffer `availability` is a Schema.org URL

Fix: Use a canonical Schema.org availability IRI on every Offer

HIGH
STRUCT-3offer-item-condition-when-not-newOffer `itemCondition` is canonical when present

Fix: Either omit `itemCondition` (defaults to NewCondition) or set it to a canonical IRI

LOW
STRUCT-4offer-price-currency-validOffer price + priceCurrency valid

Fix: Set price as a number and priceCurrency as an ISO 4217 code

CRITICAL
STRUCT-5product-aggregate-rating-presentProduct `aggregateRating` present

Fix: Add an AggregateRating to Product nodes when you have real reviews

LOW
STRUCT-6product-brand-string-or-objectProduct `brand` is a string or Brand/Organization object

Fix: Emit `brand` as either a string or a typed Brand object on every Product

MEDIUM
STRUCT-7product-description-presentProduct `description` present

Fix: Populate `description` on every Product JSON-LD node

MEDIUM
STRUCT-8product-image-populatedProduct `image` populated

Fix: Add a resolvable image URL to every Product node

HIGH
STRUCT-9product-jsonld-presentProduct JSON-LD present on PDPs

Fix: Publish a Product JSON-LD block on every PDP

HIGH
STRUCT-10product-name-populatedProduct `name` populated

Fix: Populate `name` on every Product JSON-LD node

HIGH
STRUCT-11product-offers-presentProduct JSON-LD includes `offers`

Fix: Add an `offers` object to every Product node

HIGH
STRUCT-12product-sku-populatedProduct `sku` populated

Fix: Populate `sku` on every Product JSON-LD node

MEDIUM

Product data

4 checks · prefix PROD
IDSlugNameSeveritySpecs
PROD-1product-brand-attributionBrand attribution on PDPs

Fix: Surface brand attribution on every PDP

HIGH
PROD-2product-gtin-populatedGTIN coverage on PDPs

Fix: Populate `gtin` on every branded Product node

HIGH
PROD-3product-title-no-placeholdersProduct title not a placeholder

Fix: Replace placeholder and slug-shape titles with real product names

MEDIUM
PROD-4product-title-qualityProduct title quality (present, not all-caps)

Fix: Use sentence-case product titles

LOW

Policy

15 checks · prefix POL
IDSlugNameSeveritySpecs
POL-1merchant-return-link-reachableMerchantReturnPolicy merchantReturnLink URL is reachable

Fix: Repair every merchantReturnLink URL

MEDIUM
POL-2merchant-return-policy-applicable-country-isoMerchantReturnPolicy applicableCountry uses ISO 3166-1 alpha-2 codes

Fix: Use ISO 3166-1 alpha-2 country codes in applicableCountry

MEDIUM
POL-3merchant-return-policy-category-enumMerchantReturnPolicy returnPolicyCategory uses valid Schema.org enum

Fix: Use a valid Schema.org returnPolicyCategory enum value

MEDIUM
POL-4merchant-return-policy-enums-validMerchantReturnPolicy enrichment enums use valid Schema.org values

Fix: Use Schema.org enum values for returnFees / returnMethod / refundType

LOW
POL-5merchant-return-policy-finite-daysMerchantReturnPolicy finite-window has positive merchantReturnDays

Fix: Add a positive `merchantReturnDays` to finite-window return policies

HIGH
POL-6merchant-return-policy-option-a-or-bMerchantReturnPolicy satisfies Option A (country+category) or B (returnLink)

Fix: Make every MerchantReturnPolicy node satisfy Option A or Option B

HIGH
POL-7merchant-return-policy-presentMerchantReturnPolicy node present on Product or Offer

Fix: Emit `hasMerchantReturnPolicy` on Product or Offer JSON-LD

HIGH
POL-8offer-shipping-delivery-time-validOfferShippingDetails deliveryTime is a valid ShippingDeliveryTime

Fix: Emit a ShippingDeliveryTime with handlingTime and/or transitTime populated

LOW
POL-9offer-shipping-destination-validOfferShippingDetails shippingDestination is a valid DefinedRegion

Fix: Emit shippingDestination as a DefinedRegion with ISO addressCountry

MEDIUM
POL-10offer-shipping-details-presentOffer JSON-LD carries shippingDetails (OfferShippingDetails)

Fix: Emit shippingDetails (OfferShippingDetails) on Offer JSON-LD

HIGH
POL-11offer-shipping-rate-validOfferShippingDetails shippingRate is a valid MonetaryAmount

Fix: Emit shippingRate as a valid MonetaryAmount

MEDIUM
POL-12privacy-policy-page-reachablePrivacy policy page reachable

Fix: Publish a privacy policy page and link it from your site nav/footer

HIGH
POL-13returns-policy-page-reachableReturns/refund policy page reachable

Fix: Publish a returns policy page and link it from your site nav/footer

MEDIUM
POL-14shipping-policy-page-reachableShipping policy page reachable

Fix: Publish a shipping policy page and link it from your site nav/footer

MEDIUM
POL-15terms-of-service-page-reachableTerms of service page reachable

Fix: Publish a terms of service page and link it from your site nav/footer

HIGH

Trust

9 checks · prefix TRUST
IDSlugNameSeveritySpecs
TRUST-1about-page-reachableAbout page reachable with substantive copy

Fix: Publish a substantive About page at a standard URL

LOW
TRUST-2contact-with-email-or-phoneContact page exposes email or phone

Fix: Add a `mailto:` email link or `tel:` phone link to your contact page

HIGH
TRUST-3review-app-detectedThird-party review-platform integration detected

Fix: Install a third-party review platform so agents see syndicated reviews on your storefront

MEDIUM
TRUST-4https-and-hsts-enforcedHTTPS enforced sitewide + HSTS (≥ 6-month max-age)

Fix: Enforce HTTPS sitewide and ship a Strict-Transport-Security header with max-age ≥ 6 months

CRITICAL
TRUST-5hsts-include-subdomainsHSTS policy carries the includeSubDomains directive

Fix: Add `includeSubDomains` to your Strict-Transport-Security header

MEDIUM
TRUST-6hsts-preload-directiveHSTS policy carries the preload directive

Fix: Add `preload` to your Strict-Transport-Security header and submit to hstspreload.org

LOW
TRUST-7apple-pay-detectedApple Pay markers detected (informational)

Fix: Enable Apple Pay through your payment processor (informational only)

INFO
TRUST-8google-pay-detectedGoogle Pay markers detected (informational)

Fix: Enable Google Pay through your payment processor (informational only)

INFO
TRUST-9organization-jsonld-with-contactOrganization/OnlineStore JSON-LD with contactPoint on homepage

Fix: Add an Organization (or OnlineStore) JSON-LD block to your homepage with a contactPoint

MEDIUM

Protocol (UCP)

15 checks · prefix PROT
IDSlugNameSeveritySpecs
PROT-1ucp-cache-headers-validUCP profile Cache-Control is shared-cacheable with max-age ≥ 60s

Fix: Serve `/.well-known/ucp` with `Cache-Control: public, max-age=…`

HIGH
PROT-2ucp-capability-required-fieldsEach capability has version + spec + schema

Fix: Populate version, spec, and schema on every capabilities[] entry

MEDIUM
PROT-3ucp-mcp-transport-validUCP MCP-transport entries have valid HTTPS endpoints

Fix: Make every declared MCP transport endpoint an absolute HTTPS URL

LOW
PROT-4ucp-profile-content-type-json/.well-known/ucp response Content-Type is application/json

Fix: Serve /.well-known/ucp with `Content-Type: application/json`

HIGH
PROT-5ucp-profile-no-auth-required/.well-known/ucp is publicly fetchable with no auth

Fix: Allow unauthenticated access to /.well-known/ucp

HIGH
PROT-6ucp-profile-no-redirects/.well-known/ucp returns 200 directly with no redirects

Fix: Serve /.well-known/ucp directly with a 200 response

HIGH
PROT-7ucp-profile-present/.well-known/ucp profile is present with a `version` field

Fix: Publish `/.well-known/ucp` with at minimum a `version` field

HIGH
PROT-8ucp-profile-required-keysUCP profile carries all four required top-level keys

Fix: Add every required top-level key to the UCP profile

HIGH
PROT-9ucp-service-spec-url-origin-matchesEach service's `spec` URL origin matches its namespace authority

Fix: Point each service `spec` URL at the canonical UCP authority

MEDIUM
PROT-10ucp-service-transport-conditional-fieldsEach service satisfies the transport-conditional field requirements

Fix: Populate the conditional fields required by each service's transport

HIGH
PROT-11ucp-service-transport-enumEach service `transport` is rest, mcp, a2a, or embedded

Fix: Set transport to one of rest, mcp, a2a, or embedded

HIGH
PROT-12ucp-service-version-date-formatEvery service `version` matches YYYY-MM-DD

Fix: Use ISO-date `version` strings on every service

MEDIUM
PROT-13ucp-shopping-service-validUCP profile declares a valid shopping service entry

Fix: Declare a shopping service entry with a recognised transport and an HTTPS endpoint

HIGH
PROT-14ucp-signing-keys-validEvery signing_keys[] entry is a valid JWK

Fix: Make every signing_keys[] entry a JWK with kty + kty-specific params

HIGH
PROT-15webmcp-declarative-tools-validDeclarative WebMCP forms are valid

Fix: Give every declarative WebMCP form a toolname, tooldescription, and named, described parameters

LOW

Images

2 checks · prefix IMG
IDSlugNameSeveritySpecs
IMG-1image-area-50k-pixelsProduct images meet Google’s 50,000-pixel area threshold

Fix: Upload higher-resolution product images (area ≥ 50,000 pixels)

LOW
IMG-2image-alt-text-coverageAlt text on at least 80% of PDP images

Fix: Add descriptive alt text to product images (WCAG 2.x SC 1.1.1)

LOW

Inventory rendered from the live check registry at build time. The retired check ledger and parked checks live on /spec-status.

8 · Platform normalization

Some checks are unfair to score uniformly across platforms because the default differs. Platform normalization currently lives in check logic (per-check) and is versioned through engine releases.

Every check has a fix.byPlatform recipe for shopify, bigcommerce, woocommerce, and custom — so a platform-detection miss never leaves the merchant without actionable guidance.

9 · Reproducibility

Every audit persists:

  • Engine version — current semver of packages/engine is 2.0.0. Stamped on every audit row.
  • Spec versions — exact pinned versions of ACP, UCP, Stripe SPT (Shared Payment Token). Perplexity, Meta, and Copilot tracks are modeled from public docs but are not first-class pinned spec-version fields in the audit record today.
  • Raw snapshots — every v2 audit captures the robots.txt / sitemap / JSON-LD it observed into a per-audit tarball so the report can be re-rendered without a recrawl.

Spec-drift changes are surfaced in /spec-status.

10 · Confidence and inference

Some signals are not in canonical spec documents. Microsoft Copilot in particular has thin merchant documentation — anything past Merchant Center, UCP feed, and Bingbot is inference.

When a check is based on inference rather than canonical spec, the report tags it:

  • canonicalDerived from the published ACP / UCP / Stripe / Meta / Perplexity spec.
  • inferredBased on observed behavior of the surface, vendor blog posts, or partner statements.
  • speculativeKnown unknowns; included for transparency but does not affect the score.

11 · What we deliberately don't score

  • Brand awareness or perceived authority. We score data hygiene, not reputation. Domain Rating, social signals, and editorial coverage don't enter the score.
  • Conversion rate, CTR, or revenue. Those are CRO and analytics problems, not agent-readiness problems.
  • Aesthetic quality of the storefront. Beautiful stores can score 30; ugly stores can score 90. Agents read structured data, not screenshots.
  • Whether you should enable agentic commerce. That's a business decision. We tell you whether your store is ready — opting in or out is yours.

12 · Changelog of methodology changes

Every scoring change (new severity weights, new surface weights, new platform floors, new checks) is published with the engine version and ISO date. The retired check ledger on /spec-status tracks the why for every check that has ever been removed.

See it on your store.

Free 82-check audit. Async run with live status updates.

See the live spec versions →