March 10 2025
API Versioning in Practice
How to evolve an API contract without surprising the people who depend on it.
Andrews Ribeiro
Founder & Engineer
4 min Intermediate Systems
The problem
Many teams only remember versioning after they already broke someone.
While the API is still small, changing the contract feels cheap. The consumer changes together, everyone knows everyone, and the team concludes that “we will organize this later.”
The real problem shows up when the client is already in production, the mobile app is stuck in an older release, an external partner consumes the endpoint, and one small change breaks a real checkout, integration, or screen.
Versioning starts to matter exactly when changing things casually stops being an option.
Mental model
API versioning is not a ritual about version numbers.
It is a discipline for answering this question:
how do I evolve the contract without surprising the people who depend on it?
That leads to three ideas that matter more than memorizing strategies:
- Compatibility first. If I can evolve without breaking people, better.
- A breaking change needs transition. Breaking may be necessary, but it should not be a surprise.
- Every extra version costs maintenance. Running two or three versions in parallel is not free.
Breaking the problem down
What is usually compatible
Changes that are often safe:
- adding an optional field to the response
- adding a new endpoint
- accepting a new optional parameter
- expanding supported values without changing the old meaning
Those changes are usually less dangerous because old clients can still understand the contract the way they already did.
What is usually breaking
Dangerous changes:
- removing a field
- renaming a field
- changing a data type
- changing whether a field is required
- changing return semantics
- changing an expected status code without transition
The problem here is not only technical.
It is broken expectation.
When creating a new version makes sense
A new version makes more sense when the important change does not fit inside a compatible evolution.
Examples:
- the resource model changed in a meaningful way
- authentication or error semantics changed deeply
- the old client truly cannot keep working without adaptation
But creating a new version every time the team feels uncomfortable turns versioning into escape, not strategy.
Where to version
Versioning can appear in different places:
- URL, like
/v2/orders - header
- media type
- schema or contract inside an internal gateway
The main point is not the fashionable format.
It is operational clarity:
- does the consumer understand what they are using?
- can you observe who is still on the old version?
- is the migration path clear?
Simple example
Imagine an endpoint:
GET /orders/123
Old response:
{
"id": "123",
"status": "paid",
"total": 150
}
Now the team wants to expose currency and items.
A compatible evolution would be:
{
"id": "123",
"status": "paid",
"total": 150,
"currency": "BRL",
"items": []
}
The old client still works.
Now imagine that someone wants to change total from a number to an object:
{
"total": {
"amount": 150,
"currency": "BRL"
}
}
That tends to be breaking.
Here you need transition:
- add the new field first
- mark the old one as deprecated
- measure usage
- agree on a migration window
- remove it later with predictability
Notice the strong move here is not “invent v2.” It is designing coexistence.
Common mistakes
- Creating a new version for every small adjustment.
- Changing the contract silently because “internally it was easy.”
- Forgetting that mobile apps and external partners do not update at the same pace.
- Keeping the old version forever with no retirement plan.
- Versioning the path but not the behavior.
How a senior thinks about it
Someone with more experience looks at versions as product and operations cost, not just route detail.
The reasoning usually sounds like this:
“If I can evolve compatibly, better. If I cannot, I need to make the transition observable, communicated, and temporary.”
That posture avoids two extremes:
- breaking clients out of arrogance
- carrying eternal legacy out of fear
What the interviewer wants to see
In interviews, the evaluator wants to see whether you treat the contract as a real commitment.
It signals maturity when you:
- distinguish compatible changes from breaking ones
- talk about deprecation and coexistence
- mention observability to know who still uses the old version
- avoid treating
/v2as the only possible answer
A strong answer often sounds like this:
“I would try compatible evolution first. If the change is truly breaking, I would create an explicit transition, measure old-version usage, and give a migration window before removing it.”
Good versioning does not only protect the server. It protects trust between whoever offers and whoever consumes the contract.
Quick summary
What to keep in your head
- A compatible change is usually better than creating a new version out of impulse.
- Breaking clients usually comes from changing meaning, format, or required fields without a transition.
- Deprecation, coexistence windows, and observability matter more than a rushed `/v2`.
- Good versioning protects the contract and reduces surprise for API consumers.
Practice checklist
Use this when you answer
- Can I distinguish a compatible change from a breaking one?
- Can I explain when adding a new field does not require a new version?
- Can I describe a deprecation plan with coexistence between contracts?
- Can I talk about versioning without reducing everything to `/v2` in the URL?
You finished this article
Share this page
Copy the link manually from the field below.