Serving markdown to AI agents from Cloudflare Workers with Accept: text/markdown

Paid Cloudflare zones can serve Accept: text/markdown with Markdown for Agents. Workers Free and Paid can convert HTML with env.AI.toMarkdown().

A Cloudflare site serves markdown to an AI agent when the request Accept header includes text/markdown. Markdown for Agents does that conversion at the edge on Pro, Business, and Enterprise zones, and for SSL for SaaS customers. A Worker on the Workers Free or Workers Paid plan can convert the HTML itself with env.AI.toMarkdown().

Key facts

  • Cloudflare published Markdown for Agents on 2026-02-12. On 2026-02-16 it raised the origin response limit from 1 MB to 2 MB, 2,097,152 bytes, stopped requiring Content-Length from the origin, and started converting content-encoded origin responses.
  • Markdown for Agents is included at no extra charge on Pro, Business, and Enterprise plans, and for SSL for SaaS customers. That availability list does not include the Free zone plan.
  • Workers AI, including env.AI.toMarkdown(), is included on the Workers Free and Workers Paid plans. The toMarkdown overview says most format conversions are free. Image conversion can call Workers AI models and may spend Neurons past the daily free allocation. The HTML path strips script and style tags and does not name a model.
  • Workers AI includes 10,000 Neurons per day at no charge on Workers Free and Workers Paid. Usage above that on Workers Paid costs $0.011 per 1,000 Neurons. The pricing page says it was last updated on 2026-10-01.
  • Cloudflare's 2026-02-12 announcement measured that post at 16,180 tokens as HTML and 3,150 tokens as markdown, and called the difference an 80% reduction.
  • On 2026-02-19, Checkly had seven coding agents fetch https://httpbin.org/headers. Claude Code v2.1.38, Cursor 2.4.28, and OpenCode 1.2.5 sent Accept: text/markdown. Codex 260203.1501, Gemini CLI 0.28.2, GitHub Copilot on GPT-5.2-Codex, and Windsurf 1.9552.21 did not.
  • On 2026-04-17, Cloudflare Radar said markdown content negotiation passed on 3.9% of sites. The scan started from the 200,000 most visited domains, then dropped categories such as redirects, ad servers, and tunneling services. The post says the chart updates weekly.

How Accept: text/markdown content negotiation works

HTTP content negotiation lets a client name a preferred media type in the Accept header. An agent that sends Accept: text/markdown is asking for markdown. A converted response uses Content-Type: text/markdown; charset=utf-8 and Vary: Accept, so a cache can store the HTML and markdown responses separately. Markdown for Agents also sends x-markdown-tokens and x-original-tokens.

This request asks https://insidetheloop.dev/ for markdown:

curl -i -H 'Accept: text/markdown' https://insidetheloop.dev/

At 2026-10-05 22:07:52 GMT the response headers that describe the negotiation, and the full 824-byte body, were:

HTTP/2 200
content-type: text/markdown; charset=utf-8
content-length: 824
link: </llms.txt>; rel="describedby"; type="text/plain", </sitemap.xml>; rel="describedby"; type="application/xml", </rss.xml>; rel="alternate"; type="application/rss+xml"
vary: Accept
x-markdown-tokens: 206

---
description: Field notes on agentic anything: Claude Code, Codex, Cursor and whatever ships next.
title: Inside the Loop
---

[Oct 5, 20266 minHow AI agents fetch web pages: user agents, IP ranges, and fetch originsAI crawlers fetch from vendor datacenters using published IP ranges, while CLI coding agents fetch directly from developer workstations via local IPs.](/posts/how-ai-agents-fetch-web-pages-user-agents)

[Oct 5, 20266 minHow to pass every isitagentready.com check for a content sitePassing the seven isitagentready.com content checks requires a valid robots.txt, sitemap, Link headers, markdown negotiation, Content Signals, and DNS-AID.](/posts/isitagentready-content-site-checks)

{"@context":"https://schema.org","@type":"WebSite","name":"Inside the Loop","url":"https://insidetheloop.dev"}

toMarkdown joined each post card's date, reading time, title, and excerpt into one link. The same URL without Accept: text/markdown returned a 22,739-byte HTML body in that same second. 824 / 22,739 is 3.62% of that HTML body.

Cloudflare Markdown for Agents versus Workers AI toMarkdown

Cloudflare documents two ways to answer Accept: text/markdown. Markdown for Agents is a zone setting. env.AI.toMarkdown() is a Workers AI method the Worker calls itself. The Markdown for Agents docs point HTML cleanup at the same Workers AI pre-processing page: drop script and style, pull title, description, and image meta tags into YAML front matter, and append JSON-LD in one json code block.

Feature

Markdown for Agents

Workers AI env.AI.toMarkdown()

Plan

Pro, Business, Enterprise, and SSL for SaaS

Workers Free and Workers Paid

Setup

Dashboard toggle or a configuration rule

Worker code and an ai binding

Origin size

2,097,152 bytes since 2026-02-16

No separate HTML byte cap in the toMarkdown docs

Token headers

x-markdown-tokens and x-original-tokens

doc.tokens, which this Worker copies to x-markdown-tokens

Price

Included at no extra charge on those zone plans

Free for most formats. Image conversion can spend Neurons

Cache headers

Keeps origin cache headers and adds Vary: Accept

Whatever headers the Worker sets

Implementing toMarkdown in a Cloudflare Worker

This site declares the Workers AI binding in wrangler.jsonc:

"ai": {
  "binding": "AI"
}

src/worker.ts in this site checks the HTML response, and on Accept: text/markdown sends the body to env.AI.toMarkdown(). If conversion fails, the same function returns the HTML response. LINK is the site's Link header value, and CONTENT_SIGNAL is the robots.txt content-signal line.

const CONTENT_SIGNAL = "Content-Signal: search=yes, ai-input=yes, ai-train=yes";
const LINK = '</llms.txt>; rel="describedby"; type="text/plain", </sitemap.xml>; rel="describedby"; type="application/xml", </rss.xml>; rel="alternate"; type="application/rss+xml"';

async function withAgentSignals(request: Request, url: URL, res: Response, env: Env): Promise<Response> {
	if (url.pathname === "/robots.txt" && res.ok) {
		const body = (await res.text()).replace(/^(User-agent: \*\s*\n)/m, `$1${CONTENT_SIGNAL}\n`);
		return new Response(body, res);
	}
	if (!(res.headers.get("content-type") ?? "").startsWith("text/html")) return res;
	// Markdown for agents: Accept: text/markdown gets the page converted by Workers AI.
	if (res.ok && (request.headers.get("accept") ?? "").includes("text/markdown")) {
		try {
			const html = await res.clone().text();
			const [doc] = await env.AI.toMarkdown([{ name: "page.html", blob: new Blob([html], { type: "text/html" }) }]);
			if (doc && doc.format === "markdown") {
				const headers = new Headers({ "content-type": "text/markdown; charset=utf-8", vary: "Accept", link: LINK });
				if (doc.tokens) headers.set("x-markdown-tokens", String(doc.tokens));
				return new Response(doc.data, { status: 200, headers });
			}
		} catch {
			// fall through to HTML
		}
	}
	const out = new Response(res.body, res);
	out.headers.append("Link", LINK);
	out.headers.append("Vary", "Accept");
	return out;
}

How Cloudflare Docs serves agents that omit Accept

Checkly's test on 2026-02-19 found that four of the seven agents it tried did not send Accept: text/markdown. Cloudflare's docs, described in the 2026-04-17 Agent Readiness post, add a URL fallback so those clients still get markdown:

  1. A URL Rewrite Rule matches a path ending in /index.md and rewrites it to the page path.
  2. A Request Header Transform Rule matches the original path, raw.http.request.uri.path, and sets Accept: text/markdown.

The docs llms.txt files link to those /index.md URLs. A request for /index.md then returns markdown even when the client sends no Accept preference.

Measured size of HTML and markdown

At 2026-10-05 22:07:52 GMT, https://insidetheloop.dev/ returned a 22,739-byte HTML body. The Accept: text/markdown response was 824 bytes, 3.62% of that HTML body, and the x-markdown-tokens header was 206.

Cloudflare's 2026-02-12 announcement used a different page, that blog post itself: 16,180 HTML tokens and 3,150 markdown tokens. Cloudflare called that an 80% reduction.

Sources

Last verified: 2026-10-06.

Spotted an outdated or wrong claim? Agents can report it with evidence throughPOST /api/feedback; an editor checks every report. See llms.txt for the agent API.