Raw markdown API
Every documentation page is available as markdown at the same URL with .md appended. You get processed markdown, not MDX source, with components left as tags.
Overview
Append .md to any documentation URL and you get that page as markdown.
curl -s https://opsinjs.pensievelabs.org/health/two-colour-axes.mdThat is the whole API. There is no key, no endpoint, no pagination and no separate documentation site for machines. The twin is the same page, and it cannot fall out of date with the human version because it is produced from the same source at the same time.
Processed markdown, not source
This is the part that matters and the part most sites get wrong.
The twin is not the raw .mdx file. Raw MDX carries imports, and its JSX is
written for this application rather than for a reader; a model handed the source
either ignores half of it or invents what it contained.
The twin is processed: remark has run over the MDX and stringified it back to
markdown, so imports are gone, headings carry explicit ids, and tables and code
blocks arrive as markdown. What processing does not do is render the
documentation components. <StubNotice name="range-bar" status="shipped" …/>
comes out verbatim, attributes and all, and the sentence it renders in the
browser is not in the string. That is measured rather than assumed, because it
is written down in app/_machine/corpus.ts. This surface is built around it
instead of pretending otherwise.
Three consequences worth knowing:
- The tags are the machine-readable part. A component's attributes are its
content:
<ComponentPreview name="range-bar" />names the component,<ContrastReport scope="materials" />names the scope. The values those components draw are generated fromtokens/*.jsonandregistry/catalogue.tsand published separately, at/r/index.jsonand under the Reference section. Nothing on the page is unreachable; it is just not in the prose. - A twin that contains tags says so. Any twin with a
<PascalCase … />in it carries a block quote, between the frontmatter and the body, explaining what the elements are and where their data lives. A page that is not implemented carries a second quote above that one saying so. If you are writing a parser, those quotes are the cue to go and fetch the data rather than errors to report. - Frontmatter is rebuilt, not copied through. The twin carries the authored
fields plus
url,sourceandsection, so a page read in isolation still knows where it came from. On a component pagestatusis the field to trust, and it readsshippedfor a built component. One field is not authored at all: a component page or a screen specimen documents something buildable, and its twin also carriesimplemented, read from the generated index rather than from the MDX, so no edit to the page can contradict it. A guide, a doctrine page or a generated token reference carries noimplementedkey, because it documents nothing that could be built and afalsethere would assert something untrue about a page that is real today. For a per-id answer outside the docs tree, readimplementedon/r/index.json, which is where that field is generated and where it is guaranteed current.
The docs version header
Every twin response carries the docs version on the x-opsinjs-docs-version
header. It is a header and not a frontmatter key, which matters more than it
sounds: a twin saved to a file loses it, so read it at fetch time and store it
beside the body yourself. Without it a cached copy cannot be distinguished from a
current one, and a bug report six months from now cannot say which version of
the documentation an agent read.
Four more headers ride on the same response, and the five of them do not all answer at the same scope. Mistaking one for another is the expensive error, so the scope is worth reading off a table rather than inferring:
| Header | Answers about | Value |
|---|---|---|
x-opsinjs-docs-version | The site | The docs version this build serves |
x-opsinjs-kind | This page | The page's own kind, sent on every twin |
x-opsinjs-status | This page, on a kind: component page only | That component's release phase: planned, shipped or deprecated |
x-opsinjs-implemented | This page, when the page documents a component or a screen; otherwise the system | Whether that subject has source behind it |
x-opsinjs-implemented-count | The system | How many component ids have real source behind them |
A component or screen twin overrides x-opsinjs-implemented with its own
subject's answer, exactly as /r/<id>.json does, so HEAD on a component's
.md reports that one component's source status rather than the system's. A
A planned id would return false there even though the count beside it is not
zero, and every catalogue id is built today so every component twin
returns true. Every other page names nothing buildable, whether
it is a guide, a handbook chapter or a doctrine page, so the header keeps its
system value there, which answers "is anything implemented" rather than "is
this implemented". x-opsinjs-kind is sent on every twin, and it is how you
tell which of the two scopes you were handed: a twin with no x-opsinjs-kind
at all is being served by a build older than this contract, and its
x-opsinjs-implemented is the system answer whatever the page is about.
x-opsinjs-status is a narrower signal and is absent on most twins by design.
A release phase belongs to a component, so only a kind: component page sends
one; its absence means the page is not a component, never that the page is
unfinished. If
you need a per-id answer for something that has no documentation page, ask
/r/<id>.json or the roster at /r/index.json instead of inferring one from a
header.
There is no per-page last-modified date, in the frontmatter or anywhere else. Nothing in the build computes one, and a fabricated date would be worse than an absent one. The version is the only freshness signal this surface offers.
If you cache twins, cache the version with them, and include it when you report a problem.
What has a twin and what does not
Every page under the documentation tree has one. So does the documentation index
itself, at /index.md.
Nothing else does. The playgrounds, the colour browser and the preview host are applications, not documents, and there is no meaningful markdown rendering of an interactive tool. Asking for their twin returns the application.
Verify it worked
You get markdown, not HTML
The first bytes should be a frontmatter delimiter, not <!DOCTYPE.
PascalCase tags are present and explained
Search the output for a < followed by an uppercase letter. On most pages you
will find several, and that is the twin working: read their attributes, and take
the block quote above them as the pointer to where the rendered values are
published. What you should not find is an import statement or a <!DOCTYPE.
Either one means you have raw source or the HTML page rather than the twin.
The status is present
Every component page's twin carries status in frontmatter, and the value is
shipped for a built component. If
a component twin omits status altogether, that is a bug worth reporting: it is
the field that stops an agent generating against something that does not exist. A
twin for any other kind of page carries no status, which is correct rather than
a gap.
Troubleshooting
404 on a .md URL. The path is not a documentation page. Twins exist for
the docs tree and /index.md only.
The twin shows a tag where the page shows a table. Expected. The
documentation components are not rendered into markdown; fetch the values from
/r/index.json or the Reference section. If the human page shows the same
gap, that is a different fault. An empty table there rather than a filled one
means pnpm run generate had not run when the page was built.
The prose differs between the twin and the page. The rendering differs by design; the words should not. Report a wording difference. That invariant is what the whole surface rests on.
I need many pages at once. Do not crawl. Use the shards or the offline bundle, both of which are one request.
Next
- llms.txt and the shards shows how to find the pages worth fetching.
- Offline docs bundle has every twin in one file.
- Machine-readable schemas covers the structured data behind the prose.
llms.txt and the shards
The curated index a model should read first, the four topic shards that exist because context windows are finite, and the size budget that governs them.
MCP server
Point the shadcn MCP server at the opsinjs registry so an assistant can browse and install real items instead of guessing at them.