# Markdown content negotiation for AI agents

> Markdown content negotiation: Agents can request a markup-free version with "Accept: text/markdown", which cuts token cost.

- Source: /checks/markdown-negotiation
- Site: AgenticWebScore
- Language: English

---
Agents can request a markup-free version with "Accept: text/markdown", which cuts token cost.

| Property | Value |
| --- | --- |
| Check id | `markdown-negotiation` |
| Dimension | Content Accessibility (22%) |
| Tier | Recommended — pool 20 pts |
| Weight inside tier | 3 |
| Impact | Critical |
| Maturity | Adopted — A ratified standard with real deployment. |
| Conditional | No |
| Publishing skill | `markdown-negotiation` |
| Standards | RFC 9110 |

## What is measured

The scanner re-requests the homepage `/` with `Accept: text/markdown` and inspects the response headers and body. Pass when the response carries `Content-Type: text/markdown`, the body is markdown rather than HTML (no `<!doctype html>`, no `<div>` scaffolding) and it is materially shorter than the HTML page; partial when markdown is returned under another type such as `text/plain`, or when the response omits `Vary: Accept` so a shared cache can serve the markdown body to a browser; fail when the request still returns `text/html`. RFC 9110 proactive negotiation, one URL with two representations.

## Reproduce it yourself

You do not have to take our word for any of this. Run the same measurement against your own domain:

```bash
DOMAIN="https://example.com"
curl -sSL -D /tmp/md.h -o /tmp/md.body -H 'Accept: text/markdown' "$DOMAIN/"; grep -iE '^(HTTP/|content-type|vary)' /tmp/md.h; head -3 /tmp/md.body
```

## The fix

This is the artifact to ship. It is the complete change, not a description of one.

```javascript
// server.js — serve a prebuilt markdown twin on Accept: text/markdown (RFC 9110).
import express from 'express';
import { readFile } from 'node:fs/promises';

const app = express();

app.use(async (req, res, next) => {
  if (req.method !== 'GET') return next();
  res.vary('Accept'); // both representations share one URL, so caches must key on Accept
  if (req.accepts(['text/html', 'text/markdown']) !== 'text/markdown') return next();
  const file = `./public${req.path.replace(/\/$/, '/index')}.md`;
  try {
    res.type('text/markdown').send(await readFile(file, 'utf8'));
  } catch {
    next(); // no markdown twin for this path: fall back to the HTML document
  }
});

app.use(express.static('public'));
app.listen(3000);
```

> Generate the `.md` twin at build time — converting HTML to markdown on every request is slower and lossy — and keep `Vary: Accept` on the HTML response too.

## Standards referenced

- `RFC 9110`

## Related checks

| Check | Tier | Impact | Same skill |
| --- | --- | --- | --- |
| `headings` — Semantic heading structure | Essential | High | — |
| `meta` — Title and summary metadata | Essential | High | — |
| `jsonld` — JSON-LD structured data | Essential | Critical | — |
| `faq-schema` — FAQ / question-answer schema | Recommended | High | — |
| `breadcrumb` — Breadcrumb / navigation schema | Emerging | Low | — |
| `summary` — Prominent summary block | Recommended | Medium | — |
| `lang` — Language declaration and multilingual access | Recommended | Medium | — |
| `no-js-fallback` — Content that needs no JavaScript | Essential | High | — |
