API Versioning Strategies for SaaS Products
Breaking API changes are inevitable in a growing SaaS product. The versioning strategy you choose determines whether changes break your customers or your team.
James Ross Jr.
Strategic Systems Architect & Enterprise Software Developer
APIs Are Contracts, Not Implementation Details
The moment you expose an API to external consumers — whether they're third-party integrations, partner applications, or your own mobile clients — that API becomes a contract. Every endpoint, every response shape, every error code is a promise you've made to developers who have built systems that depend on your behavior being predictable.
Breaking that contract is sometimes necessary. Products evolve. Data models change. Better patterns emerge. The question isn't whether you'll make breaking changes — you will. The question is how you manage those changes so that they don't damage the trust of the developers who depend on you.
API versioning is the mechanism for honoring old contracts while establishing new ones. But which versioning strategy you choose has real consequences for your codebase, your operational complexity, and your developer experience. I've seen teams choose poorly here and spend years living with the consequences.
The Four Versioning Strategies
There are four commonly used approaches to API versioning, and each has distinct tradeoffs.
URL path versioning (/api/v1/users, /api/v2/users) is the most explicit and the most widely used. The version is visible in every request, making it impossible to accidentally call the wrong version. Documentation is straightforward because each version is a distinct set of endpoints. The downside is that every breaking change requires a new version path, and your routing layer accumulates version-specific logic over time.
Header versioning (Accept: application/vnd.myapp.v2+json) keeps URLs clean and uses content negotiation to select the version. This is more RESTful in a theoretical sense, but it's harder for developers to use casually. You can't test versioned endpoints by pasting a URL into a browser. Most API consumers find it less intuitive than path versioning, and it's easier to misconfigure.
Query parameter versioning (/api/users?version=2) is simple to implement but semantically questionable. Version isn't a query parameter — it's a fundamental property of the request. It also makes caching more complex because cache keys now depend on query parameters. I generally advise against this approach.
No explicit versioning with additive-only changes is what some teams attempt, committing to never making breaking changes and only adding new fields and endpoints. This works until it doesn't. Eventually, you'll need to rename something, change a data type, or restructure a response. If you haven't built versioning into your architecture from the start, you'll bolt it on under pressure. I covered the broader principles of API contract design in my piece on API design best practices.
For most SaaS products, URL path versioning is the right choice. It's explicit, well-understood, and works with every HTTP client and documentation tool.
What Constitutes a Breaking Change
Defining what's "breaking" is less obvious than it sounds, and getting alignment on this within your team prevents arguments later.
Breaking changes include removing a field from a response, renaming a field, changing a field's data type, removing an endpoint, changing an endpoint's URL, changing the authentication mechanism, and altering the meaning of an error code.
Non-breaking changes include adding a new field to a response (existing consumers should ignore unknown fields), adding a new endpoint, adding an optional request parameter, and adding a new error code.
The gray area is changes that are technically non-breaking but behaviorally significant — like changing the default sort order of a list endpoint, adding pagination to an endpoint that previously returned all results, or modifying rate limits. These changes don't break the API contract in a strict sense, but they can break consumer applications that depended on the old behavior.
My rule of thumb: if a consumer's code could behave differently after the change without the consumer modifying their code, treat it as a breaking change and version it accordingly.
Implementation Architecture
The cleanest implementation pattern I've found for URL path versioning uses a router-level version prefix with version-specific controllers that delegate to shared business logic.
Your routing layer handles version extraction and routes to the appropriate controller. Each versioned controller is responsible for request validation (which may differ between versions), response shaping (which almost certainly differs), and mapping between the version-specific API contract and the internal domain model.
The critical architectural decision is keeping business logic version-agnostic. Your domain layer — the code that actually processes data, enforces rules, and interacts with the database — should know nothing about API versions. Versioned controllers translate between the external contract and the internal domain. This means a new API version requires new controllers and request/response types, but no changes to business logic.
For multi-tenant SaaS applications, versioning adds another dimension to consider. Different tenants may be on different API versions depending on when they integrated. Your system needs to track which version each integration uses and enforce appropriate rate limits and feature access per version.
Deprecation and Sunset Policy
A versioning strategy without a deprecation policy leads to indefinite maintenance of old versions. Every active version is code you maintain, test, and operate. The operational cost accumulates.
Establish a clear policy from day one. Announce deprecation at least 6 months before sunsetting a version. Provide migration guides that explain every change and offer code examples. Monitor usage of deprecated versions so you know who needs to migrate. Enforce sunset dates — at some point, the old version returns 410 Gone and consumers must upgrade.
The deprecation policy should be documented publicly and communicated proactively. A version sunset that surprises your customers is a trust violation, regardless of how clearly it was documented.
Building API versioning correctly from the start is significantly easier than retrofitting it onto a live API with active consumers. The time to make this decision is before your first external integration, not after a breaking change has already caused an outage.