How it works
Start by defining the contract: request fields, response fields, errors, defaults, and behavior. Renaming a field, changing its type, or changing what a successful response means can break clients even if the endpoint stays the same.
Prefer backward-compatible changes within a version. For example, add an optional request field with a default that preserves existing behavior rather than making every client supply it immediately. Adding response fields is usually safe only when clients tolerate unknown fields; new enum values can break exhaustive client code.
Expose incompatible contracts through an explicit version selector. Common choices are URL paths such as /v1/payments, query parameters, or headers. Paths are easy to inspect and route; headers keep URLs stable but require version-aware routing and cache configuration.
Translate each supported contract into shared application operations where semantics allow it. A v1 response adapter might return amount_cents while v2 returns an amount object containing minor_units and currency. Do not fabricate missing currency information just to make the translation work.
Run old and new contracts side by side during migration. Validate requests and responses against each contract, test old clients against new server builds, and measure usage and errors by API version and client identity.
Retire an old version through a published support policy, migration documentation, notices, and a clear cutoff. Reject unsupported versions explicitly rather than silently serving the latest behavior. API contract versions are separate from server deployment numbers and database schema versions.
A worked example
Evolving a payment amount contract
Suppose /v1/payments accepts amount_cents and supports only USD. You need multiple currencies, so /v2/payments accepts amount: {minor_units: 2500, currency: "USD"}. Keep v1's USD behavior unchanged; do not reinterpret its integers using a merchant's new default currency. Both handlers call the same payment operation and share merchant-scoped idempotency records so a retry through another version cannot create a second charge. Test response rendering for each version, including replayed results. In this hypothetical rollout, support both versions for six months, track usage by merchant, and contact remaining v1 users before retirement. Database changes must remain compatible with both handlers throughout the migration.
Trade-offs to say out loud
- Independent client upgrades versus ongoing support
- Keeping older contracts available lets clients upgrade independently, but every supported version adds testing, documentation, and operational work.
- URL versions versus header versions
- URL versions are visible in links and logs, while header versions need explicit logging and correct cache variation to avoid mixing representations.
- Shared implementation versus semantic isolation
- Adapters reduce duplicated business logic, but genuinely different behavior may require separate paths rather than increasingly fragile translation.
- Additive evolution versus client assumptions
- Avoiding version bumps reduces migration work, but even additive fields or enum values can break clients with strict parsing or undocumented assumptions.
- Retirement deadlines versus client availability
- A firm sunset bounds maintenance costs but can disable abandoned integrations or older installed clients.
- Stable contracts versus urgent security changes
- Compatibility is not a reason to preserve a vulnerability, so the support policy must allow required security fixes across supported versions.
When to reach for it
- External developers integrate with the service and control their own release schedules.
- Mobile, desktop, or device clients remain installed long after a server deployment.
- A proposed change alters required inputs, field types, errors, defaults, or observable behavior.
- Old and new clients must operate against the same backend during a gradual migration.
- An interviewer asks how the API evolves without a coordinated release across every consumer.
How to bring it up in the interview
- Identify who controls client upgrades before choosing a strategy; coordinated internal callers may need compatibility rules without a new public version.
- Name the exact breaking change and explain why an additive change cannot solve it; interviewers may challenge whether a new version is necessary.
- Choose one version selector and trace it through routing, validation, caching, and logs; expect questions about headers and cache keys.
- Explain which business logic remains shared and how both contracts read and write the same underlying data; expect probes about database migration and rollback.
- Describe contract tests, per-client adoption metrics, and retirement criteria; a version prefix alone is not a migration plan.
Common mistakes
- Creating a new API version for every server release or database schema change.
- Assuming all added response fields and enum values are automatically backward compatible.
- Changing defaults, error semantics, ordering, or pagination behavior without treating them as part of the contract.
- Duplicating the entire backend for each version instead of separating contract handling from shared business rules.
- Ignoring retries across versions: changing a payment endpoint must not accidentally bypass the operation's idempotency protection.
Where it comes up
Interview questions where api versioning is part of a strong answer, and why.
- Design a payment systemEvolving merchant-facing payment contracts requires preserving money semantics and idempotency across old and new integrations.
- Design Dropbox or Google DriveOffline sync clients can return with older contracts, requiring compatible change-feed, conflict, and deletion semantics.
- Design a system to run commands across a fleetAgents upgrade independently and may remain offline, so command contracts must remain safe across mixed agent versions.
Questions people ask
What is API versioning?
API versioning identifies different supported contracts for a service so clients can keep using known behavior while the API evolves. It covers semantics and errors as well as request and response shapes.
Should API versions go in the URL or headers?
URL paths such as /v1/orders are a straightforward default for public HTTP APIs because they are easy to see, document, and route. Headers are also valid, but routing, observability, and caches must account for the selected version. For cacheable responses selected by a request header, configure the cache key and appropriate Vary behavior.
When does an API change need a new version?
Use a new contract version when an existing supported client would need to change to keep working correctly and an additive design cannot preserve compatibility. Examples include removing a field, changing its type, or changing a default's meaning. Adding an optional field often does not require a new version, provided existing clients can safely ignore it.