stripe.com
17 of 26 applicable checks passed.
Agent Ready Summary
4 fully passingThese standards are live on your site. Here's what AI agents can do with them today.
llms.txt
DocsImproves how AI agents understand and interact with your site.
robots.txt directives
DocsImproves how AI agents understand and interact with your site.
Content quality
Improves how AI agents understand and interact with your site.
Identity
DocsImproves how AI agents understand and interact with your site.
Agent Ready
4 capabilities fully passing — agents can rely on these.
llms.txt
Learn →2 / 2 checks passed▾
llms.txt
Learn →robots.txt directives
Learn →3 / 3 checks passed▾
robots.txt directives
Learn →Content quality
2 / 2 checks passed▾
Content quality
(Optional) Add `<link rel="alternate" type="application/rss+xml" href="/feed">` or serve a feed at /feed, /rss, /atom.xml, or /feed.json. Lets agents subscribe to content updates.
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>stripe.com</title>
<link>https://stripe.com/</link>
<description>Updates from stripe.com</description>
<item>
<title>First post</title>
<link>https://stripe.com/posts/first</link>
<pubDate>Mon, 12 May 2026 12:00:00 GMT</pubDate>
<description>One-line summary of the post.</description>
</item>
</channel>
</rss>Identity
Learn →1 / 1 checks passed▾
Identity
Learn →Needs Work
8 areas with missing or partial checks.
Tailored Recommendations
4 actionsPrioritized by impact on agent performance. Tackle Critical and High items first.
Why it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 1 item- llms-full.txt foundServe `/llms-full.txt` — the companion to llms.txt containing the full concatenated text of your site so non-browsing LLMs (RAG pipelines, embedded agents) can ingest your content in one fetch. See [the spec](https://llmstxt.org/).
- 2
Homepage & Meta
Medium impact7/8 passingWhy it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 1 item- llms.txt linked from HTMLAdd `<link rel="alternate" type="text/plain" href="/llms.txt">` to your HTML `<head>` so search engines (especially Google for Gemini) index the relationship.
Why it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 5 items- Agent UA gets non-HTMLAgent fetch tools commonly send `Accept: text/markdown, text/html, */*` — markdown listed first, which per [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept) signals it as the preferred type. Serve markdown or plain text in that case, not HTML. Three common bugs produce the wrong outcome: (1) substring-matching the Accept header (`if accept.includes('text/html')`) — ignores preference order entirely; (2) framework defaults that pick HTML whenever `*/*` is in Accept, overriding explicit non-HTML preferences (Rails 8: `Accept: text/markdown, */*` returns HTML even though markdown is listed first); (3) preferring server-declared format order over client preference and ignoring q-values — even `Accept: text/plain;q=0.9, text/html;q=0.5` returns HTML when the server's internal preference list favors HTML. Fix recipes by stack: Express — `req.accepts(['text/html','text/markdown','text/plain'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — set `request.format` explicitly in a `before_action` based on the leading Accept type. All proper Accept negotiators handle preference order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which of the three bug patterns is in play. Pass requires Content-Type `text/markdown`, `text/plain`, or `application/json` (anything but HTML) with body ≥20 bytes. See https://agentgrade.com/kb/content-negotiation.
- Accept: JSON returns JSONReturn JSON with status 200 when `Accept: application/json` is sent to the root URL. Note: the response must not have a top-level `"error"` field — structured error responses like `{"error": "..."}` will fail this check.
- Accept: text returns textReturn `Content-Type: text/plain` or `text/markdown` with status 200 when `Accept: text/plain` is sent to the root URL. The body must be at least 20 bytes.
- Accept: markdown returns markdownReturn `Content-Type: text/markdown` when the client sends `Accept: text/markdown`. Agents that prefer markdown can request it directly without scraping HTML.
- + 1 more — see full breakdown below.
Why it matters: Missing this signal makes it harder for agents to understand your site.
Fix these 2 items- Cache headersReturn `Cache-Control`, `ETag`, or `Last-Modified` headers so agents know when to re-fetch vs use cached data. See https://agentgrade.com/kb/infrastructure.
- Structured errors (JSON 404)Return JSON error responses instead of HTML error pages. Agents can parse `{"error": "not found"}` but not an HTML 404 page wrapped in nav/footer chrome. Use content negotiation: when `Accept: application/json`, return JSON; for browsers, return HTML. SPA gotcha: if your catch-all serves `index.html` with status 200 for unknown paths, the check fails — return status 404 from the server, then let the SPA route on the client. See https://agentgrade.com/kb/infrastructure.
0%llms-full.txt
Learn →0 / 1 checks passed▾
llms-full.txt
Learn →Serve `/llms-full.txt` — the companion to llms.txt containing the full concatenated text of your site so non-browsing LLMs (RAG pipelines, embedded agents) can ingest your content in one fetch. See [the spec](https://llmstxt.org/).
Add `<link rel="alternate" type="text/plain" href="/llms-full.txt">` to your HTML `<head>` so agents and search engines discover the full-content companion file.
<link rel="alternate" type="text/plain" title="llms-full.txt" href="/llms-full.txt">88%Homepage & Meta
7 / 8 checks passed▾
Homepage & Meta
Add `<link rel="alternate" type="text/plain" href="/llms.txt">` to your HTML `<head>` so search engines (especially Google for Gemini) index the relationship.
<link rel="alternate" type="text/plain" title="llms.txt" href="/llms.txt">Add `<link rel="icon" type="image/png" href="/favicon.png">` (at least 180x180) or serve `/favicon.png` for broad browser compatibility.
0%agents.txt
Learn →0 / 0 checks passed▾
agents.txt
Learn →Serve `/agents.txt` with at least 10 characters of content. There is no required format — the scanner only checks for existence. Best practice: define agent access policies, rate limits, and allowed actions.
0%Content negotiation
Learn →0 / 5 checks passed▾
Content negotiation
Learn →Agent fetch tools commonly send `Accept: text/markdown, text/html, */*` — markdown listed first, which per [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept) signals it as the preferred type. Serve markdown or plain text in that case, not HTML. Three common bugs produce the wrong outcome: (1) substring-matching the Accept header (`if accept.includes('text/html')`) — ignores preference order entirely; (2) framework defaults that pick HTML whenever `*/*` is in Accept, overriding explicit non-HTML preferences (Rails 8: `Accept: text/markdown, */*` returns HTML even though markdown is listed first); (3) preferring server-declared format order over client preference and ignoring q-values — even `Accept: text/plain;q=0.9, text/html;q=0.5` returns HTML when the server's internal preference list favors HTML. Fix recipes by stack: Express — `req.accepts(['text/html','text/markdown','text/plain'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — set `request.format` explicitly in a `before_action` based on the leading Accept type. All proper Accept negotiators handle preference order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which of the three bug patterns is in play. Pass requires Content-Type `text/markdown`, `text/plain`, or `application/json` (anything but HTML) with body ≥20 bytes. See https://agentgrade.com/kb/content-negotiation.
Return JSON with status 200 when `Accept: application/json` is sent to the root URL. Note: the response must not have a top-level `"error"` field — structured error responses like `{"error": "..."}` will fail this check.
Return `Content-Type: text/plain` or `text/markdown` with status 200 when `Accept: text/plain` is sent to the root URL. The body must be at least 20 bytes.
Return `Content-Type: text/markdown` when the client sends `Accept: text/markdown`. Agents that prefer markdown can request it directly without scraping HTML.
Stricter than "Agent UA gets non-HTML": the response Content-Type must match the type the client actually preferred, accounting for client order AND q-values. The scanner probes four variations: (1) `Accept: text/markdown, text/html, */*` should return `text/markdown` (Claude Code / Cursor pattern); (2) `Accept: text/html, text/markdown, */*` should return `text/html` (reverse-order — catches sites that ignore client order and use server-side preference instead); (3) `Accept: text/markdown;q=0.5, text/html;q=1.0` should return `text/html` (catches sites that ignore q-values entirely — see [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept)); (4) `Accept: application/json, text/html, */*` should return `application/json` (programmatic discovery). All four must pass. Common bug patterns: substring matching the Accept header (`if accept.includes('text/html')`); framework defaults that fall back to HTML when `*/*` is present (Rails 8); hand-rolled selection logic using server-side priority order; q-value parsing missing entirely. Fix recipe by stack: Express — `req.accepts(['text/html','text/markdown','text/plain','application/json'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — `before_action` setting `request.format` based on the leading non-wildcard Accept type. All proper Accept negotiators handle order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which bug pattern is in play. See https://agentgrade.com/kb/content-negotiation.
Your homepage redirects agent requests (302/301) to a different URL — likely `/llms.txt` or similar. Industry best practice is **inline content negotiation**: same URL returns different bodies based on the `Accept` header. Cited by [RFC 9110 §12.2](https://www.rfc-editor.org/rfc/rfc9110#name-reactive-negotiation) (reactive/redirect-based negotiation "suffers from... needing a second request"), and used by every major content-negotiation-aware site we tested — GitHub API, Stripe docs, Cloudflare docs, Vercel, Mintlify, Sanity. The fix: detect Accept on the same URL, serve the right body inline, set `Vary: Accept`. No 302. Concrete benefits: one fetch instead of two (half the latency), the URL the agent reports to the user is the URL they asked about (not a redirect target), and caches handle it cleanly. See https://agentgrade.com/kb/content-negotiation.
When the same URL returns different bodies based on the `Accept` header, include `Vary: Accept` in the response so shared caches (CDNs, proxies) key entries by `Accept`. Without it, a CDN can serve JSON to a browser or HTML to an agent depending on which request hit first. `Vary: *` or `Cache-Control: private` / `no-store` also satisfy this. Only applies to sites that actually negotiate — API-only origins serving JSON to every request are exempt.
50%Infrastructure
Learn →2 / 4 checks passed▾
Infrastructure
Learn →Return `Cache-Control`, `ETag`, or `Last-Modified` headers so agents know when to re-fetch vs use cached data. See https://agentgrade.com/kb/infrastructure.
// Set Cache-Control + ETag on long-lived responses
app.get('/api/v1/things/:id', async (req, res) => {
const thing = await db.findThing(req.params.id);
res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
res.setHeader('ETag', '"' + thing.version + '"');
res.setHeader('Last-Modified', thing.updatedAt.toUTCString());
res.json(thing);
});Return JSON error responses instead of HTML error pages. Agents can parse `{"error": "not found"}` but not an HTML 404 page wrapped in nav/footer chrome. Use content negotiation: when `Accept: application/json`, return JSON; for browsers, return HTML. SPA gotcha: if your catch-all serves `index.html` with status 200 for unknown paths, the check fails — return status 404 from the server, then let the SPA route on the client. See https://agentgrade.com/kb/infrastructure.
// Content negotiation: JSON for agents, HTML for browsers
app.use((req, res) => {
res.status(404);
if (req.accepts('json') && !req.accepts('html')) {
return res.json({ error: 'not_found', path: req.path });
}
res.format({
'application/json': () => res.json({ error: 'not_found', path: req.path }),
'text/html': () => res.send('<h1>404 Not Found</h1>'),
default: () => res.type('text/plain').send('Not found'),
});
});(Optional) Return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, or `Retry-After` headers so agents can self-throttle. See https://agentgrade.com/kb/infrastructure.
// Surface rate-limit state so agents can self-throttle
import rateLimit from 'express-rate-limit';
app.use('/api', rateLimit({
windowMs: 60_000,
max: 60,
standardHeaders: true, // adds RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
legacyHeaders: false,
}));0%Bot authentication
Learn →0 / 0 checks passed▾
Bot authentication
Learn →Serve `/.well-known/http-message-signatures-directory` with a JSON object listing your agent identity and public keys (RFC 9421).
Add a `members` array to your signatures directory with at least one entry containing `name` and `publicKeyUrl`.
Each member in your signatures directory should have a `publicKeyUrl` pointing to a fetchable public key for signature verification.
0%A2A (Agent-to-Agent)
Learn →0 / 0 checks passed▾
A2A (Agent-to-Agent)
Learn →(Optional) Publish a Google A2A Agent Card at `/.well-known/agent.json` with `name`, `url`, `description`, and `capabilities` to let other agents discover and interact with your agent. Use your real URL and only capabilities you actually expose. See https://agentgrade.com/kb/a2a.
{
"name": "stripe.com",
"url": "https://stripe.com/",
"description": "What this agent does, in one sentence.",
"version": "1.0.0",
"capabilities": {
"streaming": false,
"pushNotifications": false
},
"skills": [
{
"id": "search",
"name": "Search catalog",
"description": "Search the product catalog for matching items"
}
]
}(Optional) Ensure your Agent Card contains at least `name` and `url` fields so agents can identify and connect to your service. See https://agentgrade.com/kb/a2a.
0%WebMCP
Learn →0 / 0 checks passed▾
WebMCP
Learn →(Optional) Publish a WebMCP manifest at `/.well-known/webmcp.json` listing tools your site exposes to browser-based AI agents. See https://agentgrade.com/kb/webmcp.
{
"name": "stripe.com",
"version": "1.0.0",
"description": "Browser-accessible MCP tools for stripe.com",
"tools": [
{
"name": "search",
"description": "Search the catalog",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } },
"required": ["query"]
}
}
]
}(Optional) Add `tool-name` and `tool-description` attributes to `<form>` elements so browser AI agents can invoke them as structured tools. See https://agentgrade.com/kb/webmcp.
<form action="/api/v1/search" method="post"
tool-name="search"
tool-description="Search the catalog">
<input name="query" type="text" placeholder="Search..." required>
<button type="submit">Search</button>
</form>Not Applicable
Skipped for this type of site.
Machine Payments
Learn →Not applicable to this site▾
Machine Payments
Learn →Return HTTP 402 with a Payment-Required header on at least one paid endpoint.
Add x402 middleware that returns a base64-encoded Payment-Required header with amount, asset, payTo, and facilitator.
Add MPP middleware: `npm install mppx`, configure with MPP_SECRET_KEY and MPP_RECIPIENT_ADDRESS.
Add Stripe SPT support via mppx: `npm install mppx`, configure with Stripe secret key. Returns 402 with `WWW-Authenticate: Payment method="stripe"`.
Add L402 support: return 402 with `WWW-Authenticate: L402 macaroon="...", invoice="..."`.
Ensure the Payment-Required header is valid base64-encoded JSON with x402Version, accepts[], and resource fields.
Include `payTo` (wallet address) in the payment challenge or in /.well-known/x402.json.
Include `amount` in the payment challenge.
Publish `/.well-known/x402.json` so agents can discover your paid endpoints. Required fields: `x402Version`, `name`, `network`, `facilitator`, `payTo`, `services[]`. See https://agentgrade.com/kb/bazaar for schema and example.
Bazaar discovery
Learn →Not applicable to this site▾
Bazaar discovery
Learn →Add `"extensions": { "bazaar": { "discoverable": true } }` inside the base64-encoded Payment-Required header JSON. See https://agentgrade.com/kb/bazaar for a full example.
Serve `/.well-known/x402.json` so agents can discover your paid endpoints. Required fields: `x402Version`, `name`, `network`, `facilitator`, `payTo`, `services[]`. See https://agentgrade.com/kb/bazaar for the full spec.
{
"x402Version": 2,
"name": "stripe.com",
"network": "base",
"facilitator": "coinbase",
"payTo": "0xYourWalletAddress",
"services": [
{
"method": "POST",
"path": "/your-paid-endpoint",
"description": "What this endpoint does",
"amount": "0.50"
}
]
}Declare at least one service with `method` and `path` fields (e.g. `"method": "POST", "path": "/your-endpoint"`). Optionally add `extensions.bazaar.discoverable: true` for Bazaar registry listing.
Add `"name": "stripe.com"` to `/.well-known/x402.json`.
Add `"facilitator": "coinbase"` (or your facilitator URL) to `/.well-known/x402.json`.
Add `"payTo": "0xYourWallet"` to `/.well-known/x402.json` — this is the wallet address that receives payments.
MCP endpoint
Learn →Not applicable to this site▾
MCP endpoint
Learn →Serve a JSON-RPC 2.0 endpoint (checked at /mcp, /api/mcp, /.well-known/mcp.json, /mcp/docs). The scanner accepts 200, 405, or 406 as indicators that an MCP endpoint exists. See https://agentgrade.com/kb/mcp.
// Minimal Express JSON-RPC 2.0 handler at /mcp
app.post('/mcp', express.json(), (req, res) => {
const { id, method, params } = req.body || {};
if (method === 'initialize') {
return res.json({ jsonrpc: '2.0', id, result: {
protocolVersion: '2024-11-05',
capabilities: { tools: {} },
serverInfo: { name: 'stripe.com', version: '1.0.0' },
}});
}
if (method === 'tools/list') {
return res.json({ jsonrpc: '2.0', id, result: { tools: [
{ name: 'echo', description: 'Echoes its input',
inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } },
]}});
}
if (method === 'tools/call') {
const { name, arguments: args } = params || {};
if (name === 'echo') {
return res.json({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: args.text }] }});
}
return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unknown tool: ' + name }});
}
res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' }});
});Handle the `initialize` JSON-RPC method and return `{ "protocolVersion": "2024-11-05", "capabilities": {}, "serverInfo": { "name": "your-service" } }` in the result. See https://agentgrade.com/kb/mcp.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "stripe.com", "version": "1.0.0" }
}
}Handle `tools/list` and return `{ "tools": [{ "name": "...", "description": "...", "inputSchema": {...} }] }`. See https://agentgrade.com/kb/mcp.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "search",
"description": "Search the catalog for matching items",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } },
"required": ["query"]
}
}
]
}
}Handle `tools/call` with `{ "name": "tool_name", "arguments": {...} }` — even a structured JSON-RPC error response proves the pipeline works. See https://agentgrade.com/kb/mcp.
// Handle tools/call in the same /mcp handler
if (method === 'tools/call') {
const { name, arguments: args } = params || {};
if (name === 'search') {
const results = await db.search(args.query);
return res.json({ jsonrpc: '2.0', id, result: {
content: [{ type: 'text', text: JSON.stringify(results) }]
}});
}
return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unknown tool: ' + name }});
}Return `name` in your MCP manifest or `serverInfo.name` in the initialize response. See https://agentgrade.com/kb/mcp.
(Optional) Serve SSE by returning `Content-Type: text/event-stream` on GET requests to the MCP endpoint. Streamable HTTP (the newer MCP transport) is detected separately via 406 status. See https://agentgrade.com/kb/mcp.
Add `Access-Control-Allow-Origin: *`, `Access-Control-Allow-Methods: GET, POST, OPTIONS`, and `Access-Control-Allow-Headers: Content-Type, Accept, Authorization` to your MCP endpoint, and respond to OPTIONS preflight with 204. Without these, browser-based agents (Claude.ai web, ChatGPT browser, extensions) cannot reach your MCP endpoint. Use a specific origin instead of `*` if your endpoint is paid or auth-gated. See https://agentgrade.com/kb/mcp.
// Browser-based agents (Claude.ai web, ChatGPT browser) need CORS on /mcp
app.options('/mcp', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization');
res.status(204).end();
});
app.use('/mcp', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
});OpenAPI spec
Learn →Not applicable to this site▾
OpenAPI spec
Learn →Serve an OpenAPI spec (checked at /openapi.json, /openapi.yaml, /swagger.json, /api-docs, /v1/openapi.json). Must contain an `openapi` or `swagger` key and be valid JSON. See https://agentgrade.com/kb/openapi.
{
"openapi": "3.1.0",
"info": {
"title": "stripe.com API",
"version": "1.0.0",
"description": "Public API for stripe.com"
},
"servers": [{ "url": "https://stripe.com" }],
"paths": {
"/api/v1/things": {
"post": {
"summary": "Create a thing",
"requestBody": {
"required": true,
"content": { "application/json": { "schema": {
"type": "object",
"properties": { "name": { "type": "string" }, "qty": { "type": "integer" } },
"required": ["name"]
}}}
},
"responses": { "201": { "description": "Created" } }
}
}
}
}Add `paths` to your OpenAPI spec with at least one endpoint definition.
Ensure the paths declared in your OpenAPI spec resolve to real routes (any non-404 response counts, including 401 and 402).
Ensure at least one declared endpoint returns a JSON response with status 200-499 (excluding 404) and a Content-Type matching your spec.
Include `"openapi": "3.0.0"` or `"swagger": "2.0"` at the root of your spec.
Add `"info": { "title": "Your API" }` to your OpenAPI spec.
Add `x-payment-info` to paid OpenAPI operations: `"x-payment-info": { "protocols": [...], "price": { "amount": "0.50", "currency": "USD" } }`. Helps agents discover pricing before triggering a 402.
Skill file
Learn →Not applicable to this site▾
Skill file
Learn →Serve `/skill.md` per the [agentskills.io](https://agentskills.io/specification) spec — markdown with YAML frontmatter (`name`, `description`) and a body of agent instructions.
Add YAML frontmatter to your skill.md: `---\nname: my-skill\ndescription: One-line summary\n---`. `name` is 1-64 chars, lowercase alphanumeric with single hyphens. `description` is 1-1024 chars.