Skip to main content

API Versioning in Practice

How to evolve an API contract without surprising the people who depend on it.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

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:

  1. Compatibility first. If I can evolve without breaking people, better.
  2. A breaking change needs transition. Breaking may be necessary, but it should not be a surprise.
  3. 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 /v2 as 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

Practice checklist

Use this when you answer

You finished this article

Next article Circuit Breaker in Practice Previous article REST vs GraphQL vs RPC: When Each One Fits

Keep exploring

Related articles