feat(agents): serve markdown for versioned api-reference URLs - #4100
feat(agents): serve markdown for versioned api-reference URLs#4100revmag wants to merge 1 commit into
Conversation
Addresses the review on #4067: that PR gave the /api-reference index a markdown twin, but the index is only a version chooser — 840 visible words, zero endpoints. The content lives at /api-reference/<tag>, and those had no markdown at all. Production demand over 30 days, all 404 today: /api-reference.md 17 <- fixed by #4067 /api-reference/v0.139.0.md 9 /api-reference/v0.138.0.md 7 /api-reference/latest.md 4 v0.135.1 / v0.128.0 / v0.122.0 3 each v0.126.1 / v0.136.1 / v0.137.1 1 each ------------------------------------ versioned total 31 <- fixed here Two changes: 1. /api-reference/<tag>.md and /api-reference/latest.md now serve markdown for that release, via /api/api-reference-markdown/<tag>. `.md` is the convention llms.txt advertises for every other page on the site. 2. /api-reference/<tag> with `Accept: text/markdown` returns markdown instead of raw YAML. Answering a markdown request with `text/yaml` is the wrong media type for the request; the spec remains available by asking for YAML explicitly, at /api/api-reference-openapi/<tag>, and at /openapi.{json,yaml}. The YAML rewrite also no longer depends on user-agent sniffing. It required `isBot`, so a browser sending the same Accept header got HTML while curl got YAML — one URL, two answers, keyed on a header no cache varies on. It now keys off Accept alone. getLatestOpenAPISpec is split so getOpenAPISpecForVersion can fetch and parse any release; getLatestOpenAPISpec becomes a thin wrapper. No behaviour change. Two tests in tests/proxy.test.js encoded the old YAML-on-markdown and bot-gated behaviour; they are rewritten to encode the new intent rather than deleted. Verification: 8 new tests; 118 tests pass across proxy, openapi-markdown, agent-markdown-routing, docs-markdown-routing, mcp-discovery, llms-txt, agent-response-headers; lint 0 errors; yarn build succeeds with `ƒ /api/api-reference-markdown/[version]` in the route manifest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @revmag's task in 3m 6s —— View job PR Review: serve markdown for versioned api-reference URLsReviewed against Findings1. Cache correctness — missing 2. 3. Dead fallback (nit) Looks good
Verification noteI did not run
|
| !isAgentMarkdownSelfFetch && shouldRewriteDocsToMarkdown(pathname, prefersMarkdown) | ||
| const apiRefYamlRewrite = shouldRewriteApiReferenceToOpenAPISpec(pathname, prefersMarkdown, isBot) | ||
| const apiRefYamlRewrite = shouldRewriteApiReferenceToOpenAPISpec(pathname, acceptHeader) | ||
| const apiRefVersionMarkdownRewrite = shouldRewriteApiReferenceVersionToMarkdown( |
There was a problem hiding this comment.
Versioned api-reference URLs now content-negotiate but their HTML variant gets no Vary: Accept. (finding anchored here; the affected code is the Vary block below at lines ~180–186, which is outside this PR's diff.)
After this PR, /api-reference/<tag> serves three representations off the same URL keyed on Accept:
- HTML (plain passthrough —
NextResponse.next) - markdown (
Accept: text/markdown→ this new rewrite to/api/api-reference-markdown/<tag>) - YAML (
Accept: *yaml→/api/api-reference-openapi/<tag>)
The two rewrite targets set Vary: Accept themselves (agentResponse({ varyAccept: true }) and the openapi route). But the plain HTML response for /api-reference/<tag> gets no Vary — the block below only covers isDocsPathname, servesMarkdownAlternate, and isApiReferenceIndexPath, and servesMarkdownAlternate explicitly excludes the /api-reference prefix. That's exactly the poisoning case that block's own comment warns about: a shared cache can store the HTML for /api-reference/v0.139.0 and later serve it to an Accept: text/markdown/yaml request (or the reverse).
The index page got Vary in #4067; this PR extends the same negotiation to versioned URLs without extending the Vary. Suggest adding the version path to that condition:
if (
isDocsPathname(pathname) ||
servesMarkdownAlternate(pathname) ||
isApiReferenceIndexPath(pathname) ||
parseApiReferenceVersionPath(pathname) !== null
) {
res.headers.append('Vary', 'Accept')
}(add parseApiReferenceVersionPath to the existing @/utils/apiReferenceMarkdownRouting import).
Pull Request
📄 Summary
The review on #4067 asked why versioned api-reference URLs answer
Accept: text/markdownwith YAML, and pointed out that "the /api-ref page lists all versions and specific version only should be requested".That's correct, and measurably so.
/api-reference/is a version chooser — 840 visible words, zero endpoints. The content is at/api-reference/<tag>/(843 KB, ~47,000 words). #4067 gave the chooser a markdown twin and left all 34 content URLs without one.Production demand over 30 days — every one of these returns 404 today:
/api-reference.md/api-reference/v0.139.0.md/api-reference/v0.138.0.md/api-reference/latest.mdv0.135.1/v0.128.0/v0.122.0.mdv0.126.1/v0.136.1/v0.137.1.mdSlightly more demand for versioned URLs than for the index.
Change 1 —
.mdworks on versioned URLs./api-reference/<tag>.mdand/api-reference/latest.mdserve markdown for that release via/api/api-reference-markdown/<tag>..mdis the conventionllms.txtand every markdown page footer advertise; it worked everywhere except here.Change 2 —
Accept: text/markdownon a version returns markdown, not YAML. Answering a markdown request withtext/yamlis the wrong media type for the request. The spec stays reachable three other ways: ask for YAML explicitly,/api/api-reference-openapi/<tag>, or/openapi.{json,yaml}— so nothing is lost.Change 3 — the YAML rewrite no longer sniffs user-agent. It required
isBot, so curl got YAML and a browser sending the sameAcceptheader got HTML. One URL, two answers, keyed on something no cache varies on. It now keys offAcceptalone.✅ Change Type
🧪 Testing Strategy
Tests added:
tests/api-reference-version-markdown.test.js— 8 tests covering tag parsing (release tags,latest,.mdtwins, trailing slashes), rejection of the index and non-version segments,.mdrewrites, markdown-over-YAML on the Accept header, YAML preserved fortext/yaml/application/yaml/application/x-yaml/application/vnd.oai.openapi, UA-independence, and untouched plain-HTML requests.Tests updated: two tests in
tests/proxy.test.jsencoded the old YAML-on-markdown and bot-gated behaviour. Rewritten to encode the new intent, not deleted — plus a new passthrough test asserting plain HTML requests are unaffected.Suites: 118 tests pass across
proxy(49),api-reference-version-markdown(8),openapi-markdown,agent-markdown-routing,docs-markdown-routing,mcp-discovery,llms-txt,agent-response-headers.yarn lint0 errors.yarn buildsucceeds withƒ /api/api-reference-markdown/[version]in the route manifest.Edge cases:
latestresolution, trailing slashes, non-version segments (/api-reference/not-a-version), nested paths (/api-reference/latest/logs), unknown release tags (404 with the markdown recovery body), and the index never being parsed as a version./api-reference/<tag>only. The index, docs, blog, and every other markdown surface are untouched. Plain HTML requests to versioned pages pass through unchanged — asserted by a test.Accept: text/markdownreturning YAML now get markdown. Three explicit YAML routes remain, and the previous behaviour was gated onisBot, so only bot-UA clients could have depended on it./api/api-reference-openapi/<tag>..mdreturns to 404 and the Accept header returns to YAML.📝 Changelog
.mdto a version URL (signoz.io/api-reference/v0.139.0.md) or sendAccept: text/markdown.📋 Checklist
👀 Notes for Reviewers
getLatestOpenAPISpecis split sogetOpenAPISpecForVersioncan fetch and parse any release;getLatestOpenAPISpecbecomes a thin wrapper over it. No behaviour change, and the existing tests cover it.Deliberately not included: redirecting versioned URLs to
latest. People pin to old releases legitimately —v0.122.0has 107 endpoints against 142 today — so collapsing them would lose real information.🤖 Generated with Claude Code