Markdown for Agents

Cloudflare shipped Markdown for Agents today. Any site on their network can now serve markdown instead of HTML through content negotiation. One header. No origin changes. This matters if you’re building AI agents on Cloudflare because it eliminates an entire step in the pipeline.

I’ve been converting HTML to markdown in my agent for months using the Browser Rendering REST API. It works. But now there’s a faster path for a growing chunk of the web.

What my agent does today

My agent runs on Durable Objects. When it needs to read a webpage, it calls the Cloudflare Browser Rendering REST API markdown endpoint. One authenticated fetch from the DO. No Puppeteer. No container. No CDP proxy.

// Current approach. Call Browser Rendering to convert any URL to markdown.
const apiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.CF_ACCOUNT_ID}/browser-rendering/markdown`;
const result = await fetch(apiUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${env.CF_API_TOKEN}`,
  },
  body: JSON.stringify({ url }),
});
const markdown = await result.json();

This spins up a headless browser on Cloudflare’s edge, loads the page, strips the HTML, and returns clean markdown. It handles JavaScript rendered pages, SPAs, anything a real browser would render. Good for arbitrary URLs where you don’t control the source.

But it’s doing work that shouldn’t always be necessary. Cloudflare already has the HTML sitting on its network before it reaches the browser. If the content is static or server rendered, spinning up a headless browser just to strip tags is overkill.

What Markdown for Agents changes

Now any Cloudflare site with the feature enabled can serve markdown directly. The agent sends an Accept header and the edge converts the HTML before it ever reaches my code.

// New approach. Ask the source for markdown directly.
const response = await fetch(url, {
  headers: {
    Accept: 'text/markdown, text/html',
  },
});
const contentType = response.headers.get('content-type');
const tokens = response.headers.get('x-markdown-tokens');
const markdown = await response.text();

No API key. No separate endpoint. No headless browser. The conversion happens at the network level during the response. The x-markdown-tokens header tells you the estimated token count before you process the body.

Cloudflare tested it on their own blog. The HTML version of the announcement post is 16,180 tokens. The markdown version is 3,150. That’s 80% fewer tokens for the same content.

The comparison

ConcernBrowser Rendering APIMarkdown for AgentsRaw HTML
HopsDO to REST API to headless browserDO to edge to originDO to origin
ConversionHeadless browser renders then stripsEdge converts on the flyNone. You get raw HTML
JavaScript renderingYes. Full browserNo. Static HTML onlyNo
Token costLow. Clean markdownLowest. Source controlled markdownHigh. All the tags and scripts
Compute costBrowser Rendering billingZero. Part of the responseZero but you pay in tokens
Works on any URLYesOnly Cloudflare sites with it enabledYes
Auth requiredCF API tokenNoneNone

Neither approach replaces the other. They cover different cases.

Smart path selection

The useful thing is that content negotiation tells you what happened. If the site supports markdown, you get content-type: text/markdown. If it doesn’t, you get text/html back. I’m adding a fallback to my agent that tries the free path first and drops to Browser Rendering when needed.

async function fetchAsMarkdown(url: string, env: Env): Promise<AgentContent> {
  // Try content negotiation first. Zero compute cost if the site supports it.
  const response = await fetch(url, {
    headers: {
      Accept: 'text/markdown, text/html',
    },
  });

  const contentType = response.headers.get('content-type') || '';

  if (contentType.includes('text/markdown')) {
    // Site served markdown directly. Use it.
    const tokens = parseInt(response.headers.get('x-markdown-tokens') || '0');
    return {
      content: await response.text(),
      tokens,
      source: 'content-negotiation',
    };
  }

  // Site returned HTML. Fall back to Browser Rendering.
  const apiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.CF_ACCOUNT_ID}/browser-rendering/markdown`;
  const result = await fetch(apiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${env.CF_API_TOKEN}`,
    },
    body: JSON.stringify({ url }),
  });

  const data = await result.json() as { content: string };
  return {
    content: data.content,
    tokens: estimateTokens(data.content),
    source: 'browser-rendering',
  };
}

Two paths. One function. The agent tries the free path first and falls back to the compute path when needed. For Cloudflare powered sites with the feature enabled, this skips the Browser Rendering call entirely.

Token budget with x-markdown-tokens

The x-markdown-tokens header is more useful than it looks. My agent manages context windows across multi turn conversations. Knowing the token count of a fetched page before processing it means the agent can decide whether to include the full content, summarize it, or chunk it. I’m wiring this into my context window management.

async function shouldIncludeFullContent(
  response: Response,
  remainingBudget: number
): Promise<'full' | 'summarize' | 'skip'> {
  const tokens = parseInt(response.headers.get('x-markdown-tokens') || '0');

  if (tokens === 0) return 'full'; // Header missing. Process and count later.
  if (tokens < remainingBudget * 0.3) return 'full';
  if (tokens < remainingBudget * 0.8) return 'summarize';
  return 'skip';
}

Without this header the agent has to download the full content, count the tokens, then decide. With it the agent makes the decision from the headers alone. Small optimization per request. Adds up across a conversation with multiple web fetches.

What this means for cost

My agent already runs on per request pricing through Durable Objects. The infrastructure cost for a conversation with 10 message exchanges runs about $0.02 to $0.08 before AI provider costs. Browser Rendering adds to that when the agent fetches web content.

For sites that support Markdown for Agents, that Browser Rendering cost drops to zero. The markdown comes back as part of a normal fetch. No separate API call. No headless browser billing.

The savings per individual request are small. But an agent that fetches 5 to 10 URLs per conversation across 50 conversations a day starts to see the difference. More importantly it’ll be faster. No browser spin up time. The markdown arrives with the response.

What this doesn’t solve

Markdown for Agents only works for sites on Cloudflare that have the feature toggled on. Today that’s a fraction of the web. The Browser Rendering API still handles everything else. JavaScript heavy SPAs, sites behind authentication, pages that require a real browser to render. Content negotiation won’t help there.

It also only converts static HTML. If the meaningful content on a page is rendered client side, the edge conversion won’t capture it. You still need Browser Rendering or Puppeteer for those cases.

The direction

The web is splitting into two audiences. Humans get HTML with styling and scripts and interactive elements. Agents get markdown with metadata and token counts. Cloudflare is positioning their network as the translation layer between the two.

For anyone building agents on their platform this is free infrastructure. Toggle it on for your own sites. Use content negotiation when fetching from other Cloudflare sites. Fall back to Browser Rendering for everything else.

I’m adding the smart path selection to my agent. About 20 lines. The agent won’t care which path it takes. It just gets clean markdown either way. Now it’ll get it faster and cheaper for a growing portion of the web.