API versioning: The cost that lands in your documentation

Oluwatise Okuwobi
Content Marketing Manager
The v2 proposal has a sprint estimate. Three sprints for the resource model changes, one for auth, two for the SDKs. Nobody costed the documentation, because documentation isn't a line item. It's assumed to fall out of the work.
Then v2 ships and you own two of everything. Two references. Two quickstarts. Two auth guides. Two sets of code samples in every SDK language you publish.
One of them has somebody's attention.
Eight months later a developer still on v1 follows a code sample that was accurate in March.
API versioning gets treated as a choice between URL schemes. That decision takes an afternoon. The one that takes years is how many versions you can afford to keep true, and almost nobody puts a number on it before saying yes to v2.
What is API versioning?
API versioning is how you ship a change that would break existing integrations without actually breaking them, by letting old and new behavior coexist under different identifiers. A developer who integrated last year keeps getting last year's behavior until they decide to move.
The distinction most teams blur is which changes need this at all. Additive changes don't. A new optional field, a new endpoint, an enum value existing clients can ignore: none of those break a contract, and shipping them behind a new version buys you nothing.
Versioning exists for the changes that do break one: removing a field, renaming it, making an optional parameter required, or changing what a value means while its name stays the same. That last category is the dangerous one, because nothing in the request or response looks different and every integration keeps running while producing wrong results.
Teams that cut a new version every release have confused a release process with a compatibility strategy, and they pay the full cost of versioning for changes that never needed it.
API versioning strategies compared
Five schemes are in common use and most of them work. The choice matters less than the discipline behind it, with one exception that only shows up later, in your docs.
Scheme | Example | Cacheable | What it does to your docs |
|---|---|---|---|
URI path |
| Yes | Clean split. |
Query parameter |
| Awkward | Version rides along on every example. Reference pages have no natural per-version address. |
Custom header |
| Needs | Invisible in URLs. Every sample has to show the header, and docs need a switcher widget because the address can't carry the version. |
Content negotiation |
| Needs | As above, plus a media type most developers have to look up before they can read your examples. |
Date-based |
| Yes | Version is a property of the caller rather than the request. Docs have to show the reader what their pinned version does, which needs real platform support. |
URI path versioning wins for most API-first companies, and not because it's technically superior. It arguably isn't. Putting a version in the path means the same resource answers at two addresses, which REST purists have objected to for years, and they have a point.
It wins because it's the only scheme a developer can see. A version in the URL can be bookmarked, pasted into a Slack thread, attached to a support ticket, and read straight off a failing request in a log. Every other scheme puts the version somewhere a human has to go looking for it.
That visibility is worth more in your docs than it is on the wire. When the version lives in the address, every documentation page has an unambiguous home. Header versioning is cleaner on the wire and quietly worse on the page, because a docs URL can't carry a version the API keeps in a header. You end up buying a switcher widget to do a job a URL would have done for free.
There's a well argued position that the whole exercise is a mistake: that no versioning scheme is actually right, and that the correct number of versions is one. It deserves more credit than it usually gets.
The reasoning runs roughly like this: most breaking changes are avoidable, so avoid them, and you never pay for a version you didn't cut. That's correct, and it's the cheapest strategy available to you. It stops being available the day you need to change your auth model or restructure a core resource. That day comes for most APIs eventually. Treat it as the default to exhaust, not as a permanent answer.
Whichever scheme you land on, the bill arrives in the same place.
What each version actually costs to document
Take a mid-size API: 40 endpoints, SDKs in four languages, the usual set of concept guides. That's one version. Here is what a second one does to it.
One version | Two live versions | |
|---|---|---|
Reference pages | 40 | 80 |
Runnable code samples | 160 | 320 |
Concept guides (quickstart, auth, errors, webhooks, pagination) | 10 | 20 |
Things that have to be true after every release | 210 | 420 |
Ship v2 without sunsetting v1 on the same day, which nobody does, and that bottom row doubles. The engineering estimate covered none of it.
Code samples are the sharpest multiplier and the one teams underestimate most, because a code sample isn't text. It's a small program that has to compile and return what the page around it claims it returns. Four SDK languages across two live versions is eight working snippets per endpoint. Change the auth flow in v2 and you've just put 160 programs out of date.
The asymmetry is what turns a maintenance cost into a revenue problem.
v2 gets attention because it's new. It's what the launch post is about, what DevRel demos, what the docs work was actually staffed for. v1 gets nothing, because v1 is finished.
Except v1 is where your existing integrators live. They already paid, already shipped, and have no urgent reason to move. So the documentation decaying fastest is the documentation serving the customers you already have, and it decays quietly, because nobody on your side is reading it.
We've audited portals where more than 40% of code examples failed against the current API version . The pattern underneath that number is consistent: the pages that have quietly stopped being true are rarely the ones on the newest version.
This isn't an argument against versioning. It's an argument for pricing it before you approve it, and for knowing exactly what has to be written when you do.
What you have to document when you version
Four deliverables: a migration guide, a deprecation notice with a real date on it, a version switcher, and per-version changelog entries. Most teams ship the switcher and skip the other three, which is backwards, because the switcher is the only one your platform might hand you for free.
The migration guide
This is the deliverable that decides whether anyone actually moves, and it's usually written as something else. Most migration guides are diffs: a table of what changed between v1 and v2. That tells a developer what is different and nothing about what to do.
A real guide answers four things in order. What changed. What breaks for me specifically. What do I change in my own code. And in what order do I change it so I'm never sitting in a half broken state.
The fourth question is the one that gets skipped and the one that matters most. A developer migrating a live integration can't take payments down while they refactor. If your guide doesn't lay out a sequence that keeps them running the whole way through, they'll schedule the migration for a quiet week, and the quiet week never arrives.
The deprecation notice
"Deprecated" with no date attached is a suggestion. Developers read it as "still works," which is true, and act accordingly.
A usable notice carries the date, what happens on that date, and a link to the migration guide. That's the half a person reads.
For the half a client reads, there are standards worth using. RFC 9745 defines a Deprecation response header carrying the date a resource was or will be deprecated, and RFC 8594 defines Sunset, carrying the date it stops responding. They're designed to work together, and the spec requires the sunset date to be no earlier than the deprecation date. RFC 9745 also defines a deprecation link relation whose whole purpose is pointing a client at the documentation that explains the policy and the migration path
Worth adopting, and worth being clear about the limit. A header reaches code, not people. The person who has to schedule the migration work is not reading your response headers. Reaching them is what release notes are for writing release notes that hold up.
The version switcher
The one piece of this most documentation platforms give you, and the one with a trap in it.
A switcher sitting in the corner of the page implies both versions are equally current. If v1 is deprecated with a sunset nine months out, a neutral dropdown is lying by omission. Put the state in the switcher itself, and put the deprecation banner on every v1 page rather than only the v1 landing page. Most readers arrive from a search result directly onto a reference page and never see your landing page at all.
One OpenAPI spec per version
If your reference documentation generates from an OpenAPI spec, versioning is largely solved: one spec file per version, each living in git next to the code it describes, each generating its own reference.
If your reference is hand maintained, versioning is where that decision finally bills you. Eighty hand written reference pages do not stay accurate across two live versions, and no amount of process discipline changes that. OpenAPI versioning is what makes the multiplication in the last section survivable, because regenerating eighty reference pages is a build step while rewriting them is a quarter.
All four deliverables are downstream of a question most teams never actually sit down and answer.
How many API versions can you afford?
Fewer than you're running now. For most teams the honest number is two: one current, and one deprecated with a sunset date already published.
Three is usually where it breaks, and serving three versions is the easy part. The third one is simply the version nobody staffed. By then v1 is old enough that nobody currently on the team wrote its documentation, and old enough that the assumptions behind it have stopped being obvious to anyone who reads it.
One question settles this before you approve a new version. Who owns the old version's documentation until sunset, and can you say their name out loud?
If the answer is a name, you're running two versions. If the answer is a team, a process, or a shrug, you're running one version and one liability. The liability doesn't announce itself. It sits there getting gradually less true while your integrators keep reading it and believing it.
This is the part of API lifecycle management that gets the least attention, because the lifecycle is usually drawn as a diagram about the API. Design, build, publish, version, deprecate, retire. Every one of those stages has a documentation deliverable hanging off it, and the diagram never shows them.
Retire isn't even a stage you can reach by deciding to. You reach it when the last integrator moves, and they move when your migration guide makes moving possible.
Before any of that, though, the cheaper move is to not need a version at all.
Most of what teams cut versions for is expandable rather than breaking. An optional field is additive. A new endpoint running alongside the old one is additive. A new value in an enum is additive if your clients were built to ignore values they don't recognize, and if they weren't, that's worth fixing once instead of versioning around forever.
The discipline is to treat a new version as a failure to find an additive path rather than as a normal part of shipping. Some teams write this into the process: a breaking change needs sign-off from whoever will be accountable for the migration. It turns out to be a remarkably effective way of discovering the change wasn't necessary.
Some changes genuinely can't be made additively. Changing the authentication model. Restructuring a core resource so its relationships to everything else change. Changing what a field means while its name stays the same.
That last one is the case where refusing to version turns dangerous, because every integration keeps working and quietly returns wrong answers.
Those are real versions. Cut them, document them properly, and publish the sunset date for the old one on the day you launch the new one.
Publishing that date is the easy half.
Sunsetting a version is a documentation project
You can't turn off v1 until the integrators on it have moved. That makes your sunset date a prediction about other people's engineering calendars, and the only lever you have on it is how easy you made the move.
Which makes sunset a documentation deliverable wearing an engineering costume.
The pattern is consistent enough to plan around. A sunset date ships with the v2 launch, twelve months out, and it feels generous at the time. Around month nine somebody checks the traffic and a third of calls are still landing on v1.
So the date slides to month eighteen, and support starts handling migration questions one integrator at a time. That's the most expensive way this could possibly go, because every answer gets written once and read once.
The slip rarely traces back to integrators being lazy. It traces back to a migration guide that explained what changed without explaining how to move, so moving required a conversation. Conversations don't scale, and the integrators who can't get one simply don't move.
Meanwhile you're still paying the bill from earlier. Two references, two sets of code samples, two quickstarts, all of it expected to survive every release you ship in the interim. The v1 documentation you weren't maintaining at month nine is documentation you're still not maintaining at month eighteen, describing an API that has moved further away from it the entire time.
The teams that actually hit their sunset dates have one thing in common, and it isn't better tooling. Somebody owned the migration guide the way you'd own a feature: judged by whether integrators moved after reading it, and rewritten when they didn't. That's a different job from writing documentation once and publishing it.
The short version
Additive changes don't need a version. API versioning exists for breaking a contract, not for shipping one.
URI path versioning wins for most teams because a developer can see it, share it, and read it off a failing request, not because it's the purest choice.
Every live version multiplies the reference pages, quickstart, error docs, and runnable code samples somebody has to keep true.
v2 gets the attention. v1 has your paying integrators. The docs rotting fastest are the ones serving revenue you already booked.
Before approving a new version, name the person who owns the old one's documentation until sunset. If you can't, you haven't approved a version, you've approved a liability.
If the breaking change is the immediate problem, covers how to communicate it so people act on it. For versions running across several products on different release cadences, structuring docs for multiple products deals with that structure directly. And when the honest answer to the ownership question is a shrug, that's the part worth fixing first, because the scheme takes an afternoon and the ownership takes a decision
Pick the sunset date the day you launch v2. Then ask who is keeping v1 true until then, and don't accept a job title as the answer.


