Skip to content
routebase
API Versioning12 chapters

Chapter 07 of 12

Running Multiple API Versions at Once

What it means for a version to be live in an environment, how routing decides who gets which one, and how docs, mocks and tests stay bound to it.

Cutting a version is a decision, and running two is an operating model. This chapter is about the second one, because that is where the cost from when versioning does not matter actually lands.

One deployment or two

There are exactly two ways to serve two contracts, and the choice shapes everything after it.

One deployment handling both. The service knows which version the caller asked for and behaves accordingly. Shared data stays consistent, operations stay simple, and the code fills with conditionals that nobody dares remove. This suits versions that differ in representation rather than in behaviour.

Separate deployments per version. Each version is its own service, clean and independently deployable, and both still write to the same data. Every schema migration now has to satisfy two consumers, and every incident has two suspects. This suits versions that differ deeply, and it works best when the older one is frozen and only receiving security fixes.

Most teams start with the first and are pushed towards the second by the third live version, which is a good argument for never having a third.

Routing is a policy, not a config file

Whichever carrier you chose, the routing layer has to answer three questions consistently, and the answers belong in your documentation rather than in a proxy rule nobody reads.

Which version a request with no version identifier receives, what happens when the identifier is unknown, and whether a caller can be pinned by account rather than per request. That third one is what large platforms use, since it lets a customer set their version once and stop thinking about it.

Everything downstream needs the same pin

The part that goes wrong is rarely the routing. It is that the documentation, the mock server, the test suite and the monitors all keep pointing at whatever is newest, while production keeps serving something older.

Production serves one version while the documentation, the mock server, the contract tests and the monitors all follow whichever is newest, so every row sits in the wrong column. Nothing in the system reports this, because each artefact is individually correct about a version that nobody is running. The pin has to be one column rather than two.
ArtefactWhat it should be bound to
Documentation portalThe version each environment actually serves, with a switcher for consumers
Mock serverA named version, so client teams build against a stable contract
Contract testsThe version its target environment runs, not the newest draft
MonitorsThe published contract of the deployed version
Client SDKsThe version they were generated from, stated in the package

Each row is the same mistake in a different costume. Something derived from the specification gets bound to the latest state of it, and then it silently changes meaning when somebody edits a draft.

A pin is a claim, not a fact

Saying that production runs version 2 is a statement about intent. Whether the service actually behaves like version 2 is a separate question, and the two drift apart quietly.

Someone deploys a hotfix straight to production, a configuration change alters a response, a shared library changes how dates serialize. Nothing in the version pin notices any of it, because the pin records what should be true.

The only way to close that gap is to check the running service against the contract the pin names. That is contract validation applied to a specific version rather than to the newest one, and it is what turns the deployment record into something you can rely on during an incident. Running that check continuously against a live environment is contract drift monitoring.

Rollback means moving the pointer

When a release goes wrong, the temptation is to edit the version that just shipped. That is the one thing to avoid, because anybody who already integrated against it now has a third contract wearing the same number.

Released contracts should be immutable, and rollback should move the pointer to the previous version. The failed version stays in the record as something that existed and was withdrawn, which is both honest and useful the next time somebody asks what happened.

Retiring one is the whole point

Two live versions is a transition. Two live versions with no retirement date is an architecture, and it is the expensive one.

Every version you run should have an answer to the question of when it goes away, and that answer should exist before the version ships. Deprecation and sunset covers how to run that end deliberately.

In Routebase

Each environment pins one version per specification, so the question of what production runs has a recorded answer. Promoting a version moves the pin forward, rolling back moves it to the previous version, and the frozen contracts themselves never change.

The contract tab of a production environment, with switches for freezing versions, feeding the public docs and feeding the mock server, and one pinned specification showing its version and a never-verified label.
The contract tab shows which version an environment claims to run, next to whether that claim has been verified against the live service.

The rest of the workspace follows the pin. Contract tests validate against the version the environment is pinned to, so a test run tells you about the contract that is actually deployed there. Mock rules are pinned to a published version and show a drift banner when the specification moves past them, with a Sync from Spec action when you want them to follow. Documentation portals expose a version switcher to consumers, and the mock server routes incoming requests by the strategy the specification declares. The Environments guide covers promotion, pins and rollback.

Frequently asked questions

How do you run two API versions at the same time?

Either the service implements both behaviours behind one deployment, or a routing layer sends each version to its own deployment. Sharing one deployment keeps the data layer consistent and fills the code with conditionals. Separate deployments keep each version clean and multiply the operational work, and they need a plan for how both talk to one database.

Which environment should run which API version?

Production runs the version your consumers depend on, staging runs the version you are about to release, and development runs the draft. Pinning each environment to a named version is what makes that statement true rather than aspirational, since without a pin the answer is whatever was deployed last.

How do you roll back an API version?

Move the pointer rather than editing the contract. A released version should be immutable, so rolling back means pointing the environment at the previous version, not changing what the new one says. Anything that reopens a frozen contract for editing turns a rollback into a second breaking change for whoever already integrated.

How do mocks and tests stay in sync with multiple API versions?

Bind each one to a named version instead of to the latest state of the specification. A mock pinned to version 2 keeps serving version 2 behaviour while the design of version 3 continues, and a contract test bound to the version its environment runs validates against the right contract. Anything bound to latest silently changes meaning the moment someone edits a draft.

Last reviewed by The Routebase Team.

Ready to ship on it?

Routebase is live. Design your API once — docs, mocks, tests, and monitoring all follow from the same source.

14-day Pro trial — no credit card required.