Content Negotiation
Cette vérification représente 4 % du score.
Serve Markdown to AI agents via Accept: text/markdown, send Vary: Accept, avoid relabeled HTML, and advertise rel=alternate fallbacks.
Homepage does not serve Markdown via content negotiation
Your server returned HTML (or another type) when asked for Accept: text/markdown. Content negotiation is standard HTTP: same URL, different representation depending on the Accept header.
The zero-code options first:
- Cloudflare (Markdown for Agents): enable it in the dashboard and Cloudflare converts your HTML to Markdown at the edge for any request carrying
Accept: text/markdown. - Vercel: supports Markdown content negotiation for framework deployments; verify it is active for your project.
- Docs platforms: Mintlify and Read the Docs already serve Markdown variants out of the box.
Implementing it yourself (Express example):
app.get('*', (req, res, next) => {
if (req.accepts(['text/html', 'text/markdown']) === 'text/markdown') {
res.set('Content-Type', 'text/markdown; charset=utf-8');
res.set('Vary', 'Accept');
return res.send(renderPageAsMarkdown(req.path));
}
next(); // fall through to the regular HTML handler
});Verify with curl:
curl -s -H "Accept: text/markdown" https://your-site.com | head # Expected: Markdown content, not <!doctype html> curl -sI -H "Accept: text/markdown" https://your-site.com | grep -i -E 'content-type|vary' # Expected: Content-Type: text/markdown; charset=utf-8 # Vary: Accept
Server responds 406 Not Acceptable
Your server performs strict content negotiation and rejects Accept: text/markdown outright instead of falling back to HTML. This is worse than ignoring the header: agents get nothing at all.
Configure your negotiation layer to treat HTML as the default representation when the requested type is unavailable, rather than returning 406. In most frameworks this means listing text/html as the fallback in the negotiation config.
No Markdown alternate link fallback
If you cannot enable negotiation on the same URL, you can still get partial credit by advertising a Markdown version of the page in your <head>:
<link rel="alternate" type="text/markdown" href="/index.md" title="Markdown version">
Agents that understand the pattern fetch /index.md directly. This is weaker than negotiation (it costs the agent an extra request and an extra discovery step), which is why it scores 40 instead of 100.
Labeled text/markdown but the body is an HTML document
Your server sets Content-Type: text/markdown but returns the same HTML document. Relabeling is not converting: agents parse the body expecting Markdown and get markup soup. This usually means a misconfigured rewrite rule or a middleware that changes the header without transforming the payload.
<!doctype html>, <html>, or <head>.Markdown response body is empty
The negotiation works (correct Content-Type) but the body is empty. Common causes: the Markdown renderer receives no content for the route, an edge function returns early, or a converter fails silently on your HTML structure. Test the exact response:
curl -s -H "Accept: text/markdown" https://your-site.com | wc -c # Expected: > 0
Vary header does not include Accept
When one URL serves two representations, every cache between you and the client needs to know the response depends on the Accept header. Without Vary: Accept, a CDN may cache the Markdown variant and serve it to browsers, or cache the HTML and serve it to agents, silently undoing your negotiation.
# Nginx
add_header Vary "Accept" always;
# Express
res.set('Vary', 'Accept');
# Vercel (vercel.json)
{
"headers": [{
"source": "/(.*)",
"headers": [{ "key": "Vary", "value": "Accept" }]
}]
}Vary: Accept fragments your cache by every distinct Accept value browsers send. If hit ratio matters, normalize the header at the edge (e.g., a Cloudflare worker that collapses Accept to two canonical values: markdown vs HTML).Markdown response is not smaller than the HTML representation
The whole point of serving Markdown is token efficiency: Cloudflare measured a typical page at ~80% fewer tokens. If your Markdown is as large as your HTML, the converter is probably including navigation menus, footers, cookie banners, or raw markup remnants. Strip boilerplate and emit only the main content. This finding is informational and does not affect the check score.
Could not fetch the homepage with Accept: text/markdown
The probe request failed at the network level: timeout, connection refused, or TLS failure. If the regular audit checks pass, this usually means a WAF rule rejects requests with unusual Accept headers. Reproduce with:
curl -v -H "Accept: text/markdown" https://your-site.com -o /dev/null
Check your WAF / bot-management logs for the blocked request and add an exception for the Accept: text/markdown pattern.
The Markdown has no frontmatter
You negotiate correctly and return real Markdown, and the response arrives with no provenance. Stripping the HTML also stripped the canonical link, the title tag and the metadata, so an agent that quotes this page cannot say where the quote came from.
--- title: How content negotiation works canonical_url: https://example.com/guides/content-negotiation last_updated: 2026-09-01 description: Serving Markdown to agents from the same URL as the HTML. --- # How content negotiation works ...
title and canonical_url are the two that matter: together they make the response attributable. Add last_updated where you have it, since an agent has no other way to judge whether what it is reading is current.
The frontmatter carries nothing useful
A YAML block is present but holds none of title, canonical_url, url, last_updated or description. Build metadata, template names and layout keys are not wrong to include, but they answer a question nobody asked.
This is normally a generator emitting its own front matter verbatim. Map the fields the page already has (its title, its canonical URL, its modified date) into the block, and keep whatever else you were emitting alongside them.