Software Engineering

API Versioning and Backward Compatibility: Evolving APIs Without Breaking Clients

What counts as a breaking change, how to choose a versioning approach, and how to deprecate old behaviour without stranding the people who depend on it.

CodeSurge AI Engineering TeamPublished 28 October 20266 min read
Table of contents

An API is a promise. The moment another team, a mobile app or a partner depends on it, every change you make is a change to their system too. Versioning is not really about numbers in a URL; it is about how you keep that promise while the product underneath keeps changing.

This guide covers what actually counts as a breaking change, the common versioning approaches and their trade-offs, how to evolve an API additively so most changes need no new version at all, and how to run deprecation so clients have time and information to move. It pairs with our [API gateway patterns guide](/blog/api-gateway-patterns-for-enterprise), which covers where versions are routed.

Quick answer
Default approach
Evolve additively; add a new version only for genuine breaking changes
Breaking changes
Removing or renaming fields, changing types or meaning, tightening validation
Deprecation
Announce early, signal in responses, set a date, and measure remaining usage
Safety net
Contract tests against real consumer expectations

What counts as a breaking change

A change is breaking if a well-behaved existing client stops working correctly without modifying its code. That is a wider category than people expect:

  • Removing or renaming a field, endpoint or enum value.
  • Changing a field's type, format or meaning (a price that was in rupees is now in paise).
  • Making an optional input required, or tightening validation so previously valid requests are rejected.
  • Changing default behaviour, ordering, pagination or error codes that clients may rely on.
  • Adding a new enum value when clients assume the set is closed.

Adding a new optional field or a new endpoint is normally safe, provided clients are written to ignore what they do not recognise. That tolerance (sometimes called the robustness principle) is the foundation of additive evolution, and it is worth stating explicitly in your API documentation.

Prefer additive evolution

The cheapest version is the one you never have to create. If you can add rather than change, do: introduce a new field alongside the old one, keep both populated for a transition period, then deprecate the old one. Keep response shapes extensible (objects rather than bare arrays at the top level, so you can add metadata later). Decide early how unknown fields are treated on input, because that choice is difficult to reverse.

Common versioning approaches
ApproachHow it looksStrengthsTrade-offs
URL path version/v2/ordersObvious, easy to route and cache, easy to test in a browserWhole-API version bumps; encourages large, infrequent breaking releases
Header or media-type versionAccept: application/vnd.example.v2+jsonClean URLs; can version per resourceLess visible, harder to debug and cache, more client friction
Query parameter version/orders?version=2Simple to addEasy to omit by accident; can fragment caching
Date-based versionA dated header chosen by the clientFine-grained, predictable change historyRequires infrastructure to support many dated behaviours
Pick one approach and apply it consistently. Consistency matters more to clients than which style you choose.

Do not keep every version alive forever

Each supported version multiplies testing, security patching and support work. Define up front how many versions are supported at once and for how long, and publish that policy. An API with no retirement path accumulates versions until the oldest ones become the biggest source of risk.

Deprecation that clients can actually act on

Deprecation fails when it is only an announcement in a changelog nobody reads. A workable process has several parts:

  1. Announce early with a specific retirement date and a migration guide that shows old and new side by side.
  2. Signal in the response itself, for example with a deprecation indicator and a sunset date in response headers, so developers notice during normal work.
  3. Measure usage per client and per endpoint. You cannot retire what you cannot see; usage data tells you who still needs a nudge.
  4. Contact affected clients directly, especially partners, before the date.
  5. Brown out if necessary: brief, scheduled failures shortly before retirement can surface forgotten dependencies safely.
  6. Retire on the stated date, or consciously extend it. Silent extensions teach everyone that dates are optional.

Contract testing and consumer awareness

Documentation says what you intended; consumers depend on what you shipped. Contract tests capture the exact request and response expectations of each consumer and run in your pipeline, so a change that would break someone fails the build instead of production. Even without a formal tool, keep a regression suite of recorded real requests. This fits naturally into the pipeline discipline in our CI/CD guide for small teams.

Internal vs. public APIs

Not every API needs heavy ceremony. An internal API consumed by a team that deploys in lockstep can change faster, provided consumers and producers can be updated together. Public and partner APIs, and any API consumed by mobile apps you cannot force-upgrade, deserve the strictest compatibility discipline, because you cannot control when clients update. Treat anything with a client you cannot redeploy as public.

Plan for the breaking change you will eventually need

Sometimes a model is simply wrong and compatibility must give way. When that happens, version at the smallest unit that makes sense, ship the new version side by side, migrate clients deliberately, and retire the old one on schedule. Doing this rarely and carefully is far better than avoiding it forever and letting a flawed design harden, which is how technical debt becomes permanent.

API compatibility checklist
  • A written definition of what counts as a breaking change
  • Changes are additive by default; new fields and endpoints are optional for clients
  • One consistent versioning approach is documented and applied
  • Clients are told to ignore unknown fields and unknown enum values
  • A stated policy for how many versions are supported and for how long
  • Deprecation notices include dates, migration guides and in-response signals
  • Per-client usage of deprecated endpoints is measured
  • Contract or regression tests run in CI against real consumer expectations

Designing or evolving APIs that other teams depend on?

Talk to our engineering team about API design, versioning strategy and safe migration plans.

Frequently asked questions

What is a breaking change in an API?+

Any change that makes a correctly written existing client stop working without code changes: removing or renaming fields, changing types or meaning, making optional inputs required, tightening validation or changing documented defaults.

Is adding a new field a breaking change?+

Normally not, as long as clients ignore fields they do not recognise. State that expectation in your documentation. Adding enum values can be breaking if clients assume the set is closed.

Should I version by URL or by header?+

Both work. URL versioning is more visible and easier to route and debug; header versioning keeps URLs clean but is harder to see and cache. Choose one and apply it consistently.

How long should I support an old API version?+

Long enough for clients to migrate with notice, but with a published limit. The right length depends on how many clients you have and whether you can force them to update, as with mobile apps or partners.

How do I know who still uses a deprecated endpoint?+

Log and measure usage per client and per endpoint. That lets you contact remaining users directly before the retirement date.

Do internal APIs need versioning?+

Not always. If producer and consumers deploy together, you can change them in lockstep. Treat any API with consumers you cannot redeploy, such as mobile apps or partners, as public.

Written by

CodeSurge AI Engineering Team

The CodeSurge AI team designs and builds AI systems, SaaS products and enterprise integrations for clients in India, the UAE and beyond — this section shares the architecture patterns, cost drivers and implementation tradeoffs we work through on real projects.

AI EngineeringEnterprise ArchitectureSaaSCloudSoftware Development

Found this useful? Share it with your team.

Share
Keep reading

Related insights

Talk to CodeSurge AI