Evolving APIs Without Breaking Clients.
Learner, Love to make things simple, Full Stack Developer, StackOverflower, Passionate about using machine learning, deep learning and AI
Why API changes fail in production and how experienced teams avoid it.
Read first article from series is here.
Introduction: APIs don’t belong to you anymore
The moment you publish an API, it stops being just your code.
It becomes:
Someone else’s dependency
Someone else’s release risk
Someone else’s production problem
Most API failures don’t come from load or latency.
They come from unexpected change.
If backward compatibility is hard in general, APIs make it harder—because your consumers are often invisible to you.
The uncomfortable truth about API consumers
You rarely know:
Who is using your API
How often they call it
What assumptions they made
How quickly they can upgrade
Even inside the same company:
Teams move on
Ownership changes
Documentation becomes outdated
Designing APIs means designing for uncertainty.
What an API contract really is
An API contract is not just:
Endpoint URL
HTTP method
Request and response fields
It also includes:
Field meanings
Validation rules
Default values
Error behavior
Ordering and timing
Breaking any of these can break clients—even if the JSON still “looks valid”.
Example 1: The hidden breaking change
Original response:
{
"total": 5
}
Later change:
{
"total": "5"
}
Everything still parses.
But:
A strongly typed client fails
Calculations behave incorrectly
Bugs appear far from the API
Lesson:
Data type changes are breaking changes—even if they seem harmless.
Additive changes: the safest way forward
The safest API evolution strategy is addition.
You can safely:
Add optional fields
Add new endpoints
Add new error codes
Clients that don’t know about them simply ignore them.
Example 2: Adding instead of modifying
Instead of changing:
{
"amount": 1000
}
Add:
{
"amount": 1000,
"pricing": {
"value": 1000,
"currency": "INR"
}
}
Old clients keep working.
New clients get richer data.
This is boring design.
And boring is good.
When behavior changes, version the API
If the meaning of a response changes, versioning is not optional.
Example 3: Business rule change
Originally:
- Order is created even if stock is low
Later:
- Order fails if stock is insufficient
Same endpoint. Same request. Different outcome.
This is a breaking change—even if the response schema is unchanged.
Correct approach:
/api/v1/orders → old behavior
/api/v2/orders → new rules
Versioning is not about structure.
It’s about behavior.
Common API anti-patterns (avoid these)
Anti-pattern 1: Overloading fields
Using one field to represent multiple concepts.
{
"status": "FAILED"
}
Does it mean:
Validation failed?
Payment failed?
System failed?
Clients guess.
Guessing leads to bugs.
Anti-pattern 2: Making optional fields mandatory
Today:
{
"email": "user@test.com"
}
Tomorrow:
{
"email": "user@test.com",
"phone": "9999999999"
}
If phone becomes required:
Old clients fail validation
Deployments break unexpectedly
Rule:
Once optional, always optional—at least in the same version.
Anti-pattern 3: Silent validation tightening
Validation changes break more clients than schema changes.
Example:
Previously allowed empty string
Now rejects it
Same field. Same API. Different outcome.
This breaks clients silently.
Be liberal in what you accept
APIs should be forgiving at the edges.
Accept:
Missing optional fields
Extra fields you don’t understand
Slightly older formats
Reject only when:
Data is invalid
Security is at risk
Business rules truly require it
Strict input + no versioning = production pain.
Error handling is part of the contract
Changing error behavior is a breaking change.
Example 4: Error response change
Old:
{
"error": "INVALID_REQUEST"
}
New:
{
"errors": [
{ "code": "INVALID_REQUEST" }
]
}
Cleaner? Yes.
Backward compatible? No.
Clients parse errors too.
How experienced teams evolve APIs safely
They do three things consistently:
Design APIs as public contracts
Assume clients upgrade slowly
Measure usage before removing anything
They treat APIs like products—not internal functions.
A simple checklist before changing an API
Before you change an API, ask:
Will old clients still work?
Are we changing behavior or just adding data?
Can this be done additively?
Do we need a new version?
How long will we support the old one?
If the answer is unclear, don’t change it yet.
Final thought: APIs fail at the boundaries
Most systems don’t fail at the core logic.
They fail at the boundaries—where assumptions meet reality.
APIs are those boundaries.
Design them carefully, evolve them slowly, and never forget:
Once an API is used, it no longer belongs only to you.
More such articles:
https://www.youtube.com/@maheshwarligade


