Back to Blog
4 min read

Version Reluctantly

Design your API well and you can grow it for years without breaking anyone. But every long-lived API eventually hits the change it can't make politely. Versioning is how you break a promise to people you can't even see, without leaving them face-down. And the goal is to use it as rarely as you can.

Listening · Version Reluctantly

Every long-lived API eventually hits the change it can't make politely. You designed a deliberate surface, you preferred adding over altering, you did everything right, and still the old shape has to go and there's no additive way around it. A breaking change is coming.

The question is how you ship it without leaving the people who depend on you face-down, because you often can't even see them. Someone wired their system to your API months ago and went home, and the moment you break the old shape, their thing falls over with no warning to either of you. Versioning is how you make a breaking change without breaking a promise. It's the escape hatch, and like most escape hatches, the whole art is using it as seldom as you can.

First, be sure you actually have to

Quick reminder of the line, because it's the entire trigger. Additive changes, new fields, new endpoints, new optional parameters, don't need versioning at all. A well-behaved consumer ignores what it didn't ask for, so adding breaks nothing. You only reach for a new version when you have to change or remove something already there, because that's the kind of change that breaks people.

So before you cut a version, be honest about whether you truly must. If you find yourself versioning constantly, the real problem is usually upstream, in changes that could have been additive but were made breaking out of a taste for tidiness. A new version is the most expensive way to make a change. Earn it.

The strategies, quickly

When you genuinely do have to break, the old and new contracts need somewhere to coexist, and there are a few common ways to arrange that.

Version in the URL path, like /v1/orders. It's explicit, it's visible, you can paste it into a browser, it's trivial to route and cache, and every developer alive understands it on sight. Purists dislike it, because in strict REST the URL is meant to identify the resource, not the version. I use it anyway for most things, because clarity and testability beat purity when other people have to consume the thing.

Version in a header, usually the Accept media type, like application/vnd.company.v2+json. It keeps URLs clean and is arguably more correct. It's also invisible, harder to test, harder to debug, and easier for a consumer to get subtly wrong. Reach for it when you have a real reason, not by default.

Version in a query parameter, like ?version=2. Simple, but it muddles versioning in with your actual parameters and tends to cause caching headaches. Usually the weakest of the three.

The strategy matters less than people argue about. Pick one, apply it consistently, make it obvious. What matters far more is the part sitting underneath all of them.

What versioning actually forces

Here's the real value, and it isn't the numbers. The moment you commit to versioning, you've committed to treating your API as a contract, and that changes how you work whether you ever ship a v2 or not.

You now have to know, for every change, whether it's additive or breaking, which means you have to actually understand your own contract. You owe your consumers stability within a version and a clear, humane path when something is going away, which means deprecation notices and timelines, not silent removals. And every version you keep alive is a version you have to maintain, so you feel the cost of a breaking change directly, which makes you avoid them, which makes the API better. The discipline shows up long before v2 does. It shows up in v1, in the fact that you now design like someone is depending on you, because someone is.

Version reluctantly

So the practical wisdom is almost the opposite of "have a great versioning strategy." Have one, yes. Then work to use it as rarely as you can. Prefer additive change. Expose a deliberate surface, not your whole table, so your internal changes stay internal. Be a tolerant reader, ignore inputs you don't recognize rather than rejecting them. Deprecate slowly and loudly. Treat a new major version as exactly what it is, a promise to maintain two contracts in parallel until you can honestly retire the old one, which is always later than you hoped.

The best versioning strategy, in the end, is the one you almost never have to use. Design the contract so it can grow, break it only when there's truly no other way, and when you must, do it loudly, slowly, and with a clear path for the people you can't see. The number in the URL is just where the promise becomes visible. Keeping it is the actual job.

Share this article

Want to Work Together?

Let's discuss how I can help with your project.

Get in Touch