API Versioning

Method

Keeping the old shape of an interface alive alongside a new one, so the people already calling it get time to move.

Why it exists

An application programming interface (API) is an agreed-upon counter that one program opens for another to call. Once that counter is public, developers at other companies write code against the promise it makes. That's where the trouble starts. The team that owns the counter renames one field in a response the way they'd rename anything in their own code, and every outside program reading that field stops working at that moment.

Picture a payments service renaming user_name to name and posting a changelog entry. Order screens at companies that missed the entry start rendering a blank where the buyer's name used to be, and it takes a day to find out why. The team that made the change adjusted one word. The teams that broke shipped nothing at all that day.

For a while the answer was to never change the shape at all. Awkward first designs stayed, and new capability arrived only as extra fields bolted on. A few years of that leaves a counter carrying fields nobody reads and names that no longer mean what they say, and reading the docs takes a translator.

Versioning is the compromise between those two. The shape does change, but the old shape isn't deleted — both run side by side for a while. Callers move on their own schedule, and the owner says up front when the old one closes.

How to run it

Start by deciding whether the change is even the kind that needs a new version. Most changes aren't.

  1. Classify the change. Adding a field to a response is safe, because old code ignores fields it doesn't know about. Removing a field, renaming one, or demanding a new required input is a breaking change, and that's where versions come in.
  2. Pick where the version lives. Putting it in the path is the most common choice: /v1/documents next to /v2/documents makes the version visible at a glance. Carrying it in a request header works too — cleaner URLs, but you have to open the logs to see which version a caller is on.
  3. Ship the new version and leave the old one running. Both usually read the same data and differ only in the shape they hand back, so the internal logic stays single and only the outward layer forks.
  4. Mark the old version as deprecated — announced as going away, with use discouraged in the meantime. Don't stop at the documentation. Put the shutdown date in a header on every response the old version returns. Few developers reread docs; everyone sees responses.
  5. Count who has moved. Tracking request volume per version tells you how many callers are still on the old one and which ones they are. Large customers still sitting there get a direct message.
  6. Close the old version. Instead of flipping it off on the announced date, switch it off for an hour or two at a time in the weeks before. Anyone still on it gets in touch that same day.

The gap between step 3 and step 6 usually runs six months to a year. If that date isn't fixed in the first announcement, the old version never goes away.

When it fits and when it doesn't

This is a method for counters exposed to the outside. You can't see the full list of who calls them, and you don't control anyone's release schedule, so you buy time instead.

Inside one company, where services call each other, it's usually too much machinery. If you can edit the calling code yourself, deploying both sides on the same day beats maintaining two shapes. Versioning every internal counter doubles the code for no gain.

The limit is cost. Two versions mean two places to fix, and one security issue means the same patch written twice. Past three versions nobody remembers which one carries what, and a bug report that starts with "the document endpoint returns the wrong author" takes an extra round of questions before anyone knows which shape the reporter is even looking at. So the working rule is two live versions at most, and the plan to close the older one gets made when the newer one opens.

Versioning for its own sake is worth watching for as well. When a change can be absorbed by designing for backward compatibility, skipping the version split is the better move.

How it sounds at work

"This is just a field rename. Do we really need to stand up a v2 for it?"

Asking whether the rename actually breaks outside code, and whether it's worth the cost of a split.

"We announced March as the v1 shutdown and 12 percent of traffic is still on v1."

Announcements alone don't move people; the remaining callers need to be contacted individually.

"This one only adds a field, so it ships without a version bump."

Old code ignores the new field, so there's nothing to split.

Pagedeck exposes a counter that other collaboration tools use to exchange documents. When Theo wanted to replace the author's name in a document response with an object holding name, email, and profile URL, Mia's first question was how many places call that endpoint today. The answer was 37, four of them paying enterprise customers.

The team opened /v2/ and left v1 in place. Every v1 response carried a notice that the version retires on September 30, and the team checked per-version request counts weekly. By the end of August three callers were left, and Sofia reached out to all three for their timelines. The shutdown happened on schedule, and not a single support ticket came in that day.

What people get wrong

The common assumption is that once versions exist, the new one is fair game to keep editing. v2 just opened and barely anyone is on it, so it feels free. But the moment someone integrates against v2, it inherits exactly the position v1 was in. Keep reshaping it and the same version number starts returning different things to different callers, which makes the number useless for predicting what you'll receive. A version number only means something while it promises the shape has stopped moving.

The other one is expecting the old version to drain by itself. The new version has the better fields, so surely people drift over. In practice no developer volunteers to touch an integration that already works, and without a fixed shutdown date old traffic is still there years later. Meanwhile both versions need care, every new feature lands in two places, and the old version that never closed becomes the reason a third one gets opened.

Easily confused with

TermWhat it refers toHow it differs from versioning
Backward compatibilityThe property of old usage continuing to workSolves the problem inside one version instead of splitting. When it's available, versioning isn't needed
Breaking changeA change that breaks a caller's codeThe thing versioning responds to. Its appearance is what triggers the decision to split

Where to go next

  • API

    see what it is that's being split into versions

  • Rate limiting

    the other thing you attach to a counter you've opened publicly

  • Deprecation

    how the wind-down period gets communicated

Last updated 2026-09-16