Validates HTML for browsers and markdown for AI via Accept negotiation
The auditor issues two GET requests to the audited URL (following redirects, with HTTP Basic Auth when provided):
Browser request β sends Accept: text/html and expects a Content-Type of text/html or application/xhtml+xml.
AI-client request β sends Accept: text/markdown and expects a Content-Type of text/markdown or text/x-markdown.
Body sanity check β when the markdown response declares a markdown media type, the body must not start as a full HTML document (<!DOCTYPE html or <html).
Aggregate β any failing rule makes the audit FAIL; when every rule passes, the audit is SUCCESS.
Media type parameters such as ;charset=UTF-8 are ignored when comparing content types. An unreachable URL produces an ERROR response; a non-200 status on either request produces a FAIL without evaluating the rules.
Every row maps to one checkRule* method and one CheckCode.
| CheckCode | Status | When it fires | Recommendation |
|---|---|---|---|
37A-MarkdownContentAuditor-400 | FAIL | The request with Accept: text/html returns a missing or blank Content-Type header. | Send Content-Type: text/html for {url} when the client asks for HTML. |
37A-MarkdownContentAuditor-401 | FAIL | The request with Accept: text/html is answered with a markdown media type (text/markdown or text/x-markdown). | Serve HTML to browsers: set Content-Type to text/html on {url} for Accept: text/html instead of {contentType}. |
37A-MarkdownContentAuditor-402 | FAIL | The request with Accept: text/html is answered with a Content-Type that is present, not markdown, and not text/html or application/xhtml+xml. | Serve {url} as text/html when Accept is text/html; current Content-Type is {contentType}. |
37A-MarkdownContentAuditor-403 | FAIL | The request with Accept: text/markdown returns a missing or blank Content-Type header. | Send Content-Type: text/markdown for {url} when the client sends Accept: text/markdown. |
37A-MarkdownContentAuditor-404 | FAIL | The request with Accept: text/markdown is still answered with an HTML media type (text/html or application/xhtml+xml), meaning the server does not negotiate markdown. | Negotiate markdown: serve {url} as text/markdown (or text/x-markdown) when Accept is text/markdown; current Content-Type is {contentType}. |
37A-MarkdownContentAuditor-405 | FAIL | The request with Accept: text/markdown is answered with a Content-Type that is present, not HTML, and not text/markdown or text/x-markdown. | Serve {url} as text/markdown when Accept is text/markdown; current Content-Type is {contentType}. |
37A-MarkdownContentAuditor-406 | FAIL | The markdown response declares a markdown media type but the body starts as a full HTML document (<!DOCTYPE html or <html). | Return markdown source at {url} for Accept: text/markdown; the response labeled {contentType} currently begins with HTML. |
37A-MarkdownContentAuditor-200 | SUCCESS | Every rule above passed; one SUCCESS check is added with the message "{url}" serves HTML ({htmlContentType}) by default and markdown ({markdownContentType}) when Accept is text/markdown. | β |
| Status | Description | Test logic |
|---|---|---|
| SUCCESS | The URL serves HTML by default and markdown through content negotiation | Accept: text/html returns text/html or application/xhtml+xml; Accept: text/markdown returns text/markdown or text/x-markdown with a body that does not start as an HTML document. Both requests return HTTP 200. |
| FAIL | Content negotiation is missing, inverted, or mislabeled | Missing Content-Type on either request (400, 403); markdown served to browsers (401); non-HTML default (402); HTML still served when markdown was requested (404); a non-markdown type on the markdown request (405); a markdown-labeled response whose body is HTML (406); or a non-200 status on either request. No rule in this auditor emits WARNING. |
| ERROR | The URL could not be fetched | The host is unreachable or the request fails before a response is received. |
Agents and LLM crawlers that request text/markdown and receive a full HTML page must parse markup, scripts, and navigation chrome, wasting context window and degrading answer quality.
Serving markdown through Accept negotiation keeps a single canonical URL per page β no separate .md URLs to publish, crawl, or keep in sync.
Content negotiation must never leak markdown to browsers: a markdown response to Accept: text/html renders as plain text and breaks the page for every visitor.
Responses that vary by Accept should include Vary: Accept so caches and CDNs do not serve the markdown variant to browsers or vice versa.
A missing Content-Type forces clients to sniff the payload, which is unreliable and a known security concern.
Declaring text/markdown while returning an HTML body is worse than not negotiating at all: clients trust the label and feed HTML into markdown pipelines.
Add this auditor to your monitoring setup and start identifying issues on your websites today.