{"contractVersion":"1.36.0","layers":[{"id":"discovery","name":"Discovery","weight":20},{"id":"accessibility","name":"Access","weight":30},{"id":"usability","name":"Usability","weight":40},{"id":"payments","name":"Payments","weight":10}],"checks":[{"id":"ard-catalog","name":"ARD discovery","description":"One catalog that tells agents everything you offer them: MCP servers, APIs, agents, and skills. Without it, every agent has to hunt for your capabilities page by page. We look where ARD v0.91 tells agents to look: an Agentic Resource Discovery catalog at /.well-known/ard.json, or one your homepage points to with a <link rel=\"ard\"> tag (an HTTP Link header with rel=\"ard\" also counts). The predecessor /.well-known/ai-catalog.json and rel=\"ai-catalog\" still count, with a nudge to move. Entries published only through a robots.txt Agentmap directive or as JSON-LD on your homepage are noted without the point, since agents resolving your domain are not required to read them. Weighted at 1 point while ecosystem adoption is early (2026-08 audit; was 3).","layer":"discovery","maxScore":1,"bonus":false,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"required","maturity":"verified","draft":false,"beta":false,"specUrl":"https://agenticresourcediscovery.org/","recommendation":"Publish an Agentic Resource Discovery catalog at /.well-known/ard.json (the ARD v0.91 canonical path), or point to one from your homepage with a <link rel=\"ard\"> tag (an HTTP Link header with rel=\"ard\" also counts); the legacy /.well-known/ai-catalog.json remains a valid alias. List your agentic resources (MCP servers, agents, skills, APIs), each entry with a urn:air identifier, a media type, and exactly one of url or data. See https://agenticresourcediscovery.org/"},{"id":"ai-catalog-published","name":"AI Catalog discovery","description":"The AI Catalog Standard (ai-catalog.io, Agent-Card WG) specifies its own discovery path, /.well-known/ai-catalog.json, separately from ARD's ard.json. Serving a valid catalog there keeps you discoverable by consumers that follow that spec. Bonus: earns credit when present, never costs points.","layer":"discovery","maxScore":1,"bonus":true,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://ai-catalog.io/","recommendation":"Serve a valid catalog at /.well-known/ai-catalog.json - the AI Catalog Standard's own discovery path - so consumers following that spec (see https://ai-catalog.io/) also find you. The entries can be the same as in /.well-known/ard.json, but this standard's document also needs \"specVersion\": \"1.0\" at the top level."},{"id":"ard-entries-valid","name":"ARD entry validity","description":"Each entry in your catalog is complete enough for an agent to actually use it: an identifier, a display name, a media type, and a working target. Validated against the AI Catalog Standard's entry model on the catalog the ARD discovery check found (ard.json, a rel=\"ard\" source, else the legacy ai-catalog.json). Only applies when the catalog exists; partial adoption earns credit and never costs points.","layer":"discovery","maxScore":2,"bonus":true,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://ai-catalog.io/","recommendation":"Make every catalog entry fully valid: a domain-anchored urn:air identifier, a displayName, a media type, and exactly one of url or data. Applies to the catalog at /.well-known/ard.json, behind a rel=\"ard\" link, or at the legacy /.well-known/ai-catalog.json."},{"id":"ard-trust-manifest","name":"ARD trust manifest","description":"Trust signals inside your catalog (verified identity, compliance attestations, signatures) that let cautious agents pick you over an unverified alternative. The trust model comes from the AI Catalog Standard and is validated on whichever path serves your catalog. Only applies when the catalog exists; never costs points.","layer":"discovery","maxScore":2,"bonus":true,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://ai-catalog.io/","recommendation":"Add a trustManifest (verifiable identity, compliance attestations, or signature) to your catalog entries (ard.json, a rel=\"ard\" source, or the legacy ai-catalog.json) so clients can verify your resources with progressive trust."},{"id":"agentic-search-usecase","name":"Category share of voice","description":"When someone asks an AI for a tool that does what you do, do you come up? This measures your share of voice for the problem you solve, not just your name.","layer":"discovery","maxScore":6,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":true,"recommendation":"Build topical authority for your category. Publish comparison pages, 'best X for Y' content, tutorials, and integration guides that rank for generic use-case queries - not just your brand name - so agents surface you when users describe a need."},{"id":"agentic-search-specific","name":"Developer resource discoverability","description":"Can an agent that knows your name find your developer resources by searching: API docs, OpenAPI spec, MCP server, auth docs, developer portal, SDK documentation? Any one recognized resource type earns full marks. Missing resources refer to this search sample, not proof that pages are unindexed. Weighted at 3 points while search-result categorisation is noisy (2026-08 audit; was 6, requiring 2 types).","layer":"discovery","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Check whether your developer resources (API docs, OpenAPI spec, auth docs, developer portal, MCP server, SDK documentation) surface in name-based searches. If they do not, use predictable URLs, link them in llms.txt, and include your product name in page titles and headings. This result reflects one search sample."},{"id":"brand-search-accuracy","name":"Brand name discoverability","description":"When people search for your brand together with your main product, your domain should be in the top results. If it is not, agents cannot reliably tell you apart from lookalikes and resellers.","layer":"discovery","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Make sure your own domain ranks in the top results when people search your brand together with your main product (for example \"Acme payments API\"). If it does not, your brand may be too generic, conflict with a more established term, or not yet indexed. Strengthen it by describing your main product clearly in your homepage title and headings, earning press mentions that link to your domain, and avoiding redirect chains that mask your main domain in search results."},{"id":"wikipedia-presence","name":"Wikipedia / Wikidata entity presence","description":"A Wikipedia article and Wikidata entry that link to your domain. Wikipedia is the largest single source of citations in AI answers (~48% of ChatGPT citations), so presence there decides whether AIs can verify who you are.","layer":"discovery","maxScore":4,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Establish a Wikipedia article and a Wikidata entity for your brand, with the domain set as the official website (Wikidata property P856) and a corresponding external link on Wikipedia. Wikipedia is the largest single source of citations in ChatGPT and a primary input to Knowledge Graphs across Google, Bing, and LLM training data. Earn third-party press coverage first to satisfy notability, then draft the article with cited references rather than self-promotion."},{"id":"sitemap","name":"Sitemap exists","description":"A map of every page you want found. Agents use your XML sitemap to discover content without crawling blind, so gaps here mean pages that never get read.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Add a valid XML sitemap at /sitemap.xml listing all indexable URLs. Include lastmod dates and keep it under 50MB."},{"id":"content-no-js","name":"Content without JavaScript","description":"Most AI crawlers never run JavaScript. If your pages need it to show content, agents see a blank site. We fetch your pages with JavaScript off and check the content is still there.","layer":"accessibility","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Serve at least 500 characters of meaningful homepage content in raw HTML. Add a clear H1, keep deeper heading levels sequential, and remove excessive non-content markup."},{"id":"bot-detection","name":"Not blocked by bot detection","description":"Your bot protection may be turning away the visitors you want. We check whether AI agents can reach your site without being blocked.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Let the agents that act for users and search (ChatGPT-User, OAI-SearchBot, Claude-User, PerplexityBot) through your WAF and bot rules. On Cloudflare, allow verified bots rather than matching User-Agent strings, which anyone can fake."},{"id":"robots-ai-policy-quality","name":"robots.txt AI crawler policy","description":"Whether your robots.txt lets the AI search crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) read your public pages, so AI search can find you. Blocking AI training crawlers such as GPTBot and ClaudeBot is your choice and never lowers this score.","layer":"discovery","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"In robots.txt, allow the AI search crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) to read your public pages, so AI search can find and cite them. The result shows which pages and which robots.txt lines block them. Keep private and account pages blocked. Blocking AI training crawlers such as GPTBot and ClaudeBot is your choice and never lowers this score. Visits that people ask an AI to make are covered by a separate check."},{"id":"agent-discovery-file","name":"Agent discovery file","description":"A dedicated endpoint that tells arriving agents what you are and where to start, like a front desk for automated visitors.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish an Agent Skills index at /.well-known/agent-skills/index.json that lists your capabilities, with each skill carrying a name and a description so agents can find and parse what you offer."},{"id":"agent-skills-index-v2","name":"Agent Skills index conformance (v0.2.0)","description":"Your published agent skills follow the current discovery spec, so tools can verify and install them safely. We check /.well-known/agent-skills/index.json for the v0.2.0 schema and typed entries, then verify one artifact digest (SKILL.md or archive).","layer":"accessibility","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Upgrade /.well-known/agent-skills/index.json to the v0.2.0 schema: add \"$schema\": \"https://schemas.agentskills.io/discovery/0.2.0/schema.json\", and give every entry a type (skill-md or archive), url, and digest. Use \"digest\": \"sha256:<64 lowercase hex chars>\" (e.g. \"digest\": \"sha256:a3f1...\") - a bare \"sha256\": \"<hex>\" field is also accepted. Compute the value from the artifact's raw bytes. For archives, hash the ZIP or tar.gz download itself; a separate skill-md entry is not required."},{"id":"a2a-agent-card","name":"A2A / agent-card","description":"A business card for agent-to-agent contact: who you are, what you can do, and how to reach you, published at /.well-known/agent-card.json (the A2A protocol).","layer":"accessibility","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish an Agent-to-Agent (A2A) agent card at /.well-known/agent-card.json describing your agent's capabilities, skills, and contact endpoint."},{"id":"mcp-registry-listed","name":"Listed in MCP registries","description":"Agents find MCP servers through registries the way people find apps through app stores. We check the major ones (Smithery, mcp.so) for an entry verified against your domain.","layer":"discovery","maxScore":1,"bonus":false,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Register your MCP server on Smithery (smithery.ai) or mcp.so so agent platforms can discover your tools. Link the registry entry from your homepage or docs for bi-directional verification."},{"id":"npm-sdk-package","name":"NPM/PyPI SDK package","description":"An official SDK or CLI on NPM or PyPI gives agents and the developers steering them a ready-made way in, instead of hand-rolling every API call.","layer":"discovery","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish a JavaScript/TypeScript SDK package on npm so developers can integrate your API programmatically. In package.json set `repository` to your source repo and `homepage` to your product domain - these links are how agents confirm the package is your official SDK rather than a third-party tool with a similar name."},{"id":"agent-rules-repo","name":"Agent platform configs","description":"Published rules or config files for AI coding tools (Claude, Cursor, Windsurf). They teach coding agents to use your product correctly from the first prompt.","layer":"discovery","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Add an AGENTS.md or .cursorrules file to your public GitHub repo with instructions for how AI coding agents should interact with your codebase. Then make sure the repo is documented in the entry-point pages agents read - homepage, docs, and llms.txt - so it can be discovered without guessing."},{"id":"agent-plugins-repo","name":"Agent Plugins manifest","description":"An Agent Plugins manifest (plugin.json) in your public GitHub repos, bundling skills and MCP servers into one installable package. An emerging format, so it never costs points.","layer":"discovery","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://agent-plugins.org/specification","recommendation":"Bundle your agent skills and MCP servers as an Agent Plugin: a plugin.json manifest with the agent-plugins.org $schema and a name in your public repo. See https://agent-plugins.org/specification"},{"id":"skills-sh-listed","name":"Listed on skills.sh","description":"Official skills published on skills.sh, the public directory where agents and developers browse for ready-made capabilities.","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Publish agent skills on skills.sh so AI agents can discover your product's capabilities. Create a SKILL.md in your GitHub repo and register it with 'npx skills add'. See skills.sh/docs."},{"id":"pricing-md","name":"pricing.md exists","description":"An agent comparing options needs your prices in a form it can read. We check for a machine-readable pricing file at /pricing.md.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Create a /pricing.md file with your pricing tiers, features, and limits in plain markdown. This lets AI agents compare costs and recommend plans without scraping HTML pricing pages."},{"id":"nlweb-schema-feeds","name":"NLWeb Schema Feeds","description":"A schemamap directive in robots.txt pointing at structured data feeds (NLWeb). It hands agents your catalog as data instead of making them scrape pages.","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Add a schemamap: directive to robots.txt pointing to a Schema Map XML file listing your structured data feeds (JSONL/RSS). See the NLWeb Schema Feeds spec."},{"id":"mcp-well-known-discovery","name":"MCP well-known discovery","description":"Publishing an MCP server is not enough; agents have to find it. We look where MCP clients look (a server card on your own domain listed in /.well-known/ai-catalog.json, the same catalog ARD crawlers read at /.well-known/ard.json, or a card served at <server URL>/server-card) and where sites also announce a server: /.well-known/mcp/server-card.json, a /.well-known/mcp.json manifest, llms.txt, or a /.well-known/mcp redirect to a working server, conventions no MCP spec defines.","layer":"accessibility","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"List each MCP server's card, hosted on your own domain, in /.well-known/ai-catalog.json, the document MCP clients fetch for discovery (ARD crawlers read the same catalog at /.well-known/ard.json), and serve the card at <server URL>/server-card, or name the server URL in llms.txt, so agents can find the server without anyone typing its URL in."},{"id":"agent-mode-view","name":"Agent mode view","description":"A stripped-down, structured view of your site at ?mode=agent, built for machine readers: plain content and direct endpoint links.","layer":"accessibility","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Add a ?mode=agent query parameter to your homepage that returns a structured, machine-readable view with API endpoints, authentication info, and key capabilities instead of marketing HTML."},{"id":"link-headers-discovery","name":"HTTP Link headers (RFC 8288)","description":"HTTP Link headers that point agents at your sitemap and service docs before they parse a single page, making every automated visit faster.","layer":"accessibility","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Add HTTP Link: response headers (RFC 8288) advertising your sitemap, markdown alternates, API service descriptions, and API catalog. Example: Link: </sitemap.xml>; rel=\"sitemap\", </index.md>; rel=\"alternate\"; type=\"text/markdown\"."},{"id":"markdown-url-fallback","name":"Markdown URL fallback","description":"Append .md to a page URL and get the markdown version: the cleanest possible read for an agent. Homepage /index.md is the baseline; .md twins on content pages earn full credit.","layer":"accessibility","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Let agents fetch markdown by appending .md to page URLs. Required for any credit: serve a markdown homepage at /index.md. For full credit (2/2): also serve a .md twin for each content page (e.g. /docs/auth -> /docs/auth.md). Content-Type should be text/markdown and the body should start with a top-level heading (not HTML)."},{"id":"modular-llms-txt","name":"Modular llms.txt per product area","description":"Per-section llms.txt files (like /docs/llms.txt) so an agent working in one area gets a focused index instead of your whole site.","layer":"accessibility","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Add an llms.txt file for each main section of your site (for example /docs/llms.txt or /blog/llms.txt) and link each one from your root llms.txt, so an agent working in one area can fetch a focused index instead of the whole site."},{"id":"registry-branding","name":"Registry branding","description":"Your MCP registry entry has a display name, icon, and description. Agents choosing between servers see this first; a bare entry looks abandoned.","layer":"discovery","maxScore":2,"bonus":false,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Give your MCP server-card (at /.well-known/mcp/server-card.json) a display name, an icon or logo, and a description - all three together are what reads as a complete, branded listing agents can present."},{"id":"chatgpt-app-listed","name":"ChatGPT app listed","description":"Listed in the ChatGPT app directory, where hundreds of millions of users can pull your product into a conversation by name. Upside-only: the directory is curated and gated, so absence never costs points.","layer":"discovery","maxScore":2,"bonus":true,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Submit your app to the ChatGPT apps / connectors directory (the apps-in-ChatGPT surface) so ChatGPT users can discover and use your product."},{"id":"sitemap-lastmod","name":"Sitemap freshness (lastmod)","description":"Dates in your sitemap tell agents what changed and when, so they reread fresh pages instead of guessing. We check lastmod dates parse and the newest is recent.","layer":"accessibility","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://www.sitemaps.org/protocol.html","recommendation":"Add <lastmod> dates (W3C datetime, e.g. 2026-08-01) to your sitemap entries and update them when content actually changes. Aim for lastmod on at least half your entries with the newest within the last year. Verify with `curl https://yourdomain.com/sitemap.xml | grep lastmod`."},{"id":"noindex-directives","name":"Homepage not hidden from search","description":"Agents often find a site by searching the web. A noindex tag or X-Robots-Tag header removes the page from search results, so we check the homepage for one.","layer":"discovery","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://developers.google.com/search/docs/crawling-indexing/block-indexing","recommendation":"Remove the noindex directive from your homepage: the robots, googlebot or bingbot meta tag in the page head, or an X-Robots-Tag response header. Common causes are a staging setting left on after launch (in WordPress, Settings > Reading > Discourage search engines), an SEO plugin setting, or a host adding the header, as preview builds often do. Verify with `curl -sI https://yourdomain.com | grep -i x-robots-tag` and by searching the page source for name=\"robots\"."},{"id":"robots-agent-user-policy","name":"robots.txt agent-user policy","description":"Declared homepage permission for ChatGPT-User, Claude-User, and Perplexity-User. General rules and permission by default count. Actual fetch behavior depends on the vendor; unverifiable policies are N/A.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Review the matched homepage rules for ChatGPT-User, Claude-User, and Perplexity-User. If you intend to permit these requests, remove or narrow the reported restriction while keeping private paths protected. Review applicable ai-input=no declarations if answer-time use is intended. This check reports declared permission; a robots.txt change alone does not establish live access."},{"id":"llms-txt-exists","name":"llms.txt exists","description":"llms.txt is your site's guide for AI readers: what you do and where the important pages are. We check for /llms.txt or /.well-known/llms.txt.","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Create an llms.txt file at your domain root (/llms.txt) - the AI equivalent of robots.txt. Write at least 100 characters of real content: what your product is, what it does, and links to your key docs. Then verify it with `curl https://yourdomain.com/llms.txt` - you should see your text, not HTML. If your app returns its homepage for every URL (common with single-page apps), add a static file route so the raw text is served. A placeholder with just a heading earns no credit."},{"id":"llms-txt-formatting","name":"llms.txt formatting","description":"An llms.txt only helps if agents can parse it. We check the format: a heading up top, markdown links, and enough substance to navigate by.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Format your llms.txt as a navigation index: start with a markdown heading, include markdown links to deeper resources, and keep it under 30,000 characters. If you have more to say, move long-form content into /llms-full.txt or per-section files (e.g. /docs/llms.txt, /api/llms.txt) and link to them from the main index."},{"id":"json-ld","name":"JSON-LD structured data","description":"Structured data that states in machine terms what you are: a product, a company, a person. Without it, every AI describing you is guessing from prose. We grade the completeness of your homepage JSON-LD.","layer":"accessibility","maxScore":4,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Add JSON-LD structured data to your homepage using the identity type that matches your site - SoftwareApplication for products, Organization or LocalBusiness for companies, Person for personal sites, Article for blogs - with name, description, url, and type-appropriate fields (offers, sameAs, author) so AI can parse your identity programmatically."},{"id":"pricing-info","name":"Pricing info accessible","description":"If an agent cannot find your prices, it cannot recommend you for a purchase. We check that pricing is discoverable and readable on your site.","layer":"accessibility","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Make pricing discoverable - add a /pricing page or include pricing as schema.org/Offer structured data, so agents can find it without scraping a marketing page."},{"id":"public-api-docs","name":"Public API/docs linked from homepage","description":"Your API docs, linked straight from your homepage. If an agent has to search for your documentation, most will not. Only docs you own count.","layer":"accessibility","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish API documentation at a discoverable URL (/docs, /api, /developers). Include authentication, endpoints, and example requests."},{"id":"agent-instruction","name":"Agent instruction / when-to-use","description":"Explicit guidance on when to use your product, written where agents will read it. An agent choosing between ten tools picks the one that says what it is for.","layer":"accessibility","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Tell agents when to reach for you: add a 'when to use this' section to your llms.txt (or a dedicated agent-instructions file) that names your best-fit use cases and how an agent should call you. Be specific about the jobs you are right for - generic marketing copy does not read as guidance."},{"id":"skills-sh-quality","name":"Skills.sh skill quality","description":"Not just listed on skills.sh, but worth installing: multiple skills, descriptive names, and real adoption.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Expand your skills.sh presence with multiple skill repos covering different use cases. Add descriptive skill names, clear SKILL.md files, and organize by capability area."},{"id":"json-ld-entity-linking","name":"JSON-LD entity linking (sameAs)","description":"sameAs links from your structured data to your official profiles (GitHub, LinkedIn, app stores). They let AIs confirm that every mention of you is actually you.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Add sameAs links in your JSON-LD structured data pointing to your Wikipedia page, Wikidata entry, GitHub org, and social profiles. This helps AI disambiguate your brand from similarly named entities."},{"id":"mcp-tool-descriptions","name":"MCP tool descriptions","description":"An agent picks tools by reading their descriptions: vague ones get skipped, clear ones get called. We grade every tool on your MCP server, with different bars for product and docs servers.","layer":"usability","maxScore":3,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Add detailed descriptions (>= 20 chars) to every MCP tool. Agents use these to decide which tool to call - vague descriptions lead to wrong tool selection."},{"id":"mcp-param-schemas","name":"MCP parameter schemas","description":"Typed parameter schemas on your MCP tools, so agents know what to send without trial and error. Product servers need required-field lists; docs servers need at least a query.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Define inputSchema with typed properties and required arrays for each tool. Agents need schema info to construct valid tool calls without guessing."},{"id":"mcp-server-identity","name":"MCP server identity","description":"Your MCP server introduces itself in the handshake: name, version, and usage instructions. An anonymous server makes every agent guess what it connected to.","layer":"usability","maxScore":1,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Set server name, version, and instructions in your MCP server's initialize response. Instructions help agents understand your server's purpose and constraints."},{"id":"metadata-completeness","name":"Metadata completeness","description":"The basics AIs use to identify a page: canonical URL, language, and Open Graph image and type. Missing pieces mean mangled citations and wrong previews.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Add all four signals to your homepage: <link rel=\"canonical\">, <html lang=\"...\">, <meta property=\"og:image\">, and <meta property=\"og:type\">. Agents use these for entity resolution and attribution."},{"id":"org-schema-completeness","name":"Organization schema completeness","description":"Company details (contact, address) in your structured data. AIs use them to verify you are a real business before recommending you.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Add Organization JSON-LD that includes both contactPoint (with email/phone and contactType) and address (PostalAddress). This lets AI verify your business legitimacy and answer contact queries."},{"id":"schema-type-breadth","name":"Schema type breadth","description":"The more kinds of content you mark up with schema.org (FAQs, reviews, services, events, videos), the more kinds of questions an AI can answer about you with confidence.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Expand your JSON-LD beyond Organization/WebSite with more schema.org types that match what your homepage shows. Only add a type where the page shows that content."},{"id":"trust-anchors","name":"Trust anchor pages","description":"About, contact, and privacy pages with real content. Agents check these to verify you are legitimate before recommending you, the way a careful person would.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish real /about, /contact, and /privacy pages with at least 500 characters of content each. These are the pages AI agents check to verify your business is legitimate before recommending you."},{"id":"mcp-tool-listing","name":"MCP tool listing","description":"Your MCP server actually returns tools when asked. We call tools/list and grade the surface: product servers earn on breadth, docs servers on focus.","layer":"usability","maxScore":3,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Expose your tools via your MCP server's tools/list endpoint. A product MCP server scores on breadth: 3 or more tools that cover read, write, and search. A docs MCP server scores on focus: 3 tools or fewer, such as one search tool and one fetch tool. Keep every outputSchema valid and self-contained: a $ref that points outside the schema, such as #/components/schemas/... copied from an OpenAPI file, makes clients built on the official TypeScript SDK 1.x reject your whole tool list."},{"id":"mcp-tool-naming","name":"MCP tool naming","description":"Predictable tool names (create_invoice, search_docs) that agents parse at a glance. Inconsistent naming makes every call a small research project.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Use consistent naming conventions (snake_case or camelCase) for all MCP tools. Names should be descriptive (>= 4 chars) and not generic (avoid 'run', 'get', 'do')."},{"id":"llms-txt-links-resolve","name":"llms.txt links resolve","description":"The markdown links an llms.txt declares actually resolve - broken links strand agents that follow the index","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://llmstxt.org","recommendation":"Make every link your llms.txt declares resolve to real content. Verify each one with `curl -L <url>` - you should see the linked document, not your homepage. If your app returns the homepage shell for unknown paths (common with single-page apps), a 200 status is not proof: check the body. Fix or remove any dead link; agents that follow the index treat a broken link as a dead end."},{"id":"markdown-link-alternate","name":"Markdown alternate link","description":"Pages advertise their markdown twin via a rel=alternate link or HTTP Link header, and the advertised target really serves markdown.","layer":"accessibility","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"specUrl":"https://www.rfc-editor.org/rfc/rfc8288","recommendation":"Advertise a markdown twin of each page with <link rel=\"alternate\" type=\"text/markdown\" href=\"...\"> in the HTML head (or an equivalent Link response header), and make sure the advertised URL actually serves markdown - an advertisement pointing at HTML is worse than none. Verify the target with `curl -s <href>` and check the body starts with a heading, not <!doctype html>."},{"id":"markdown-frontmatter","name":"Markdown frontmatter metadata","description":"Served markdown docs open with a frontmatter block (title plus description / canonical / last-updated) so agents get document metadata without scraping","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Open your served markdown docs with a --- frontmatter block carrying title plus at least one of description, canonical, or last-updated. Agents read frontmatter as document metadata without scraping. A Link: rel=\"canonical\" response header also satisfies the canonical slot."},{"id":"redirect-hygiene","name":"Redirect hygiene","description":"Pages reach real content without meta-refresh stubs, JavaScript-only redirects, or cross-domain hops that strand non-JS agents","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://www.rfc-editor.org/rfc/rfc9110#name-redirection-3xx","recommendation":"Replace meta-refresh and JavaScript-only redirects with real HTTP 301/302 redirects. Non-JS agents never execute `location.href` or wait for a meta refresh - they see only the stub page. Verify with `curl -sI <url>` - you should see a Location header, not a 200 with a near-empty body."},{"id":"page-token-budget","name":"Page token budget","description":"Individual pages keep extracted text within an agent-readable budget (~25K tokens) so they fit a context window without truncation","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Keep each page's extracted text under ~100K characters (~25K tokens) so it fits an agent's context window without truncation. Split oversized reference pages into focused per-topic documents and link them from an index. Check a page with `curl -s <url> | wc -c` and remember agents read the extracted text, not the raw HTML."},{"id":"code-fence-validity","name":"Code fence validity","description":"Fenced code blocks in served markdown are balanced - an unclosed fence swallows the rest of the document when an agent parses it","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"specUrl":"https://spec.commonmark.org/0.31.2/#fenced-code-blocks","recommendation":"Close every fenced code block (``` or ~~~) in your served markdown. CommonMark treats everything after an unclosed fence as code, so an agent parsing the document silently loses the rest of it. Count fence lines per file - the total must be even."},{"id":"docs-auth-gate","name":"Content behind auth","description":"Sampled content pages are publicly readable - pages behind a login wall are invisible to agents","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Serve your content pages without a login wall. Agents cannot complete auth flows while browsing - a 401/403 or a login-form page is invisible content. Keep public documentation public; if some content must stay gated, publish an ungated summary so agents can still represent it."},{"id":"public-api","name":"Public API with reachable endpoints","description":"The foundation of agent access: an API that agents can actually call. We check for documentation and at least one endpoint that responds.","layer":"usability","maxScore":7,"bonus":false,"applicability":"api","protocol":"either","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Expose a public REST or GraphQL API. AI agents need programmatic access  - not just a web UI  - to integrate with your product."},{"id":"openapi-spec","name":"OpenAPI spec published","description":"An OpenAPI spec is your API in a form machines can read. Agents use it to integrate without a human studying the docs, so it is one of the highest-leverage files you can publish.","layer":"accessibility","maxScore":7,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish an OpenAPI (Swagger) specification at /openapi.json or /api/openapi.yaml. This is how agents understand your API surface automatically."},{"id":"oauth-support","name":"OAuth 2.0 support","description":"Agents need a standard way to sign in. We check for OAuth 2.0, or an explicitly open API that needs no keys at all.","layer":"usability","maxScore":5,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Implement OAuth 2.0 for API authentication. Publish your authorization server metadata at /.well-known/oauth-authorization-server."},{"id":"scoped-permissions","name":"Scoped permissions","description":"Granular permissions so an agent gets only the access it needs, nothing more. Businesses connect faster when the blast radius of a mistake is small. We look for declared OAuth scopes (OpenAPI security schemes, RFC 9728 metadata), not prose mentions.","layer":"usability","maxScore":5,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Declare scoped API permissions where machines can read them: named OAuth scopes in your OpenAPI security schemes, or scopes_supported in RFC 9728 protected-resource metadata. Prose descriptions of roles help humans, but agents need the machine-readable declaration to request least-privilege access."},{"id":"developer-portal","name":"Developer portal","description":"A place to sign up, get keys, and manage an integration without emailing anyone. Self-serve is the difference between integrating today and never.","layer":"accessibility","maxScore":6,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Create a developer portal at /developers with API keys, documentation, quickstart guides, and a sandbox environment."},{"id":"mcp-auth-mechanism","name":"MCP auth mechanism","description":"The right lock on the right door: docs servers should be public, product servers should require OAuth. We check your MCP auth matches what the server does.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Protect your MCP server with OAuth 2.0 authentication. Publish authorization server metadata at /.well-known/oauth-authorization-server for automatic agent auth flows."},{"id":"mcp-oauth-metadata","name":"MCP OAuth metadata","description":"Standard OAuth metadata (RFC 8414) that lets agents discover your sign-in flow automatically, instead of a developer wiring it by hand.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish RFC 8414 authorization server metadata with issuer, authorization_endpoint, and token_endpoint so agents can authenticate without hardcoded URLs. For client registration, advertise client_id_metadata_document_supported (CIMD) - Dynamic Client Registration is deprecated and worth keeping only as a compatibility path."},{"id":"mcp-pkce-s256","name":"MCP PKCE S256 support","description":"PKCE with S256, the OAuth hardening that stops intercepted codes from being replayed. Table stakes for agent auth done right.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Support PKCE with S256 code challenge method in your OAuth server. Add 'S256' to code_challenge_methods_supported in your authorization server metadata."},{"id":"mpp-support","name":"MPP payment protocol","description":"Machine Payments Protocol lets an agent pay you over plain HTTP. Supporting any one payment protocol is enough, so this reads N/A when another is detected. Optional for non-commerce sites.","layer":"payments","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Implement the Machine Payments Protocol so agents can pay for premium resources over HTTP 402. Return a complete WWW-Authenticate: Payment challenge - the full set of standard MPP parameters, not just the bare scheme - and advertise x-payment-info in your OpenAPI spec so agents can discover it."},{"id":"x402-support","name":"x402 payment protocol","description":"x402 lets an agent pay per request over plain HTTP micropayments. Supporting any one payment protocol is enough, so this reads N/A when another is detected. Optional for non-commerce sites.","layer":"payments","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Implement x402 payment protocol so AI agents can pay for API access via HTTP 402. x402 uses PAYMENT-REQUIRED/PAYMENT-SIGNATURE/PAYMENT-RESPONSE headers with Base64-encoded JSON. Add a /discovery/resources endpoint for agent discovery."},{"id":"onboarding-friction","name":"Agent onboarding friction","description":"Can an agent go from discovering you to a first successful API call with no human in the loop? Every manual step is a place automated integrations quietly die.","layer":"usability","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Offer a free tier or trial, self-serve API key generation, and a sandbox environment. Agents can't fill out 'contact sales' forms."},{"id":"web-bot-auth-directory","name":"Web Bot Auth directory","description":"A public key directory (RFC 9421) that lets agents cryptographically sign their requests, so you know exactly who is calling and can let the good ones through.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Publish a Web Bot Auth directory at /.well-known/http-message-signatures-directory. Serve a JSON document with a 'keys' array of Ed25519 JWKs (kty=OKP, crv=Ed25519, kid, nbf, exp). This lets agents sign their requests per RFC 9421 so you can distinguish legitimate bots from spoofers."},{"id":"api-catalog-rfc9727","name":"API catalog (RFC 9727)","description":"A machine-readable list of all your APIs at /.well-known/api-catalog (RFC 9727), so agents see your full surface in one request.","layer":"accessibility","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish an API catalog at /.well-known/api-catalog per RFC 9727. Serve it with Content-Type: application/linkset+json;profile=\"https://www.rfc-editor.org/info/rfc9727\" and include a 'linkset' array with item entries pointing to your OpenAPI specs and service descriptions."},{"id":"oauth-protected-resource","name":"OAuth Protected Resource metadata (RFC 9728)","description":"Metadata at /.well-known/oauth-protected-resource (RFC 9728) that tells agents your auth requirements up front, instead of leaving them to trial and error.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource. Include the resource field plus enough supporting metadata - your authorization servers, supported scopes, accepted bearer methods - that an agent can work out how to authenticate without first triggering a 401."},{"id":"ucp-support","name":"UCP - Universal Commerce Protocol","description":"A Universal Commerce Protocol profile at /.well-known/ucp, telling shopping agents how to transact with you. Supporting any one payment protocol is enough, so this reads N/A when another is detected. Optional for non-commerce sites.","layer":"payments","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish a UCP discovery profile at /.well-known/ucp with a required `version` (YYYY-MM-DD) and advertised `services`/`capabilities` per ucp.dev. Also expose the REST checkout surface (`POST /checkout-sessions` with `UCP-Agent` and `Idempotency-Key` headers) so agents can transact without per-vendor integrations."},{"id":"acp-support","name":"ACP - Agentic Commerce Protocol","description":"A live Agentic Commerce Protocol checkout endpoint (/checkout_sessions), the flow behind agent-driven purchases in ChatGPT. Supporting any one payment protocol is enough, so this reads N/A when another is detected. Optional for non-commerce sites.","layer":"payments","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Implement the Agentic Commerce Protocol checkout REST API: `POST /checkout_sessions` (create), update/get/complete/cancel variants, with `API-Version: YYYY-MM-DD` and `Idempotency-Key` required headers. Preflight OPTIONS should allow POST or return an ACP-shaped error with `supported_versions` so agents can negotiate."},{"id":"acp-delegate-payment","name":"ACP delegate payment","description":"The ACP endpoint that lets a buyer's agent hand you a payment credential securely (/agentic_commerce/delegate_payment). Supporting any one payment protocol is enough, so this reads N/A when another is detected. Optional for non-commerce sites.","layer":"payments","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Expose the ACP Delegate Payment endpoint at `POST /agentic_commerce/delegate_payment`. The request takes `payment_method`, `allowance` (max amount, currency, expiry, merchant scope), and `risk_signals`; the response returns a vault token. This lets agents pay on behalf of users with scoped, revocable credentials."},{"id":"ap2-support","name":"AP2 - Agent Payments Protocol","description":"Google's Agent Payments Protocol: signed mandates that prove a human authorized the purchase, backed by Mastercard, Visa, PayPal and Amex. Supporting any one payment protocol is enough, so this reads N/A when another is detected. Optional for non-commerce sites.","layer":"payments","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://ap2-protocol.org/","recommendation":"Adopt Google's AP2 (Agent Payments Protocol) authorization layer: advertise an AP2 mandate capability (e.g. `dev.ucp.shopping.ap2_mandate`) in your UCP discovery profile, and verify the three signed mandates (Intent, Cart, Payment - SD-JWT verifiable digital credentials) server-side before routing to a settlement rail. Note: one agentic payment protocol is sufficient - AP2 is only needed if you are not already covered by x402 / MPP / ACP / UCP."},{"id":"auth-md-exists","name":"auth.md exists","description":"auth.md is a walkthrough that teaches an agent how to get its own credentials, served at /auth.md. An emerging standard from WorkOS.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":true,"beta":false,"specUrl":"https://github.com/workos/auth.md/blob/main/AUTH.md","recommendation":"Publish /auth.md as a markdown prose walkthrough of how agents should obtain credentials. Serve it with Content-Type: text/markdown, lead with a top-level heading, and write at least ~200 chars of real content (not just a placeholder). See the WorkOS auth.md spec at https://github.com/workos/auth.md."},{"id":"auth-md-structure","name":"auth.md structure","description":"Your auth.md follows the standard walkthrough structure (discover, pick a method, register, claim, exchange, use, errors, revocation), so any agent can follow it step by step. We grade the content of each section, not just the headings.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":true,"beta":false,"specUrl":"https://github.com/workos/auth.md/blob/main/AUTH.md","recommendation":"Structure /auth.md as the WorkOS spec prescribes: sections for Discover, Pick a method, Register, Claim, Exchange, Use the access_token, Errors, and Revocation, with spec anchor keywords (agent_auth, identity_endpoint, identity_assertion, service_auth, id-jag, WWW-Authenticate). Reference https://github.com/workos/auth.md."},{"id":"auth-md-walkthrough-simulation","name":"auth.md walkthrough simulation","description":"We do not take your auth.md's word for it: we simulate the walkthrough end to end (read-only) and verify an agent that only knows a user's email could actually get through it.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":true,"beta":false,"specUrl":"https://github.com/workos/auth.md/blob/main/AUTH.md","recommendation":"Make your published auth-discovery chain traversable end to end: an agent starting at /auth.md (or your protected-resource metadata) should be able to follow the links to your authorization-server metadata and registration endpoint without hitting a dead link. Test the whole path, not just each file in isolation."},{"id":"agent-auth-discovery-metadata","name":"Agent auth discovery metadata","description":"The machine-readable half of agent auth: discovery documents (RFC 9728 and 8414) with an agent_auth block that round-trips to your auth.md, so agents can look up the exact request shape for each supported identity type.","layer":"usability","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":true,"beta":false,"specUrl":"https://github.com/workos/auth.md/blob/main/AUTH.md","recommendation":"Publish RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource on your resource server (the host that actually serves the API, e.g. api.<apex>) with `resource` and `authorization_servers`. Publish RFC 8414 authorization-server metadata at /.well-known/oauth-authorization-server on the AS origin, and include the WorkOS auth.md `agent_auth` block with `identity_endpoint`, `identity_types_supported` drawn from the spec enum (`anonymous`, `identity_assertion`, `service_auth` - the assertion variant, the ID-JAG URN `urn:ietf:params:oauth:token-type:id-jag`, belongs inside `identity_assertion.assertion_types_supported`, not at the top level), and the `identity_assertion.assertion_types_supported` block when you advertise that type, so agents can check their assertion shape is accepted before minting. Cross-link by listing the AS origin in PRM `authorization_servers`, and point `agent_auth.skill` back at your published /auth.md. Spec: https://github.com/workos/auth.md."},{"id":"agent-auth-www-authenticate","name":"Agent auth WWW-Authenticate hint","description":"When an agent hits a 401 on your API, the response should point at the metadata explaining how to authenticate. One WWW-Authenticate header turns a dead end into a signpost.","layer":"usability","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":true,"beta":false,"specUrl":"https://github.com/workos/auth.md/blob/main/AUTH.md","recommendation":"Return a 401 carrying a spec-shaped `WWW-Authenticate: Bearer resource_metadata=\"<your protected-resource metadata URL>\"` header on your API's primary entry points, so an agent learns your auth requirements from one request instead of hunting for the well-known document. Point the metadata URL at /.well-known/oauth-protected-resource on the host that serves the API. Spec: https://github.com/workos/auth.md."},{"id":"agent-auth-endpoints-reachable","name":"agent_auth endpoints reachable","description":"The registration, claim, and revocation surfaces your auth docs advertise actually respond. Stale discovery data is worse than none: it sends agents to doors that no longer exist.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":true,"beta":false,"specUrl":"https://github.com/workos/auth.md/blob/main/AUTH.md","recommendation":"Make sure the URIs you advertise (in the AS metadata agent_auth block OR in your /auth.md prose) for identity_endpoint, claim_endpoint, and events_endpoint actually resolve. An OPTIONS preflight should return any HTTP status (2xx/3xx/4xx that isn't 404). DNS-level failure or a 404 means the discovery block / prose is stale - either remove the URI or stand up the endpoint."},{"id":"pap-discovery","name":"Personal Agent Protocol discovery file","description":"Publishes /.well-known/poppy.json (Personal Agent Protocol) over HTTPS: a valid document for the requested host, naming an interface and an OAuth issuer when one is needed.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://personalagentprotocol.org/docs/spec","recommendation":"Publish a Personal Agent Protocol discovery file at /.well-known/poppy.json on the exact host agents request, served over HTTPS as a JSON object. Set protocol_version to \"0.2\" (the current draft, still in progress), set organization.domain to the host that serves the file (after any redirect, with www. if that host has it), describe at least one interface (agent, apis, or web), and name your OAuth issuer in auth.issuer whenever you list agent or apis entries, a feedback endpoint, or a web.browser_session_endpoint. Every URL in the file must be https://. Spec: https://personalagentprotocol.org/docs/spec"},{"id":"pap-issuer-binding","name":"PAP issuer binding","description":"The OAuth issuer named in poppy.json publishes RFC 8414 metadata whose issuer matches exactly and whose poppy_domains lists this domain, with the endpoints each sign-in type needs.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://personalagentprotocol.org/docs/spec","recommendation":"Serve RFC 8414 authorization-server metadata for the issuer named in poppy.json at /.well-known/oauth-authorization-server (inserted before the issuer's path when it has one). The metadata `issuer` must match auth.issuer exactly, `poppy_domains` must list this domain, and it must advertise token_endpoint and revocation_endpoint, plus authorization_endpoint when you offer direct sign-in and device_authorization_endpoint when you offer device sign-in. If you list dpop_signing_alg_values_supported, include ES256. Give every domain whose poppy.json names this issuer the same auth block. Send cache headers, and list only S256 in code_challenge_methods_supported. Spec: https://personalagentprotocol.org/docs/spec"},{"id":"pap-auth-config","name":"PAP sign-in and extension config","description":"Sign-in scopes in poppy.json are defined, mediated sign-in is well formed, and extensions are named and versioned as the spec requires.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://personalagentprotocol.org/docs/spec","recommendation":"Request only scopes PAP defines: poppy:read, poppy:write, and the keys you declare in auth.custom_scopes (custom scope names must not start with poppy:). Give mediated sign-in an https:// endpoint, its fields (each with name, label, and secret), and its scopes, name protocol extensions such as operations by their plain name and your own extensions with a prefix from your domain, give each one a version, and advertise the operations extension at version 1 with an https:// endpoint. Spec: https://personalagentprotocol.org/docs/spec"},{"id":"pap-interfaces","name":"PAP API and agent entries","description":"Conversation and API entries in poppy.json are complete: OpenAPI URLs serve OpenAPI 3.0 or 3.1, and MCP servers publish protected resource metadata naming the PAP issuer.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://personalagentprotocol.org/docs/spec","recommendation":"Complete every interface entry in poppy.json: each agent.protocols entry needs a type and an https:// endpoint, and each apis entry needs a type (openapi or mcp), a url, and a description. Point OpenAPI entries at a document that parses as OpenAPI 3.0 or 3.1, and have each MCP server publish RFC 9728 protected-resource metadata whose authorization_servers includes your auth.issuer (and, if it lists dpop_signing_alg_values_supported, ES256). Spec: https://personalagentprotocol.org/docs/spec"},{"id":"pap-endpoint-enforcement","name":"PAP endpoints reject unauthenticated agents","description":"Unauthenticated requests to the PAP token, conversation, MCP, browser-session and operations endpoints are rejected in the shape the spec defines. Runs only on a scan requested for this domain.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://personalagentprotocol.org/docs/spec","recommendation":"Reject unauthenticated agents in the shape PAP defines. Your token endpoint should answer a session request (the RFC 7523 JWT grant) it cannot verify with one of the codes the spec lists: invalid_client (401), invalid_grant (400), or invalid_dpop_proof (400) when the DPoP proof is missing. The conversation endpoint should return 401 with a `WWW-Authenticate: DPoP` challenge when no token is sent, and an MCP server that needs a token should answer a request without one with 401 and `WWW-Authenticate: Bearer resource_metadata=\"<your /.well-known/oauth-protected-resource URL>\"`, plus a DPoP challenge with the same resource_metadata when the server supports DPoP. The browser_session_endpoint should answer an invalid assertion with 400 and set no cookie, the operations endpoint should return 401 without a token, and a feedback endpoint should refuse a report sent with no token with 401 and a `WWW-Authenticate: DPoP` challenge. Answer the token, API, conversation, feedback and operations endpoints at the address you publish, without redirects, and require a DPoP proof with every token endpoint request and every request that carries a Session Token. The exception is an MCP server that does not support DPoP: requesting its Bearer token, and using it, needs no proof. Revocation and device authorization requests are neither, so they need no proof. Spec: https://personalagentprotocol.org/docs/spec"},{"id":"pap-cross-protocol","name":"PAP agrees with ARD and auth.md","description":"poppy.json agrees with the site's other agent surfaces: the same MCP server and organization as its ARD catalog, one OAuth issuer with auth.md, and every bound domain publishing its own poppy.json.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":true,"beta":false,"specUrl":"https://personalagentprotocol.org/docs/spec","recommendation":"Keep poppy.json consistent with your other agent surfaces: list the same MCP server and organization as your ARD catalog (/.well-known/ard.json), name the same OAuth issuer that your protected-resource metadata lists in authorization_servers, and, when one issuer serves both PAP and auth.md, list every grant both protocols send to its token endpoint in grant_types_supported. Publish a poppy.json naming the same issuer on every domain listed in poppy_domains, and point every 401 at the same resource_metadata URL. Spec: https://personalagentprotocol.org/docs/spec"},{"id":"mcp-server","name":"MCP server / manifest","description":"MCP is how agents plug directly into your product, the way apps plug into an app store. We check for a Model Context Protocol server or manifest.","layer":"usability","maxScore":6,"bonus":false,"applicability":"all","appliesTo":["domain","mcp","mcp-app"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Build an MCP (Model Context Protocol) server exposing your API as tools. Use Streamable HTTP transport for full score. This lets Claude, ChatGPT, and other AI agents call your product natively."},{"id":"mcp-error-handling","name":"MCP error handling","description":"When an agent sends your MCP server a bad request, the answer should be a structured error it can correct from, not a silent failure.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Return structured JSON-RPC errors (with code and message) when agents call invalid tools or pass bad arguments. Don't crash or return empty responses."},{"id":"mcp-transport-modern","name":"MCP modern transport","description":"Your MCP server speaks the current transport (Streamable HTTP), not just the legacy one. New agent clients increasingly expect it.","layer":"usability","maxScore":1,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Upgrade your MCP server from legacy SSE to Streamable HTTP transport. HTTP+SSE is formally deprecated with a year-long offramp; Streamable HTTP is the current standard and supports bidirectional communication."},{"id":"webmcp","name":"WebMCP support","description":"WebMCP (a W3C draft) exposes tools to agents right on your web pages, no separate server needed. Chrome and the ChatGPT desktop browser now discover and call these tools, so agents browsing your site can act, not just read.","layer":"usability","maxScore":5,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Expose in-page tools through WebMCP, a proposed web standard for browser agents. Register tools with document.modelContext.registerTool() and use navigator.modelContext only as a trailing compatibility fallback. Declarative toolname forms do not count: the WebMCP specification does not define them, so register the same actions with registerTool(). Chrome's origin trial covers versions 149-156, with shipping currently targeted for 157. ChatGPT can discover and call WebMCP site tools in the desktop app's built-in browser when the feature is available."},{"id":"markdown-negotiation","name":"Markdown agent docs","description":"Agents prefer markdown: the same content at a fraction of the noise. We check whether your docs are available as markdown, via content negotiation or .md routes.","layer":"accessibility","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Pick one: (a) return Content-Type: text/markdown on GET <homepage> when the request sends Accept: text/markdown, or (b) publish a static /llms.md, /auth.md, or /agents.md file at your root with real markdown content. Option (b) is usually a single static file. This is the cold-discovery path for agents that land at your homepage from web search without reading llms.txt first."},{"id":"markdown-negotiation-vary","name":"Markdown content negotiation (acceptmarkdown.com)","description":"The homepage serves Markdown when requested and HTML for browsers, with Vary: Accept on the Markdown response. Markdown support on a docs page alone does not satisfy this homepage check.","layer":"accessibility","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Enable Markdown negotiation on the scanned homepage. Supporting it only on /docs or a separate .md URL does not satisfy this check. Requests with Accept: text/markdown must receive a nonempty Markdown body with Content-Type: text/markdown and Vary: Accept. Keep serving HTML for Accept: text/html. Adding Vary alone does not create a Markdown response. Verify both with `curl -sS -L -i -H 'Accept: text/markdown' https://yourdomain.com/` and `curl -sS -L -i -H 'Accept: text/html' https://yourdomain.com/`. Check the final response headers and body: Markdown with Vary: Accept for the first request, HTML for the second."},{"id":"rate-limit-headers","name":"Rate limit response headers","description":"Standard RateLimit headers tell agents how much room they have left, so they slow down gracefully instead of hitting errors and giving up on you.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"either","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Return standard rate-limit headers on your API responses (the RFC RateLimit headers, plus Retry-After on a 429) so agents can self-throttle in real time, and document the conventions alongside your API."},{"id":"idempotency-key-support","name":"Idempotency-Key support","description":"Networks fail mid-request. Idempotency keys let an agent retry a payment or an order safely, without doubling it. We check that write endpoints accept the header.","layer":"usability","maxScore":3,"bonus":false,"applicability":"api","protocol":"either","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Support an idempotency key on your write operations and declare it where agents can read it: an Idempotency-Key header parameter on your POST/PUT/PATCH operations in your OpenAPI spec for REST, or a client-supplied id argument on your GraphQL mutations. Agents retry on network failures, and without this a retry can double-charge or duplicate a record."},{"id":"json-error-responses","name":"JSON error responses","description":"When something breaks, agents need a structured JSON error, not an HTML error page. One is recoverable, the other is a dead end.","layer":"usability","maxScore":4,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"required","maturity":"verified","draft":false,"beta":false,"recommendation":"Return structured JSON error responses with error codes, messages, and resolution hints. Agents can't parse HTML error pages."},{"id":"api-error-model","name":"REST typed error model","description":"A typed error schema in your OpenAPI spec, so agents know every failure shape in advance and can handle each one deliberately.","layer":"usability","maxScore":3,"bonus":false,"applicability":"api","protocol":"rest","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Document your error responses in your OpenAPI spec: give 4xx and 5xx responses a typed error schema (or use RFC 9457 application/problem+json). A consistent error object with a machine-readable code and a human-readable message lets agents handle failures without guessing."},{"id":"api-versioning-policy","name":"REST versioning / deprecation policy","description":"Agents integrate once and then run unattended. A versioning and deprecation policy is your promise about when things will break and how much warning they get.","layer":"usability","maxScore":3,"bonus":false,"applicability":"api","protocol":"rest","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Declare a versioning policy agents can rely on: version your API (in the URL path or a version header) and publish how you signal deprecation (a Sunset/Deprecation header or a documented timeline). Agents avoid integrating against a surface that can change without warning."},{"id":"pagination-shape","name":"REST pagination pattern","description":"A predictable way to page through lists, cursor-based preferred. Without one, agents read page one and quietly miss the rest of your data.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"rest","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Use a consistent, documented pagination shape on your list endpoints (cursor-based preferred) and define the pagination fields in your OpenAPI response schemas, so agents can page through results without guessing the shape."},{"id":"async-job-pattern","name":"REST async-job pattern","description":"Long-running operations should return 202 Accepted with a way to poll for the result. Otherwise agents time out and retry work that was still running.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"rest","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"For long-running operations, return 202 Accepted and point agents at where to poll for the result (a status/location reference plus a job identifier in the body), documented in your OpenAPI spec, so work that does not finish in one request is still followable."},{"id":"cli-tool","name":"CLI tool available","description":"An official CLI. Coding agents live in the terminal, so a CLI makes your product directly operable by the fastest-growing class of agents.","layer":"usability","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish an official CLI tool on npm, PyPI, or Homebrew. A CLI lets agents and developers script interactions with your product without building API integrations from scratch."},{"id":"rest-sdk-packages","name":"Multi-language SDK packages","description":"Official SDKs across ecosystems (npm, PyPI, Go). Each one is an integration an agent does not have to hand-roll, in the language it is already working in.","layer":"usability","maxScore":3,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish official SDK packages across multiple language ecosystems (npm, PyPI, Go modules, RubyGems). Auto-generate them from your OpenAPI spec using tools like openapi-generator. For each package set the project URL or homepage to your product domain (package.json `repository`/`homepage`, PyPI `Home-Page` or `project_urls`, RubyGems `homepage_uri`) - this is how agents verify the package is your official SDK."},{"id":"nlweb-ask","name":"NLWeb /ask endpoint","description":"An NLWeb /ask endpoint that answers natural-language questions about your site directly, no crawling needed.","layer":"usability","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Implement the NLWeb protocol (v0.55) with a POST /ask endpoint that accepts a JSON body with the question in query.text and returns JSON with _meta (response_type, version) plus a results array, or a failure with an error code. See github.com/nlweb-ai/website/blob/main/NLWEBSPEC.md."},{"id":"nlweb-streaming","name":"NLWeb streaming support","description":"Your /ask endpoint streams results as they are found, so agents can show progress instead of a spinner.","layer":"usability","maxScore":1,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Add SSE streaming to your NLWeb /ask endpoint. Accept prefer.streaming: true and respond with Content-Type: text/event-stream using the NLWeb v0.55 events (start, result, error, complete)."},{"id":"response-schema-coverage","name":"REST response schema coverage","description":"Typed response schemas on most of your API operations, so agents know the shape of what comes back before they call.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"rest","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Define typed JSON response schemas for every endpoint in your OpenAPI spec. Agents rely on these to know what fields they will get back; missing or partial schemas force trial-and-error."},{"id":"mcp-tool-annotations","name":"MCP tool annotations","description":"Labels that tell agents which tools are safe to call freely and which change or destroy things (readOnlyHint, destructiveHint). Essential when your MCP server can mutate real data.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Add behavioral annotations (readOnlyHint, destructiveHint) to your MCP tools. Agents use these to avoid destructive actions without user confirmation."},{"id":"mcp-server-card","name":"MCP server-card.json","description":"A server card for each MCP server, at <server URL>/server-card or listed in /.well-known/ai-catalog.json, that tells an agent what the server is and where to connect before it does.","layer":"usability","maxScore":2,"bonus":true,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Publish a server card for each MCP server (draft MCP server-card extension). List each card in /.well-known/ai-catalog.json, or serve it at <server URL>/server-card. /.well-known/mcp/server-card.json is still read for a site with one server, but the extension does not recommend it. Include name, description, and version, and put the server's exact address in remotes[].url (serverUrl is also read) so agents match each card to the right server before connecting. Follow the extension schema for full credit: $schema set to https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json, a reverse-DNS name such as com.example/mcp, and no tools[] (agents always trust the live tools/list). Keep the description to 100 characters or fewer, as the schema requires. A card without that $schema is still read, and then an optional tools[] list completes it."},{"id":"mcp-multi-surface-coverage","name":"Product + docs MCP coverage","description":"Two MCP servers, cleanly split: one for taking actions in your product, one for answering questions from your docs. The mature pattern for brands that have both.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Beyond your product MCP server, expose a documentation MCP surface so agents can pull your docs and reference material over the same protocol they use to act. Covering both the 'do' and the 'learn' surfaces over MCP earns this."},{"id":"sandbox-environment","name":"Sandbox / test environment","description":"A sandbox or test mode where agents can build an integration without touching production data. It lowers the stakes of every first call.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Provide a sandbox or test mode so agents can exercise your API without touching production data, and document how to reach it - this lowers the risk of a destructive call against live data."},{"id":"batch-endpoints","name":"REST batch / bulk endpoint","description":"Batch operations let an agent do a thousand things in one request instead of a thousand requests. We look for a /batch endpoint or array request bodies.","layer":"usability","maxScore":2,"bonus":true,"applicability":"api","protocol":"rest","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Offer a batch endpoint that accepts an array of operations in one request, documented in your spec, so an agent acting on many items can do it in bulk instead of looping one call at a time."},{"id":"mcp-resource-listing","name":"MCP resources exposed","description":"If your MCP server claims to offer resources, resources/list should return some. Advertising a capability and delivering nothing erodes agent trust.","layer":"usability","maxScore":3,"bonus":false,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"If your MCP server advertises the resources capability in its initialize handshake, make sure resources/list returns at least one resource. If you don't intend to expose resources, omit the capability - the check returns na with no penalty for tool-only servers. Quality of the resources you do return is scored separately by mcp-resource-quality."},{"id":"graphql-error-type-definition","name":"GraphQL typed error model","description":"Typed errors in your GraphQL schema (userErrors, error unions), so agents can tell 'bad input' from 'try again later' and react correctly.","layer":"usability","maxScore":3,"bonus":false,"applicability":"api","protocol":"graphql","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Model your GraphQL errors in the schema: define an error type and surface it through your mutation payloads (or a result union) instead of relying only on the top-level errors array, so agents can handle failures by type."},{"id":"graphql-versioning-policy","name":"GraphQL versioning / deprecation policy","description":"Deprecations declared in the schema (@deprecated) or a documented evolution policy, so agents learn about breaking changes before the break.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"graphql","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Adopt a GraphQL evolution policy: mark retiring fields with @deprecated (with a reason) and document how you sunset schema elements. @deprecated is the standard signal agents read."},{"id":"graphql-pagination-pattern","name":"GraphQL pagination pattern","description":"Relay-style cursor pagination (Connection and PageInfo types), the pattern every GraphQL client and agent already knows how to walk.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"graphql","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Paginate GraphQL lists with the standard Relay connection pattern (Connection and PageInfo types with cursors), a shape agents recognize and can traverse without custom handling."},{"id":"graphql-async-job-pattern","name":"GraphQL async-job pattern","description":"Async job mutations with status types or progress subscriptions, so long-running work looks like progress instead of a hang.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"graphql","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"For long-running GraphQL work, model it as an async job: return a job/task type the agent can query for status, and consider a subscription for progress, so a mutation does not block on slow work."},{"id":"graphql-schema-completeness","name":"GraphQL schema description coverage","description":"Descriptions on at least 80% of your GraphQL types and fields. A well-described schema documents itself to any agent that introspects it.","layer":"usability","maxScore":3,"bonus":true,"applicability":"api","protocol":"graphql","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Document your GraphQL schema thoroughly - descriptions on your types, fields, and arguments - so agents can decide how to call your API from the schema alone."},{"id":"graphql-batch-mutations","name":"GraphQL batch mutations","description":"Bulk mutation fields (createMany, updateMany) so agents can act at scale in one round trip instead of hundreds.","layer":"usability","maxScore":2,"bonus":true,"applicability":"api","protocol":"graphql","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Offer batch or bulk mutations (a mutation that accepts many inputs at once) so agents can apply changes to many records in a single round trip instead of one mutation per item."},{"id":"agent-ua-markdown","name":"Bot-UA markdown serving","description":"You recognize AI bots by their user-agent and serve them markdown even when they ask for HTML. Proactive courtesy for machine readers.","layer":"accessibility","maxScore":1,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Optionally detect AI-bot User-Agents (GPTBot, ClaudeBot, PerplexityBot) server-side and serve them a markdown representation of the page directly, even when they send Accept: text/html. Verify with `curl -A \"ClaudeBot/1.0\" https://yourdomain.com/` - a markdown body earns this bonus. Accept-header negotiation is scored separately."},{"id":"agent-friendly-404","name":"Agent-friendly 404s","description":"A real 404/410 status earns partial credit. Full credit also requires a Markdown error body, so agents can read what happened and where to go next.","layer":"usability","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"specUrl":"https://www.rfc-editor.org/rfc/rfc9110#name-404-not-found","recommendation":"This check awards partial credit for a real HTTP 404/410 status and full credit when a Markdown error body is also available. If the status is already correct, keep it and add the Markdown body. Include at least 20 characters explaining the error and point agents to your docs, sitemap, or llms.txt. Verify with `curl -sS -L -i -H 'Accept: text/markdown' https://yourdomain.com/some-path-that-does-not-exist`. Check both the final 404/410 status and the Markdown body, served with Content-Type: text/markdown. A correct status with an HTML error page still earns only partial credit."},{"id":"ax-document-structure","name":"Accessible document structure","description":"Agents navigate your page the way screen readers do: by landmarks and headings. We check your HTML is a real document, with a main region, nav, and a sane heading order.","layer":"usability","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false},{"id":"ax-native-controls","name":"Native interactive controls","description":"Buttons should be buttons. Clickable divs are invisible to agents that target elements by role and name, so they cannot click what your users can.","layer":"usability","maxScore":3,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false},{"id":"ax-accessible-names","name":"Accessible names on controls","description":"Every control needs a name a machine can compute (text, label, aria-label, alt). An unnamed icon button cannot be referenced, so it cannot be used.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false},{"id":"ax-form-labeling","name":"Form control labeling","description":"Form fields with real labels, so agents know what each one expects. Placeholder text disappears on focus and never counts. N/A when a page has no forms.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false},{"id":"ax-tree-injection-safe","name":"Accessibility-tree injection safety (bonus)","description":"We scan for hidden instructions in aria-labels, alt text, and off-screen content that could hijack an agent reading your page (prompt injection). Upside only; never costs points.","layer":"usability","maxScore":2,"bonus":true,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false},{"id":"mcp-app-registry","name":"MCP Apps support","description":"MCP Apps let you ship real interactive UI inside ChatGPT and Claude, not just text answers. We check whether your server exposes any. N/A for pure API and data services.","layer":"usability","maxScore":4,"bonus":false,"applicability":"mcp-app","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Add MCP Apps support to your MCP server using @modelcontextprotocol/ext-apps. Expose ui:// resources and add _meta.ui.resourceUri to tools so agents can render interactive UIs directly in conversation."},{"id":"a2ui-support","name":"A2UI / generative UI support","description":"Your product can render UI inside an agent conversation, via MCP Apps, the OpenAI Apps SDK, or generative UI patterns. Where users see you when they never visit your site.","layer":"usability","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"emerging","maturity":"emerging","draft":false,"beta":false,"recommendation":"Support Agent-to-UI rendering via MCP Apps (ui:// resources), OpenAI Apps SDK, or generative UI patterns that let agents render interactive UIs in conversation."},{"id":"mcp-apps-ui-quality","name":"MCP Apps UI quality","description":"Your in-agent UI holds up to scrutiny: correct MIME type, valid HTML, dark mode support, and no hardcoded secrets in the markup.","layer":"usability","maxScore":4,"bonus":true,"applicability":"mcp-app","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Ensure your MCP Apps resources use MIME type text/html;profile=mcp-app, include <!DOCTYPE html>, and add <meta name=\"color-scheme\" content=\"light dark\"> for dark mode. Never hardcode secrets in resource HTML."},{"id":"mcp-view-domain","name":"MCP App view reachable","description":"The view your MCP App points at loads publicly and serves HTML with no auth wall. A broken or gated view is worse than none: it fails in front of the user.","layer":"usability","maxScore":4,"bonus":false,"applicability":"mcp-app","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Make sure your MCP App view is reachable and public. For inline ui:// resources: return HTML with <!DOCTYPE html> and no login form. For external HTTP origins (referenced via <base href> or <meta refresh>): return 200 OK + text/html without requiring auth (no 401/403, no password input in the body)."},{"id":"mcp-view-csp","name":"MCP App view CSP","description":"ChatGPT, Claude, and other hosts sandbox your MCP App view with the CSP you declare in _meta.ui.csp. We check that it names specific origins, or is left out so hosts apply their strict default.","layer":"usability","maxScore":4,"bonus":false,"applicability":"mcp-app","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Declare your view's CSP in _meta.ui.csp on the ui:// resource's resources/read content item, using the camelCase origin lists: connectDomains (fetch and WebSocket targets), resourceDomains (scripts, styles, images, fonts, media), frameDomains (nested iframes), and baseUriDomains. List specific origins such as https://api.example.com or https://*.example.com, never * or a bare scheme like https:. If the view makes no network calls and loads no external assets, leave csp out and hosts apply their restrictive default. Hosts build the iframe's CSP from this declaration, so a CSP in the view's own HTML does not replace it, and browsers ignore frame-ancestors in a <meta> tag."},{"id":"api-schema-analysis","name":"API schema complexity analysis","description":"Is your API simple enough for an agent to use without a human explaining it? Deeply nested, ambiguous schemas fail silently in agent hands.","layer":"usability","maxScore":2,"bonus":false,"applicability":"api","protocol":"either","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Make your API spec self-describing: a unique operationId and a description on every operation, typed parameters, and response schemas. For GraphQL, a fully typed schema with a documented cost or rate limit reads best."},{"id":"agent-crawler-reachability","name":"Agent crawler reachability","description":"Whether the homepage is reachable to the agents that act for users and search (ChatGPT-User, OAI-SearchBot, Claude-User, PerplexityBot). A site that blocks these agents is invisible to them from step one.","layer":"accessibility","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Let ChatGPT-User, OAI-SearchBot, Claude-User, PerplexityBot reach your homepage through your WAF, bot rules, and robots.txt."},{"id":"function-calling-compat","name":"Function calling compatibility","description":"Whether your API spec translates cleanly into the function-calling format ChatGPT, Claude, and Gemini actually consume. Incompatible specs mean agents cannot call you even when they want to.","layer":"usability","maxScore":2,"bonus":false,"applicability":"domain-only","appliesTo":["domain"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Ensure API endpoints have unique operation IDs, typed schemas, and descriptions compatible with LLM function-calling formats."},{"id":"mcp-resource-quality","name":"MCP resource quality","description":"The MCP resources you declare should actually read: valid type, non-empty content. We grade the share that works.","layer":"usability","maxScore":3,"bonus":false,"applicability":"mcp","appliesTo":["domain","mcp","mcp-app"],"tier":"recommended","maturity":"verified","draft":false,"beta":false,"recommendation":"Ensure every resource returned by resources/list reads cleanly via resources/read: declare a valid mimeType, return non-empty content, and make sure any URIs in the content resolve. Broken or empty resources break agent UX silently."}]}