sephora.com
Work at sephora.com?Verify your @sephora.com email to rescan on demand.
Shopping agents are blocked or challenged before they can see a product.
Higher than 56% of 192 scanned stores
Pillars
- Access12.3/35
- Catalog legibility5/25
- Transactability0/25
- Trust & policy7/15
Capped at 29: 6 of 6 shopping-agent user agents were blocked or challenged on the product page
Capped at 59: No Product structured data was found on the product page
Capped at 79: No agent-native transaction surface was found (UCP, Storefront MCP, or attested ACP)
Checks
5 of 27 scored checks passed. Expand a row for the evidence behind it.
Access
12.3/35Catalog legibility
5/25Transactability
0/25Trust & policy
7/15How to fix it
22 fixes for sephora.com, ready to paste into a ticket or hand to a coding agent.
Serve the homepage to AI agents
access.home-agents · partial
When we requested your homepage as GPTBot, ChatGPT-User, OAI-SearchBot, PerplexityBot, Meta-ExternalAgent, Meta-ExternalFetcher, Amazonbot, Bytespider, and AURbot, the response was a block, a challenge page, or an empty shell instead of the page a browser gets.
aurbot: challenge (403)gptbot: challenge (403)amazonbot: challenge (403)bytespider: challenge (403)chatgpt-user: challenge (403)oai-searchbot: challenge (403)perplexitybot: challenge (403)meta-externalagent: challenge (403)meta-externalfetcher: challenge (403)
Blocks like this almost never come from robots.txt. They come from bot management at the edge (Cloudflare Bot Fight Mode or "Block AI bots", Akamai Bot Manager, DataDome, HUMAN/PerimeterX, Kasada, Imperva) or from a hosting firewall. The response headers usually name the layer that challenged the request; add an allow rule there for the shopping agents.
Cloudflare: in Security → Bots (or AI Crawl Control), set the AI bots control to allow the shopping agents, and make sure Super Bot Fight Mode is not challenging verified bots. For anything still caught, add a WAF custom rule with the action Skip (skipping bot management and managed challenges) for:
(http.user_agent contains "ChatGPT-User") or (http.user_agent contains "OAI-SearchBot") or (http.user_agent contains "Claude-User") or (http.user_agent contains "PerplexityBot") or (http.user_agent contains "meta-externalfetcher") or (http.user_agent contains "AURbot")Other WAFs: look for a "known bots" or "verified bots" category and allow it; otherwise allow-list the user agents above. Rate-limit rules scoped to product or search paths are a frequent hidden cause.
If an outcome below reads
js-shell, that agent was not blocked: it received an empty JavaScript app shell with no product content. Server-render the page so the content is in the HTML.Unblock shopping agents on product pages
access.pdp-agents · fail
ChatGPT-User, OAI-SearchBot, Claude-User, PerplexityBot, Meta-ExternalFetcher, and AURbot could not read your product page. These are the agents that act for a live shopper, so this is the single most expensive block on the site: with four or more blocked, your AUR is capped at 29 regardless of everything else.
aurbot: challenge (403)claude-user: challenge (200)chatgpt-user: challenge (403)oai-searchbot: challenge (403)perplexitybot: challenge (403)meta-externalfetcher: challenge (403)
If the homepage check passed but this one did not, look for a rule scoped to product paths: price-scraping protection, rate limits on
/products/*, or a "challenge on high-value pages" setting.Blocks like this almost never come from robots.txt. They come from bot management at the edge (Cloudflare Bot Fight Mode or "Block AI bots", Akamai Bot Manager, DataDome, HUMAN/PerimeterX, Kasada, Imperva) or from a hosting firewall. The response headers usually name the layer that challenged the request; add an allow rule there for the shopping agents.
Cloudflare: in Security → Bots (or AI Crawl Control), set the AI bots control to allow the shopping agents, and make sure Super Bot Fight Mode is not challenging verified bots. For anything still caught, add a WAF custom rule with the action Skip (skipping bot management and managed challenges) for:
(http.user_agent contains "ChatGPT-User") or (http.user_agent contains "OAI-SearchBot") or (http.user_agent contains "Claude-User") or (http.user_agent contains "PerplexityBot") or (http.user_agent contains "meta-externalfetcher") or (http.user_agent contains "AURbot")Other WAFs: look for a "known bots" or "verified bots" category and allow it; otherwise allow-list the user agents above. Rate-limit rules scoped to product or search paths are a frequent hidden cause.
If an outcome below reads
js-shell, that agent was not blocked: it received an empty JavaScript app shell with no product content. Server-render the page so the content is in the HTML.Make site search work for agents
access.search-agents · partial
Your search results page (https://www.sephora.com/?keyword=gift) did not return usable content for every probe. Agents use site search to find a product when the user names it; a search page that is blocked or empty forces them to guess URLs or give up.
chatgpt-user: challenge (403)
Typical causes and fixes:
- Results are rendered client-side. Third-party search (Algolia, Klevu, Searchspring, Nosto) often replaces the results grid with JavaScript. Keep the platform's server-rendered results as the HTML fallback, or render results on the server before enhancing them.
- A WAF rule treats query strings as scraping. Allow the shopping agents (see the homepage and product-page fixes) on the search path, and raise any rate limit on
?q=. - The search URL is hard to find. Expose a plain
<form action="/search">with aqinput in the header, and declare it inWebSiteJSON-LD so agents can jump straight to results:
{ "@context": "https://schema.org", "@type": "WebSite", "url": "https://www.example.com/", "potentialAction": { "@type": "SearchAction", "target": { "@type": "EntryPoint", "urlTemplate": "https://www.example.com/search?q={search_term_string}" }, "query-input": "required name=search_term_string" } }Stop challenging datacenter traffic
access.browser-baseline · partial
A plain Chrome request from our scanner (a datacenter IP) was blocked or challenged. AI shopping agents run from the same kind of cloud infrastructure, so a rule that challenges every non-residential visitor blocks all of them at once, whatever their user agent says.
- Homepage:
content (200) - Product page:
challenge (403)
Tune bot management to act on behaviour rather than network type:
- Switch "likely automated" or "non-residential" traffic from Challenge to Allow, and keep challenges for traffic that actually misbehaves (rate, form abuse, carding).
- Allow verified bots and the shopping agents explicitly (see the product-page fix for a Cloudflare rule).
- Turn off "I'm Under Attack" / JavaScript-challenge-everything modes once an incident is over.
- Adopt Web Bot Auth so signed agents can prove who they are instead of being judged by IP (see that fix).
Until this changes, your report carries a "scanner blocked" flag. The score still counts, because what blocked us also blocks the agents.
- Homepage:
Add a Content-Signal directive
access.content-signal · partial
Your robots.txt has no Content-Signal directive. Content Signals (contentsignals.org) is a robots.txt extension that states, separately, whether your content may be used for search, as real-time AI input (which is what shopping agents do), and for training. An explicit signal scores fully; silence scores half.
You can welcome shopping agents while still declining training. Add to the
*group (or to each group you care about):User-agent: * Content-Signal: search=yes, ai-input=yes, ai-train=no Allow: /Keep
ai-input=yes; setai-trainhowever you like. If Cloudflare manages your robots.txt, the equivalent setting lives in AI Crawl Control → Content Signals.Edit the
robots.txtserved from your web root (or the CDN/edge rule that generates it) and redeploy. Crawlers cache robots.txt for up to 24 hours, so re-scan after a day.Publish a Web Bot Auth key directory
access.web-bot-auth · fail
Nothing usable was found at
/.well-known/http-message-signatures-directory(HTTP 404). Web Bot Auth is the IETF draft that lets agents sign requests with HTTP Message Signatures; parties that sign publish their Ed25519 public keys in this directory, and verifiers fetch it to check theSignatureandSignature-Inputheaders. Visa's Trusted Agent Protocol, Mastercard's Agent Pay, and Cloudflare's signed-agent verification are all built on it.Two steps matter for a storefront:
- Verify signatures at your edge so signed agents are let through bot management instead of challenged. On Cloudflare this is the Verified Bots / signed agents setting; other WAFs are adding the same.
- Publish your own directory for any requests you sign (your own agent, service-to-service calls, or a verifier that expects one to exist). Serve it with
Content-Type: application/http-message-signatures-directory+json:
{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "kid": "2026-01", "x": "<base64url-encoded public key>", "nbf": 1767225600, "exp": 1798761600 } ] }Serve the file from the same origin as the storefront, with a long cache lifetime, and rotate keys by adding the new one before removing the old.
Add Product structured data
catalog.product-jsonld · fail
Your product page has no Product JSON-LD or microdata. This is the most important single fix on the list: without it agents cannot reliably read the name, price, or availability, and your AUR is capped at 59.
A complete block looks like this (one per product page, in the server-rendered HTML):
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Product", "name": "Trail Runner 2", "image": ["https://www.example.com/images/trail-runner-2.jpg"], "description": "Lightweight waterproof trail shoe.", "sku": "TR2-BLK-10", "gtin13": "0012345678905", "brand": { "@type": "Brand", "name": "Example" }, "offers": { "@type": "Offer", "url": "https://www.example.com/products/trail-runner-2", "price": "129.00", "priceCurrency": "USD", "availability": "https://schema.org/InStock", "itemCondition": "https://schema.org/NewCondition", "priceValidUntil": "2026-12-31" } } </script>For products with variants, use the selected or default variant's price and availability, or a
ProductGroupwith oneProductper variant. Validate with the Schema.org validator or Google's Rich Results Test.Render the JSON-LD block server-side in the product page template (not injected by client-side JavaScript), one
Productper page, with the price of the default or selected variant.Add identifiers and brand to product markup
catalog.identifiers · fail
Your product markup has neither an identifier nor a brand. Agents use a GTIN, MPN, or SKU to recognise the same item across retailers (for price comparison and reviews) and the brand to answer "is this the real thing".
Add to the
Productnode:"sku": "TR2-BLK-10", "gtin13": "0012345678905", "mpn": "TR2-BLK", "brand": { "@type": "Brand", "name": "Example" }Use
gtin13/gtin12/gtin14for barcodes (EAN/UPC),mpnfor the manufacturer part number, andskufor your own code. One identifier is enough for the check; a GTIN is the most useful.Add shipping, returns, and price validity to the Offer
catalog.offer-details · fail
Your
Offeris missingshippingDetails,hasMerchantReturnPolicy, andpriceValidUntil. Agents rank options by delivery time, delivery cost, return risk, and how confident they are in the price; these three fields are where they look.Add to the
offersobject:"priceValidUntil": "2026-12-31", "shippingDetails": { "@type": "OfferShippingDetails", "shippingRate": { "@type": "MonetaryAmount", "value": "0", "currency": "USD" }, "shippingDestination": { "@type": "DefinedRegion", "addressCountry": "US" }, "deliveryTime": { "@type": "ShippingDeliveryTime", "handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" }, "transitTime": { "@type": "QuantitativeValue", "minValue": 2, "maxValue": 5, "unitCode": "DAY" } } }, "hasMerchantReturnPolicy": { "@type": "MerchantReturnPolicy", "applicableCountry": "US", "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow", "merchantReturnDays": 30, "returnMethod": "https://schema.org/ReturnByMail", "returnFees": "https://schema.org/FreeReturn" }Use your real values; one representative shipping rate and destination is enough. Set
priceValidUntilto a date you can honour (the end of the current season or year, regenerated as it passes).Many stores define the return policy once at the
Organizationlevel and reference it from eachOffer; either is fine as long as theOfferon the product page carries or links to it.Server-render the product name and price
catalog.ssr-price · fail
Agents read the raw HTML. If the product name and price only appear after client-side JavaScript runs, most agents see a blank page or a price-less one.
- Browser request: challenge (403)
- ChatGPT-User request: challenge (403)
Render the title, price, availability, and the main options into the HTML on the server (SSR or static generation), and let JavaScript enhance rather than create them. Avoid loading the price by a secondary XHR, showing it only as an image, or hiding it behind "add to cart to see price".
Frameworks: Next.js (server components /
getServerSideProps), Nuxt (ssr: true), Remix, SvelteKit, and Astro all render on the server by default; make sure the product route has not opted out.Add BreadcrumbList and ItemList markup
catalog.list-markup · partial
You have
ItemListon a listing page but noBreadcrumbListon the product page. Breadcrumbs tell an agent where a product sits in the catalog; anItemListlets it read a whole category or result set in one pass.On the product page:
{ "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://www.example.com/" }, { "@type": "ListItem", "position": 2, "name": "Footwear", "item": "https://www.example.com/collections/footwear" }, { "@type": "ListItem", "position": 3, "name": "Trail Runner 2" } ] }Offer a Markdown version of product pages
catalog.markdown-negotiation · fail
Requesting https://www.sephora.com/product/one-size-by-patrick-starrr-turn-up-base-buttersilk-concealer-P473741 with
Accept: text/markdownreturned HTML. Agents that support content negotiation get a page that is a tenth of the size and needs no parsing. This is worth one point and is a nice-to-have.When the
Acceptheader preferstext/markdown, respond with a Markdown body andVary: Accept:GET /products/trail-runner-2 HTTP/1.1 Accept: text/markdown HTTP/1.1 200 OK Content-Type: text/markdown; charset=utf-8 Vary: Accept # Trail Runner 2 **$129.00** · In stock · SKU TR2-BLK-10 Lightweight waterproof trail shoe. Sizes 6–13. Free shipping over $75, 30-day returns.The simplest place to do this is edge middleware (a Cloudflare Worker, Vercel or Netlify middleware, or a Next.js route) that converts the rendered HTML or renders a dedicated template. If you are on Cloudflare, the "Markdown for Agents" option in AI Crawl Control does the conversion for you.
Publish a UCP business profile
tx.ucp-profile · fail
No profile was found at
/.well-known/ucp. The Universal Commerce Protocol (ucp.dev) is how an agent discovers, in one request, that your store can be shopped programmatically: which services you expose, over which transports (REST and MCP), and whether checkout is supported. It is the largest single item in the transactability pillar, and without it (or an attested ACP integration) your AUR is capped at 79.A complete profile declares a shopping service with an endpoint and a checkout capability:
{ "ucp": { "version": "2026-01-11", "services": { "dev.ucp.shopping": { "version": "2026-01-11", "spec": "https://ucp.dev/specification/overview", "rest": { "schema": "https://ucp.dev/services/shopping/openapi.json", "endpoint": "https://www.example.com/api/ucp" }, "mcp": { "schema": "https://ucp.dev/services/shopping/mcp.json", "endpoint": "https://www.example.com/api/ucp/mcp" } } }, "capabilities": [ { "name": "dev.ucp.shopping.checkout", "version": "2026-01-11", "spec": "https://ucp.dev/specification/checkout", "schema": "https://ucp.dev/schemas/shopping/checkout.json" }, { "name": "dev.ucp.shopping.order", "version": "2026-01-11", "spec": "https://ucp.dev/specification/order", "schema": "https://ucp.dev/schemas/shopping/order.json" } ], "payment_handlers": [] } }Serve it as
application/jsonfrom the storefront origin. The endpoints must exist and speak the protocol; the MCP one is tested by the next check. Check ucp.dev for the currentversionstring and schema URLs.Serve the profile from the storefront origin and implement the shopping service behind it; most of the work is the checkout capability, which maps onto your existing cart and checkout APIs.
Answer tools/list on your UCP MCP endpoint
tx.ucp-mcp-endpoint · fail
https://www.sephora.com/api/ucp/mcpdid not return a tool list (HTTP 403). The endpoint must speak MCP over Streamable HTTP: accept a JSON-RPC POST and answer with JSON or an SSE stream.Test it the way we do:
curl -sS -X POST https://www.sephora.com/api/ucp/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'tools/listmust work without authentication; require auth on the tool calls that need it, and advertise it through OAuth discovery (see that fix).- Return
200withContent-Type: application/json(ortext/event-stream), never a 405 for POST or an HTML page. - Make sure the URL in the UCP profile (
services → dev.ucp.shopping → mcp.endpoint) points at this endpoint exactly.
Any MCP server SDK (TypeScript, Python, Go) with the Streamable HTTP transport will do; mount it on the storefront origin so the profile, the endpoint, and the cookies share a domain.
Serve a Storefront MCP server at /api/mcp
tx.storefront-mcp · fail
No MCP server answered
tools/listat/api/mcp(HTTP 403). This is the conventional storefront endpoint (Shopify's Storefront MCP lives here) where an agent can search the catalog, read policies, and manage a cart without scraping.Expose an MCP server over Streamable HTTP at that path with tools such as
search_shop_catalog,get_product_details,search_shop_policies_and_faqs,get_cart, andupdate_cart.tools/listmust be callable without authentication.If you already run the UCP MCP endpoint, mounting the same server at
/api/mcpis enough.Publish an MCP server card
tx.mcp-server-card · fail
Nothing was found at
/.well-known/mcp/server-card.json. The server card is the MCP discovery document that lets an agent find your server, its transport endpoint, and how to authenticate, without reading documentation.Publish a JSON document describing the server (field names follow the current MCP server-card draft; adjust to the version you target):
{ "serverInfo": { "name": "example-storefront", "title": "Example Storefront", "version": "1.0.0" }, "description": "Search the Example catalog, read policies, and manage a cart.", "transport": { "type": "streamable-http", "endpoint": "https://www.example.com/api/mcp" }, "capabilities": { "tools": {} }, "authentication": { "required": false } }Serve it as
application/jsonwith CORS enabled, and keep the endpoint in sync with the one your UCP profile names.Publish an API catalog (RFC 9727)
tx.api-catalog · fail
No API catalog was found. RFC 9727 defines
/.well-known/api-catalog, a linkset that points agents at your APIs and their descriptions (OpenAPI, MCP, UCP) in one place.Serve this as
application/linkset+json:{ "linkset": [ { "anchor": "https://www.example.com/", "service-desc": [ { "href": "https://www.example.com/api/openapi.json", "type": "application/vnd.oai.openapi+json" } ], "service-doc": [ { "href": "https://www.example.com/developers", "type": "text/html" } ], "service-meta": [ { "href": "https://www.example.com/.well-known/ucp", "type": "application/json" }, { "href": "https://www.example.com/.well-known/mcp/server-card.json", "type": "application/json" } ] } ] }Also advertise it from the homepage with a
Linkheader, which we accept on its own:Link: </.well-known/api-catalog>; rel="api-catalog"Publish OAuth discovery metadata
tx.oauth-discovery · fail
None of
/.well-known/oauth-authorization-server,/.well-known/openid-configuration, and/.well-known/oauth-protected-resourcereturned a JSON document. Agents that need to act on a customer's account (order status, saved addresses, loyalty) look for OAuth 2.0 discovery before they will attempt authenticated calls; it is also what MCP clients use to authorise against your MCP server.If you run an authorization server on this origin, publish RFC 8414 metadata at
/.well-known/oauth-authorization-server:{ "issuer": "https://www.example.com", "authorization_endpoint": "https://www.example.com/oauth/authorize", "token_endpoint": "https://www.example.com/oauth/token", "registration_endpoint": "https://www.example.com/oauth/register", "scopes_supported": ["cart:write", "orders:read"], "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"] }If authentication is handled by a separate identity provider, publish RFC 9728 protected-resource metadata at
/.well-known/oauth-protected-resourcepointing at it:{ "resource": "https://www.example.com/api/mcp", "authorization_servers": ["https://auth.example.com"], "scopes_supported": ["cart:write", "orders:read"], "bearer_methods_supported": ["header"] }Support PKCE and dynamic client registration (RFC 7591) so agents can onboard without a manual app review.
Publish an agent card or skills index
tx.agent-cards · fail
None of
/.well-known/agent-card.json,/.well-known/agent.json, and/.well-known/agent-skills/index.jsonwas found. An A2A agent card tells other agents that your store has an agent of its own and what it can do; an Agent Skills index lists reusable skill files. Either satisfies this one-point check.A minimal A2A agent card at
/.well-known/agent-card.json:{ "name": "Example Shopping Assistant", "description": "Find, compare, and buy Example products.", "url": "https://www.example.com/a2a", "version": "1.0.0", "protocolVersion": "0.3.0", "capabilities": { "streaming": true }, "defaultInputModes": ["text/plain"], "defaultOutputModes": ["text/plain", "application/json"], "skills": [ { "id": "find-products", "name": "Find products", "description": "Search the catalog by query, size, colour, and price.", "tags": ["shopping", "search"] } ] }Or, if you publish skills rather than an agent, serve
/.well-known/agent-skills/index.jsonlisting each skill's name, description, and the URL of itsSKILL.md. Only publish a card for an endpoint that actually answers.Publish and link return and shipping policy pages
trust.policy-pages · fail
We could not find a return/refund and shipping policy page. We look for links on the homepage whose text or path mentions returns/refunds and shipping/delivery, plus your platform's standard paths, and require the page to load (200, HTML). Agents read these to answer "can I send it back" and "when will it arrive" before they recommend a purchase.
- Give each policy its own URL (not a tab inside an FAQ accordion, not a PDF).
- Link both from the homepage footer with plain anchor text: "Returns" / "Refund policy" and "Shipping".
- Keep the pages public and server-rendered; a 200 with an empty JavaScript shell does not count.
Conventional paths such as
/returns,/return-policy,/shipping, and/shipping-policyare also probed directly.Add MerchantReturnPolicy markup
trust.return-policy-markup · fail
No
MerchantReturnPolicystructured data was found on the product offer, the homepage, or the organization. A written policy page is good; machine-readable terms let agents compare return windows and fees across stores without reading prose.Attach it to the product
Offer(preferred, since that is where agents are when deciding) or to theOrganization/OnlineStoreon the homepage:"hasMerchantReturnPolicy": { "@type": "MerchantReturnPolicy", "applicableCountry": "US", "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow", "merchantReturnDays": 30, "returnMethod": "https://schema.org/ReturnByMail", "returnFees": "https://schema.org/FreeReturn", "refundType": "https://schema.org/FullRefund", "merchantReturnLink": "https://www.example.com/policies/refund-policy" }Use
MerchantReturnNotPermittedfor final-sale items andMerchantReturnUnlimitedWindowif you accept returns indefinitely. Keep it consistent with the written policy page.Add aggregateRating to product markup
trust.reviews · fail
Your product markup has no
aggregateRating. Agents weigh review scores heavily when choosing between similar products, and they only trust ratings they can read from structured data.Add to the
Productnode, with real numbers from reviews shown on the page:"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.6", "reviewCount": "128", "bestRating": "5" }Only emit it for products that actually have reviews, and keep it server-rendered: markup injected by a review widget's JavaScript is invisible to agents.
History
| Date | AUR | Level | Engine |
|---|---|---|---|
| Oct 20, 2026 | 12 | Blocked | v1.0.1 |
| Oct 20, 2026 | 12 | Blocked | v1.0.1 |