Skip to content

feat(agents): serve markdown for versioned api-reference URLs - #4100

Open
revmag wants to merge 1 commit into
feat/agent-openapi-discoveryfrom
agent-md/versioned-api-reference-markdown
Open

feat(agents): serve markdown for versioned api-reference URLs#4100
revmag wants to merge 1 commit into
feat/agent-openapi-discoveryfrom
agent-md/versioned-api-reference-markdown

Conversation

@revmag

@revmag revmag commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Pull Request

Stacked on #4067 — targets feat/agent-openapi-discovery, not main. Answers the review comment on that PR. Happy to squash it into #4067 instead if that's easier.


📄 Summary

The review on #4067 asked why versioned api-reference URLs answer Accept: text/markdown with 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:

Requested Count
/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 .md 3 each
v0.126.1 / v0.136.1 / v0.137.1 .md 1 each
versioned total 31 ← fixed here

Slightly more demand for versioned URLs than for the index.

Change 1 — .md works on versioned URLs. /api-reference/<tag>.md and /api-reference/latest.md serve markdown for that release via /api/api-reference-markdown/<tag>. .md is the convention llms.txt and every markdown page footer advertise; it worked everywhere except here.

Change 2 — Accept: text/markdown on a version returns markdown, not YAML. Answering a markdown request with text/yaml is 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 same Accept header got HTML. One URL, two answers, keyed on something no cache varies on. It now keys off Accept alone.


✅ Change Type

  • ✨ Feature
  • 🐛 Bug fix (media type; UA-dependent negotiation)
  • ♻️ Refactor
  • 🛠️ Infra / Tooling
  • 🧪 Test-only

🧪 Testing Strategy

Tests added: tests/api-reference-version-markdown.test.js — 8 tests covering tag parsing (release tags, latest, .md twins, trailing slashes), rejection of the index and non-version segments, .md rewrites, markdown-over-YAML on the Accept header, YAML preserved for text/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.js encoded 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 lint 0 errors. yarn build succeeds with ƒ /api/api-reference-markdown/[version] in the route manifest.

Edge cases: latest resolution, 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.


⚠️ Risk & Impact Assessment

  • Blast radius: /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.
  • Behaviour change: clients that relied on Accept: text/markdown returning YAML now get markdown. Three explicit YAML routes remain, and the previous behaviour was gated on isBot, so only bot-UA clients could have depended on it.
  • New surface: one route handler at 1d revalidate, same caching as /api/api-reference-openapi/<tag>.
  • Rollback: revert. .md returns to 404 and the Accept header returns to YAML.

📝 Changelog

Field Value
Deployment Type Cloud / OSS / Enterprise
Change Type Feature
Description Every released version of the SigNoz API reference is now available as markdown — append .md to a version URL (signoz.io/api-reference/v0.139.0.md) or send Accept: text/markdown.

📋 Checklist

  • Tests added or explicitly not required
  • Manually tested
  • Breaking changes documented (the YAML→markdown switch on the Accept header, above)
  • Backward compatibility considered (three explicit YAML routes preserved)

👀 Notes for Reviewers

getLatestOpenAPISpec is split so getOpenAPISpecForVersion can fetch and parse any release; getLatestOpenAPISpec becomes 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.0 has 107 endpoints against 142 today — so collapsing them would lose real information.

🤖 Generated with Claude Code

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>
@revmag
revmag requested a review from a team as a code owner September 2, 2026 06:01
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
signoz-web Ready Ready Preview Sep 2, 2026 6:04am UTC

Request Review

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished @revmag's task in 3m 6s —— View job


PR Review: serve markdown for versioned api-reference URLs

Reviewed against origin/feat/agent-openapi-discovery using the frontend PR review skill. This is a well-scoped, well-tested change — the getLatestOpenAPISpec/getOpenAPISpecForVersion split is clean reuse, the new route mirrors the existing openapi route, and the tests encode the new intent rather than just deleting the old assertions. One correctness gap and a couple of minor notes.

Findings

1. Cache correctness — missing Vary: Accept on the HTML variant of versioned URLs (medium) — inline
Versioned /api-reference/<tag> now serves HTML and markdown and YAML off the same URL, negotiated on Accept. The two rewrite targets set Vary: Accept, but the plain HTML passthrough does not — the Vary block in proxy.ts (~L180) covers the index but not versioned paths (servesMarkdownAlternate excludes the /api-reference prefix). A shared cache can serve HTML to a markdown/YAML request or vice versa. This is the exact case that block's own comment warns about, and the index got Vary in #4067. Fix is a one-line addition to the condition (suggested inline).

2. .md extension loses to a YAML Accept header (minor)
For /api-reference/<tag>.md sent with Accept: application/yaml, apiRefYamlRewrite is evaluated before apiRefVersionMarkdownRewrite and parseApiReferenceVersionPath strips the .md, so it resolves to YAML. Appending .md is an explicit request for markdown and arguably should win over the header. Very rare in practice; noting for completeness.

3. Dead fallback (nit)
buildApiReferenceVersionMarkdownRewritePath / buildApiReferenceOpenAPISpecRewritePath use version || 'latest', but every caller only invokes them after parseApiReferenceVersionPath(...) !== null, so version is never null. Harmless, just noise.

Looks good

  • New route handler correctly resolves latest, returns the markdown 404 recovery body on miss, and shares the getOpenAPISpecForVersion path — no duplicated fetch/parse logic.
  • Dropping the isBot UA-sniffing from the YAML rewrite is the right call: one URL returning two answers keyed on something caches don't vary on was a genuine bug.
  • Test coverage is thorough (tag parsing, latest, .md twins, trailing slashes, index rejection, nested paths, all YAML media types, UA-independence, HTML passthrough).

Verification note

I did not run yarn build/yarn test in this review; the PR reports 118 passing tests + clean lint/build. The Vary finding is a static/caching-behavior issue not covered by the existing unit tests (they assert rewrite targets, not response Vary headers), so it wouldn't be caught by the current suite.


  • Read changed files & gather context
  • Apply frontend PR review skill
  • Analyze correctness, edge cases, tests
  • Post inline findings & summary
    · branch agent-md/versioned-api-reference-markdown

Comment thread proxy.ts
!isAgentMarkdownSelfFetch && shouldRewriteDocsToMarkdown(pathname, prefersMarkdown)
const apiRefYamlRewrite = shouldRewriteApiReferenceToOpenAPISpec(pathname, prefersMarkdown, isBot)
const apiRefYamlRewrite = shouldRewriteApiReferenceToOpenAPISpec(pathname, acceptHeader)
const apiRefVersionMarkdownRewrite = shouldRewriteApiReferenceVersionToMarkdown(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant