A teardown of the most-copied developer portal on the internet, and the parts of it you can actually ship this week.
TLDR: We took apart Stripe's developer docs to find out what a nine-figure engineering budget buys you. Answer: most of it is copyable. This is a teardown of how they generate, version, and serve docs to both humans and AI agents, plus eight things you can ship this week without their team size.
Scroll to the bottom of any page on Stripe's documentation and you will find a line that explains most of what they have been building for the last two years. It reads: "LLM? Read llms.txt."
Next to it: "Powered by Markdoc."
Five footer links, and two of them are the whole strategy.
Those two lines are the whole thesis. Stripe's docs are generated from a machine-readable source, and they are served to machines on purpose. Almost everything worth copying follows from that, and almost none of it requires Stripe's budget.
Three things follow from that, and we'll come back to each of them. Stripe never makes you pick between your developer and the agent reading on their behalf. It treats a first-time visitor differently from someone on their hundredth API call. And it draws a hard line between what a machine proposes and what a human ships. Underneath all three is the question of how much of this you can actually copy without Stripe's headcount.
The three-column reference, and the detail everyone misses
Start with the shape, because it is the part that gets imitated most and it is the least interesting.
The shape gets copied. The part underneath it, a working API key handed to a stranger before signup, is the part that actually matters.
The API reference is three columns. Resource navigation on the left. Prose and the attribute list in the center. A sticky code panel on the right, which on endpoint pages splits into a dark request panel and a light RESPONSE panel with the full JSON directly beneath it. Nine languages in the switcher, and the second entry after cURL is the Stripe CLI, which quietly reframes the CLI from a side tool into a way to call the API.
Request on top, response directly beneath it, and the whole thing sticky while you scroll the parameters.
Putting the CLI second in the list is a positioning decision disguised as a dropdown.
Now the detail that almost never makes it into a teardown. Load the create-a-charge page signed out, in a private window, having never touched Stripe. The cURL sample already contains a working sandbox secret key. So does the API keys card on the docs home page.
Note the "Sign in" button. Those keys are there before you have an account.
A stranger can copy a curl command out of Stripe's documentation and run it. No signup, no account, no email. Stripe decided that requiring an account before a first successful request was a cost they were not willing to pay, and then they went further: their AI guidance page documents stripe sandbox create, which provisions an anonymous, claimable sandbox with working keys and a seven-day claim window.
Signup is not on the critical path to a 🟢 200. That is a product decision, not a docs decision, and it is the single most consequential thing on this page.
Why the reference looks generated: because it is
A missing doc string fails the build. Not a ticket, not a backlog item. The build.
Stripe engineers published the whole pipeline in June 2026, and it is the best thing written about docs infrastructure this year. Mikael Mello and technical writer Anna Mason wrote up how API changes flow into Stripe's developer products, and the honesty in it is what makes it useful.
The short history: OpenAPI internally since 2016, published publicly in 2017, first auto-generated SDK in 2019. Then it broke. Their 2024 v2 namespace used a separate internal spec format, and the consequences were exactly what you would expect. In their words, SDK and reference generators "had to be duplicated," CLI preview support for v2 APIs was "shut down entirely because the maintenance burden was too high," and the Shell "didn't support v2 APIs at all." The API reference had historically bypassed OpenAPI and parsed their internal Ruby DSL directly.
The fix was convergence. One OpenAPI description, extended with vendor extensions, generating per-consumer artifacts controlled by three parameters: which surface, which phase, which version.
You can see the result in the UI if you know where to look. The Expandable badge next to an attribute on the charge object page is rendered from a spec extension called x-expandableFields. A line in a YAML file becomes a badge on a page. So does x-stripeAccess, and x-stripeEvent, and the rest.
Nobody typed that badge. It is one field in the spec, and it explains itself on hover.
And then the governance line, which is the one to bring to your next planning meeting. Documentation and code samples "are authored and validated alongside the API definition itself," and a lint rule named empty_doc_string requires that "every public API method, event, error, field, and enum value should have a doc string."
Every page exists three times
Here is where it gets genuinely novel, and this is the part we had not seen written up anywhere.
Append .md to any Stripe docs URL and you get clean markdown. docs.stripe.com/webhooks.md works, and Stripe documents the behavior along with why: fewer tokens, hidden content included, hierarchy preserved.
Same page, same content, three characters of difference in the URL.
That is two renditions. There is a third. Request the ordinary HTML URL with a bot user-agent and Stripe serves markdown too, with a preamble injected that the .md file itself does not contain. The preamble tells the agent to install the CLI and run stripe agent setup.
Read that again. Stripe is running an onboarding funnel inside its responses to crawlers. The agent reading the page is treated as a visitor who can be converted, and it is handed an install command.
Whether you find that clever or slightly unnerving, it is a real answer to a question every docs team now has: what does a machine get when it asks for your page? Stripe's answer is a different, better, deliberately shaped document.
Worth noting what is not there, since it gets assumed. docs.stripe.com/llms-full.txt returns a 404. They do not publish one.
The strangest file on the internet, and it is doing developer relations
A plain text file at a public URL, telling models what to recommend and what to stop recommending.
Stripe's llms.txt is about 90KB and 443 links, organized by product. Buried between the Architecture and Billing sections is a heading that stops you:
Instructions for Large Language Model Agents: Best Practices for integrating Stripe
What follows is roughly 1,900 words of editorial policy addressed to models. Not links. Directives:
Prioritize the Checkout Sessions API… and never recommend the Charges API.
Never recommend the legacy Card Element or the Payment Element in card mode, and advise users who ask for the Card Element to migrate.
ALWAYS use the Accounts v2 API for new integrations… Don't recommend the outdated terms Standard, Express, or Custom as account types.
Understand what this is. Every model in production learned Stripe from years of blog posts, Stack Overflow answers and old tutorials, most of which recommend APIs Stripe has since moved past. Training data does not get patched. So Stripe wrote a correction file and put it where the agents will find it.
That is developer relations aimed at models, and as far as we can tell almost nobody else is doing it yet. Your product has three or four things that models consistently get wrong about it. Writing them down costs an afternoon.
The MCP server at mcp.stripe.com is the same idea in a different shape, with a tool named search_stripe_documentation sitting alongside the API tools, and a published skills catalogue at a .well-known path. Stripe also writes its own security caveat into the MCP docs, recommending human confirmation of tools "to avoid prompt injection attacks," which is more candor than this category usually manages.
Versioning, treated as a product
Underneath the whimsy, the discipline is the point.
Stripe names API releases after flowers, in alphabetical order. Acacia, Basil, Clover, and currently Dahlia. Version strings look like 2026-08-26.dahlia. Each major release page opens with a short paragraph about the actual plant, which is the kind of thing that costs nothing and makes a version number something people can say out loud in a meeting.
The changelog shows a general availability version and a paired preview version shipping every single month, back through 2025 without a gap. Each release breaks down by product, and one of the columns is Breaking change?, answered per row.
Generally available and Public preview are tabs, not footnotes. Every row below is its own URL.
Every individual change gets its own URL. Not an anchor. A page, at a path like /changelog/dahlia/2026-08-26/adds-frozen-fields-property-to-invoice-items. That makes a single API change linkable in a support thread, citable by an agent, and indexable on its own.
The upgrade guide enumerates what Stripe considers backward-compatible, down to reordering response properties and adding prefixes to opaque ID strings, and it tells you to store IDs as VARCHAR(255). Then the detail that shows someone thought about the person doing the upgrade at 4pm on a Thursday: for 72 hours after upgrading, you can roll back, and webhooks that failed with the new object structure get retried with the old one.
Where it is not perfect
A teardown that finds nothing wrong is an advertisement. Here's what's actually missing.
We could not find a dedicated deprecations index. Deprecation is handled in three separate places instead: a badge on the method page, the Breaking change? column in the changelog, and the instructions in llms.txt. If you want to know everything Stripe is moving away from, you assemble that yourself.
The AI suggestions in search are hit and miss. Searching "webhook signing" returned a solid results list with breadcrumb trails and highlighted terms, and then an Ask AI Assistant block offering three follow-up questions about a SignInView component, which is not what anyone searching for webhook signing wants.
The results are good. The three suggested questions underneath them are about something else entirely.
And the docs home page routes primarily by product, with a small task layer sitting above it. That is a defensible choice at Stripe's surface area. It also means "I want to do a thing" resolves to "pick which of our thirty products owns that thing" more often than it should.
The task layer is short, but it is a real shell. You can run the command from the page.
What you can copy on Monday
Short on time? Start here, this is the payoff section.
None of this requires the pipeline. Some of it is one route and one CI check. Ranked by what you get for the effort:
- Serve markdown at every doc URL. One route. Stripe's own stated payoff is fewer tokens, hidden content included, hierarchy preserved. Half a day of work.
- Put a "Copy for LLM" button next to every heading, copying that section's markdown. It is the cheapest possible signal that you take machine readers seriously, and Stripe has it on every section of every page.
- Write the instructions section in your
llms.txt. Not a link list, a policy. Never recommend X, always default to Y, migrate anyone asking for Z. Treat the file as control over what agents see rather than as a ranking lever, because the evidence for the second thing is not there yet. - Put "LLM? Read llms.txt." in your footer. One line of HTML.
- Make a missing doc string fail the build. You do not need Stripe's generator to add one CI check that rejects a public field with no description.
- Give every changelog entry its own URL, and add a
Breaking change?column. Pure information architecture, no engineering. - Ask "was this section helpful?" per section rather than per page. Same widget, and it tells you which paragraph is broken instead of which page.
- Ship a working sandbox key in your samples for logged-out visitors, and make
/open search with the keycap rendered in the input.
What we would not try to copy: the OpenAPI variant generator, nine languages of auto-generated samples, and the dashboard tooling. Those are the parts that need Stripe's headcount, and imitating them badly is worse than not imitating them.
The argument underneath all of it
Every choice on this list points the same direction. A working key before signup. A doc string that fails CI. A markdown rendition for the reader who is not a person. A version number you can pronounce.
That is the argument we make constantly at ReadMe, and Stripe is the cleanest public proof of it. Documentation is the UI for your API. It is the interface between a person and your code, and Stripe treats it like an interface: instrumented, versioned, tested, and shipped on a schedule. Docs shouldn't just be paragraphs of text, and at Stripe they mostly aren't. They are a generated artifact of the API itself, which is why they stay correct.
Three things follow from that, and they are why we build what we build.
Two audiences, one source
You don't have to choose between the developer and the agent reading your docs.
Static API docs weren't built for an agentic world, and the fix is not a separate microsite for the robots. It is one source that renders for the developer reading a getting-started guide and for the agent parsing your spec at 3am. Stripe does this with a build step. Your hub should do it with a checkbox: an interactive API Reference and Try It! for the person, LLMs.txt and an MCP Server for the machine, generated from the same OpenAPI file.
The first visit and the hundredth visit
A developer starting out and a developer debugging at 2am want completely different things from you.
A developer starting out wants a getting-started guide and their API key, which is exactly what Stripe hands a signed-out stranger. A developer on their hundredth call at 2am wants to know what changed and whether it was them, which is what a per-change changelog URL answers, and what the Developer Dashboard and real-time API logs answer on our side. Behind every one of those requests is a real person having a bad afternoon.
The machine proposes, the human decides
AI should catch the gap. It shouldn't be the one who ships.
Notice that Stripe's empty_doc_string rule doesn't write the description. It refuses to ship without one. That is the shape we think AI belongs inside docs: the AI Linter catches the gap, AI Branch Reviews open a branch with a suggestion, and a person merges it. AI doesn't publish to your hub.
The tradeoff, named honestly
Here is the tradeoff we took, so you can weigh it against the one Stripe took. Stripe built the pipeline, and they have the headcount to keep it. ReadMe is a hosted platform, which means you give up some control of the rendering layer you would keep if you ran your own static site generator, and you get the reference, the analytics, the machine renditions and the governance without staffing any of it. If your docs are one engineer's markdown folder and that genuinely works, keep the folder. The moment two audiences and an audit trail enter the picture, the folder stops scaling and the pipeline is the expensive answer.
The parts of Stripe's list you can copy this week are mostly the cheap parts. That is the good news. The rest is a platform problem, and it is the one we spend our days on.
Next up in this series: another public developer hub. Have one you want us to take apart? Let us know!
Happy Documenting!