
Once an external client depends on your API, your freedom to change it ends. Versioning is how you get some of that freedom back — but the strategy matters less than the discipline of avoiding breaking changes in the first place.
Most changes need not break anything
Adding a field, adding an optional parameter, adding an endpoint — all safe, provided clients ignore unknown fields, which you should state explicitly in your documentation. The changes that break are removals, renames, type changes and semantic changes where the field name stays and the meaning shifts. That last one is the cruellest because nothing fails loudly.
The dangerous change is not the one that breaks the response. It is the one that changes its meaning silently.
Version at the boundary you can support
URL path versioning is explicit, cacheable and obvious in logs. Header versioning is cleaner in theory and harder to debug in practice. Whichever you pick, the real constraint is how many versions you can genuinely maintain — every additional live version multiplies your testing surface, so two is comfortable and five is a trap.
Deprecation is a process, not an announcement
A deprecation needs a documented date, a response header on the deprecated endpoint, direct communication to identified consumers and usage monitoring throughout. Announcing a removal without knowing who still calls it is how integrations break on a Friday.
Contract tests catch what review misses
A test asserting the exact response shape fails when a developer removes a field they believed was unused. This is cheap to add and catches the class of change that code review reliably misses, because the removal looks harmless in the diff.
Errors are part of the contract
Clients branch on error codes, so changing an error response is a breaking change even when the success path is untouched. Use stable machine-readable codes alongside human messages, and treat the codes as versioned surface — the messages can change freely, the codes cannot.
Documentation from the specification
Hand-written API documentation drifts within weeks. Generate the reference from an OpenAPI specification that is itself validated against the implementation in CI, and hand-write only the quickstart. This guarantees the reference is accurate and puts the effort where it helps: the first successful call.





