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.previewEngine2026-06-08.v1

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) that never changes once assigned — it's the identifier stored on every result;
  • 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.

How a check lands on pass, partial, or fail comes down to one of two measurement shapes:

  • Binary — the signal is either present and valid or it isn't. Example: a non-empty robots.txt at the site root is a pass (`100`); a missing or blank one is a fail (`0`). No partial.
  • Coverage-banded — the check samples a set of product pages and measures the share that satisfy the rule, then bands the result. A typical check passes at ≥ 95% coverage (`100`), is partial in a middle band such as 50–95% (`50`), and fails below it (`0`). Exact thresholds are tuned per check and fixed by the engine version; if nothing applicable is found, the check is na and drops out of aggregation rather than scoring zero.

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 carries the heaviest weight because most of its 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 canonical slug, what the check measures, its severity, the one-line fix it recommends, and the specs it normatively cites.

Discovery

25 checks · prefix DISC
SlugNameDescriptionSeverity
googlebot-allowed-on-productsGooglebot allowed on product paths

robots.txt permits Googlebot at a representative product-detail path.

Fix: Allow Googlebot on product paths

Specs: Google merchant listing — crawlability

CRITICAL
wildcard-root-disallowNo global wildcard root disallow

robots.txt has no wildcard `User-agent: *` group with `Disallow: /` that locks every crawler out.

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

Specs: RFC 9309 section 2.2.2 — Disallow rules

CRITICAL
openai-search-bot-allowedOAI-SearchBot allowed

robots.txt permits OAI-SearchBot (OpenAI's ChatGPT discovery crawler) at the site root.

Fix: Allow OAI-SearchBot in robots.txt

Specs: OpenAI bots — OAI-SearchBot

CRITICAL
bingbot-allowedBingbot allowed

robots.txt permits Bingbot (the crawler behind Microsoft Copilot Shopping's index) at the site root.

Fix: Allow Bingbot in robots.txt

Specs: RFC 9309 — Bingbot User-agent groups

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

Each sampled product page exposes a single Product JSON-LD node.

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

Specs: Google merchant listing — single product per page

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

Every sampled product page is indexable — no `noindex` in HTML meta or X-Robots-Tag.

Fix: Remove the noindex directive from every PDP

Specs: Google merchant listing — indexable PDPs

HIGH
perplexity-bot-allowedPerplexityBot allowed

robots.txt permits PerplexityBot (Perplexity's shopping index crawler) at the site root.

Fix: Allow PerplexityBot in robots.txt

Specs: Perplexity bots — PerplexityBot

HIGH
products-discoverable-no-jsProduct pages discoverable without JavaScript

At least one product page is discoverable from a non-JavaScript crawl of the sitemap or initial HTML.

Fix: Make product pages discoverable without JavaScript

Specs: Google merchant listing — crawlable content

HIGH
products-machine-discoverableProducts are machine-discoverable

Agents can reliably discover the store's products via a declared feed, platform catalog, typed sitemap, or content-verified crawl.

Fix: Publish a product feed or a crawlable product sitemap

Specs: Google merchant listing — product feed delivery, sitemaps.org — XML sitemap discovery

HIGH
robots-txt-presentrobots.txt present at root

A non-empty /robots.txt is reachable at the site root, giving the merchant a control surface for every crawler.

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

Specs: RFC 9309 §2.2.3 — Access method (robots.txt)

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

No sampled PDP returns 401 or 403 to an anonymous request.

Fix: Open PDPs to anonymous fetches

Specs: Google merchant listing — anonymously fetchable PDPs

HIGH
sitemap-entries-escapedSitemap <loc> entries are entity-escaped

Every sampled <loc> value escapes `&`, `<`, and `>` as XML entities.

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

Specs: sitemaps.org — Entity escaping

MEDIUM
sitemap-declared-in-robotsSitemap declared in robots.txt

robots.txt includes at least one `Sitemap:` line pointing at the XML sitemap.

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

Specs: sitemaps.org — Sitemap location

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

Every <loc> URL shares the host of its containing sitemap document.

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

Specs: sitemaps.org — Sitemap file location

MEDIUM
sitemap-resolvable-with-productsSitemap resolvable and includes at least one product URL

An XML sitemap is reachable and includes at least one product-shaped URL.

Fix: Publish a sitemap containing product URLs

Specs: sitemaps.org — XML sitemap format

MEDIUM
sitemap-urlset-namespaceSitemap root declares the sitemaps.org 0.9 namespace

Every fetched sitemap document declares the sitemaps.org 0.9 xmlns.

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

Specs: sitemaps.org — XML tag definitions (namespace)

MEDIUM
robots-content-type-plain/robots.txt is served as text/plain

The /robots.txt response carries `Content-Type: text/plain`.

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

Specs: RFC 9309 §2.3 — File format (content type)

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

The /robots.txt body decodes cleanly as UTF-8 per RFC 9309 §2.3.

Fix: Serve /robots.txt as UTF-8

Specs: RFC 9309 §2.3 — File format (encoding)

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

The /robots.txt body is under 512,000 bytes (500 KiB).

Fix: Trim /robots.txt below 500 KiB

Specs: RFC 9309 §2.5 — Limits

LOW
chatgpt-user-allowedChatGPT-User allowed

robots.txt permits ChatGPT-User (OpenAI's user-initiated live fetcher) at the site root.

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

Specs: OpenAI bots — ChatGPT-User

LOW
sitemap-loc-under-2048Every sitemap <loc> URL is under 2048 characters

Every <loc> URL across all resolved sitemap documents is shorter than 2,048 characters.

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

Specs: sitemaps.org — URL length cap

LOW
perplexity-user-allowedPerplexity-User allowed

robots.txt permits Perplexity-User (Perplexity's live user-initiated fetcher) at the site root.

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

Specs: Perplexity bots — Perplexity-User

LOW
sitemap-utf8Sitemap is served as UTF-8

Every fetched sitemap document decodes cleanly as UTF-8.

Fix: Serve every sitemap document as UTF-8

Specs: sitemaps.org — XML tag definitions (encoding)

LOW
sitemap-size-limitsSitemap respects 50 MiB / 50,000-URL caps per document

No fetched sitemap document exceeds 50 MiB or 50,000 URLs.

Fix: Split over-cap sitemaps into a sitemap index

Specs: sitemaps.org — Sitemap size limits

LOW
llms-txt-presentllms.txt present (informational)

An /llms.txt manifest is reachable at the site root (informational; never fails).

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

Specs: llmstxt.org — /llms.txt manifest

INFO

Structured data

12 checks · prefix STRUCT
SlugNameDescriptionSeverity
offer-price-currency-validOffer price + priceCurrency valid

Each Offer carries a parseable numeric `price` (≥ 0) and a 3-letter ISO 4217 `priceCurrency`.

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

Specs: Google merchant listing — price + priceCurrency required, schema.org/Offer.price + priceCurrency

CRITICAL
offer-availability-schema-urlOffer `availability` is a Schema.org URL

Each Offer's `availability` is a canonical Schema.org IRI (InStock / OutOfStock / PreOrder / BackOrder).

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

Specs: schema.org/ItemAvailability, Google merchant listing — Offer availability

HIGH
product-image-populatedProduct `image` populated

Each Product JSON-LD node resolves at least one image URL.

Fix: Add a resolvable image URL to every Product node

Specs: schema.org/Product.image, Google merchant listing — image required

HIGH
product-name-populatedProduct `name` populated

Each Product JSON-LD node carries a non-empty `name` string.

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

Specs: schema.org/Product.name, Google merchant listing — title required

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

Each Product JSON-LD node has a resolvable Offer (or AggregateOffer) block.

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

Specs: schema.org/Offer, Google merchant listing — offers required

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

Each sampled PDP exposes a Product JSON-LD node in its initial HTML.

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

Specs: schema.org/Product, Google merchant listing — Product structured data

HIGH
product-brand-string-or-objectProduct `brand` is a string or Brand/Organization object

Each Product JSON-LD node carries `brand` as either a non-empty string or an object with a non-empty `name`.

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

Specs: schema.org/brand — Text | Brand | Organization, Google merchant listing — brand recommended

MEDIUM
product-description-presentProduct `description` present

Each Product JSON-LD node carries a non-empty `description` after HTML stripping.

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

Specs: schema.org/Product.description, Google merchant listing — description recommended

MEDIUM
product-sku-populatedProduct `sku` populated

Each Product JSON-LD node carries a non-empty `sku` string or numeric value.

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

Specs: schema.org/Product.sku, Google merchant listing — sku optional

MEDIUM
breadcrumb-list-presentBreadcrumbList present on PDPs

Each sampled PDP exposes a BreadcrumbList JSON-LD block with a populated itemListElement.

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

Specs: schema.org/BreadcrumbList

LOW
offer-item-condition-when-not-newOffer `itemCondition` is canonical when present

Each Offer either omits `itemCondition` (defaults to NewCondition) or sets it to a canonical Schema.org IRI.

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

Specs: schema.org/OfferItemCondition, Google merchant listing — condition required only for non-new

LOW
product-aggregate-rating-presentProduct `aggregateRating` present

Each Product JSON-LD node carries an AggregateRating with ratingValue in [0,5] and reviewCount/ratingCount ≥ 1.

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

Specs: schema.org/AggregateRating

LOW

Product data

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

Each PDP carries brand attribution — either via Product JSON-LD `brand` or via visible HTML (Open Graph or Microdata).

Fix: Surface brand attribution on every PDP

Specs: schema.org/Product.brand, Google merchant listing — brand recommended

HIGH
product-gtin-populatedGTIN coverage on PDPs

Each PDP carries a valid GTIN (8, 12, 13, or 14 digits) on its Product JSON-LD.

Fix: Populate `gtin` on every branded Product node

Specs: schema.org/Product.gtin, Google merchant listing — GTIN required for branded goods

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

Each Product title is not a known placeholder (Default Title, Untitled, Product 1, …) and not a slug-shape token (e.g. `red-hat-001`).

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

Specs: schema.org/Product.name (quality)

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

Each Product title is a non-empty string and contains at least one lowercase letter (rejects all-caps promotional titles).

Fix: Use sentence-case product titles

Specs: Google merchant listing — title quality, schema.org/Product.name

LOW

Policy

15 checks · prefix POL
SlugNameDescriptionSeverity
merchant-return-policy-finite-daysMerchantReturnPolicy finite-window has positive merchantReturnDays

Finite-window return policies carry `merchantReturnDays` as a positive number.

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

Specs: Google return-policy SD — merchantReturnDays required for finite windows

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

Each PDP's Product JSON-LD (or its Offer) carries a `hasMerchantReturnPolicy` node.

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

Specs: Google return-policy structured data — entry-point field, schema.org/MerchantReturnPolicy

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

Every MerchantReturnPolicy node satisfies Option A (country + category) or Option B (returnLink URL).

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

Specs: Google return-policy SD — required-shape MUSTs, schema.org/MerchantReturnPolicy

HIGH
offer-shipping-details-presentOffer JSON-LD carries shippingDetails (OfferShippingDetails)

Each PDP's Offer JSON-LD carries `shippingDetails: OfferShippingDetails`.

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

Specs: schema.org/OfferShippingDetails, Google shipping-policy SD — entry-point field

HIGH
privacy-policy-page-reachablePrivacy policy page reachable

A privacy policy page is reachable from the homepage nav or at a platform-conventional URL with non-stub body content.

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

Specs: Google merchant listing — privacy policy

HIGH
terms-of-service-page-reachableTerms of service page reachable

A terms-of-service page is reachable from the homepage nav or at a platform-conventional URL with non-stub body content.

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

Specs: Google merchant listing — terms of service

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

When present, `applicableCountry` is a 2-letter ISO 3166-1 alpha-2 code (or an array of codes).

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

Specs: Google return-policy SD — applicableCountry MUST be ISO alpha-2

MEDIUM
merchant-return-link-reachableMerchantReturnPolicy merchantReturnLink URL is reachable

Every `merchantReturnLink` URL on a MerchantReturnPolicy node returns a 2xx response.

Fix: Repair every merchantReturnLink URL

Specs: Google return-policy SD — merchantReturnLink reachability

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

When present, `returnPolicyCategory` is one of MerchantReturnFiniteReturnWindow / MerchantReturnUnlimitedWindow / MerchantReturnNotPermitted (bare or schema.org URL form).

Fix: Use a valid Schema.org returnPolicyCategory enum value

Specs: Google return-policy SD — returnPolicyCategory enum

MEDIUM
offer-shipping-destination-validOfferShippingDetails shippingDestination is a valid DefinedRegion

When present, `shippingDestination` is a DefinedRegion (or array) with ISO alpha-2 `addressCountry`.

Fix: Emit shippingDestination as a DefinedRegion with ISO addressCountry

Specs: Google shipping-policy SD — shippingDestination as DefinedRegion

MEDIUM
offer-shipping-rate-validOfferShippingDetails shippingRate is a valid MonetaryAmount

When present, `shippingRate` is a MonetaryAmount with numeric value/maxValue and ISO 4217 currency.

Fix: Emit shippingRate as a valid MonetaryAmount

Specs: Google shipping-policy SD — shippingRate as MonetaryAmount

MEDIUM
returns-policy-page-reachableReturns/refund policy page reachable

A returns/refund policy page is reachable from the homepage nav or at a platform-conventional URL with non-stub body content.

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

Specs: Google merchant listing — return policy

MEDIUM
shipping-policy-page-reachableShipping policy page reachable

A shipping policy page is reachable from the homepage nav or at a platform-conventional URL with non-stub body content.

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

Specs: Google merchant listing — shipping policy

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

When present, `returnFees`, `returnMethod`, and `refundType` are members of their Schema.org enums.

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

Specs: Google return-policy SD — enum field constraints

LOW
offer-shipping-delivery-time-validOfferShippingDetails deliveryTime is a valid ShippingDeliveryTime

When present, `deliveryTime` is a ShippingDeliveryTime with handlingTime and/or transitTime populated.

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

Specs: Google shipping-policy SD — deliveryTime as ShippingDeliveryTime

LOW

Trust

9 checks · prefix TRUST
SlugNameDescriptionSeverity
https-and-hsts-enforcedHTTPS enforced sitewide + HSTS (≥ 6-month max-age)

Homepage is served over HTTPS, http:// redirects to https://, and the HSTS max-age is ≥ 6 months.

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

Specs: RFC 6797 — HTTP Strict Transport Security (HSTS)

CRITICAL
contact-with-email-or-phoneContact page exposes email or phone

Contact page is reachable AND exposes at least one of: mailto link, tel link, plain email, or plausible phone number.

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

Specs: Google merchant listing — customer-support contact info

HIGH
hsts-include-subdomainsHSTS policy carries the includeSubDomains directive

The Strict-Transport-Security header on the homepage includes the `includeSubDomains` directive.

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

Specs: RFC 6797 — HSTS `includeSubDomains` directive

MEDIUM
organization-jsonld-with-contactOrganization/OnlineStore JSON-LD with contactPoint on homepage

Homepage JSON-LD includes an Organization or OnlineStore node with a contactPoint exposing email or telephone.

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

Specs: schema.org/Organization (and OnlineStore subclass) with contactPoint

MEDIUM
review-app-detectedThird-party review-platform integration detected

Homepage or sampled PDPs reference a known third-party review platform.

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

Specs: schema.org/Review vocabulary

MEDIUM
about-page-reachableAbout page reachable with substantive copy

An About page is reachable at a standard URL with at least 200 characters of text content.

Fix: Publish a substantive About page at a standard URL

Specs: Google merchant listing — customer-support / about page

LOW
hsts-preload-directiveHSTS policy carries the preload directive

The Strict-Transport-Security header on the homepage includes the `preload` directive.

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

Specs: RFC 6797 — HSTS `preload` directive (vendor extension)

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

Homepage or sampled PDPs reference the Apple Pay JS SDK, web-component, or domain-association file.

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

Specs: Apple Pay availability — payment options observed

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

Homepage or sampled PDPs reference the Google Pay JS SDK, Payments API namespace, or web-component.

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

Specs: Google Pay availability — payment options observed

INFO

Protocol (UCP)

15 checks · prefix PROT
SlugNameDescriptionSeverity
ucp-profile-no-auth-required/.well-known/ucp is publicly fetchable with no auth

The `/.well-known/ucp` URL returns a 200 to unauthenticated requests.

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

Specs: Google UCP merchant profile — public fetchability, UCP overview — discoverable profiles

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

`/.well-known/ucp` is reachable and carries a top-level `version` string.

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

Specs: UCP overview — profile location and shape, Google UCP merchant profile guide

HIGH
ucp-profile-content-type-json/.well-known/ucp response Content-Type is application/json

The `/.well-known/ucp` response declares `Content-Type: application/json`.

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

Specs: UCP overview — JSON profile media type

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

Fetching `/.well-known/ucp` returns 200 with no redirect chain.

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

Specs: UCP overview — profile location is stable

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

Each `services[].transport` is one of `rest`, `mcp`, `a2a`, `embedded`.

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

Specs: UCP overview — transport enum

HIGH
ucp-service-transport-conditional-fieldsEach service satisfies the transport-conditional field requirements

rest/mcp services declare `endpoint` + `schema`; a2a declares `endpoint`; embedded declares `schema`.

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

Specs: UCP overview — transport-conditional fields

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

Every `signing_keys[]` entry parses as a valid JWK with `kty` and the kty-specific required fields.

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

Specs: RFC 7517 — JSON Web Key (JWK)

HIGH
ucp-cache-headers-validUCP profile Cache-Control is shared-cacheable with max-age ≥ 60s

`/.well-known/ucp` Cache-Control is `public, max-age=N` with N ≥ 60 and no `no-store`/`no-cache`/`private`.

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

Specs: UCP overview — profile caching

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

Profile carries top-level `version`, `services`, `capabilities`, and `signing_keys`.

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

Specs: UCP overview — required profile keys

HIGH
ucp-shopping-service-validUCP profile declares a valid shopping service entry

UCP profile declares at least one shopping service with a recognised transport and an HTTPS endpoint.

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

Specs: UCP overview — services declaration, Google UCP profile — services structure

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

Every `capabilities[]` entry declares `version`, `spec`, and `schema`.

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

Specs: UCP overview — capabilities

MEDIUM
ucp-service-spec-url-origin-matchesEach service's `spec` URL origin matches its namespace authority

Every `services[].spec` URL points at the canonical UCP authority or includes the service's namespace.

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

Specs: UCP overview — service specs

MEDIUM
ucp-service-version-date-formatEvery service `version` matches YYYY-MM-DD

Each `services[].version` matches the ISO-date pattern `YYYY-MM-DD`.

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

Specs: UCP overview — versioned services

MEDIUM
webmcp-declarative-tools-validDeclarative WebMCP forms are valid

Any declarative WebMCP <form> carries a valid toolname, tooldescription, and well-formed parameters.

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

Specs: WebMCP — declarative form API

LOW
ucp-mcp-transport-validUCP MCP-transport entries have valid HTTPS endpoints

Every UCP service entry declaring `transport: "mcp"` carries an absolute HTTPS endpoint.

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

Specs: UCP overview — transport declarations

LOW

Images

2 checks · prefix IMG
SlugNameDescriptionSeverity
image-alt-text-coverageAlt text on at least 80% of PDP images

At least 80% of <img> tags on each sampled PDP carry non-empty alt text.

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

Specs: WCAG 2.x SC 1.1.1 — Non-text Content

LOW
image-area-50k-pixelsProduct images meet Google’s 50,000-pixel area threshold

At least one product image on each PDP has width × height ≥ 50,000 pixels.

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

Specs: Image quality (≥50K pixels area)

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

A few signals can't be scored identically on every platform because the out-of-the-box default differs — penalizing a store for something its platform already handles wouldn't be fair. Where that's the case, the check accounts for the platform when it scores, and the normalization is versioned with each engine release.

Every fix ships in two forms: platform-specific steps for Shopify, BigCommerce, and WooCommerce, plus a generic version for custom and other platforms. So even when we can't determine the platform, the merchant still gets actionable guidance.

9 · Reproducibility

Every audit persists:

  • Engine version — current semver of packages/engine is 2026-06-08.v1. 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.
  • Per-check evidence — every check records the artifacts it observed (robots.txt, sitemap, JSON-LD nodes, response headers, and the offending vs. expected snippets) onto the audit record, so the report renders from stored evidence rather than a re-crawl.

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 →