TL;DR
- Version only for breaking changes. Additive changes (new optional fields/endpoints) should be backward‑compatible.
- Prefer path versions (
/v1) for public APIs—clear, cache‑friendly, and easy to route. Use media types/headers when you need finer control. - Communicate and phase changes: ship deprecations, send Sunset/Deprecation headers, run versions in parallel, and provide a migration guide.
1) What is a breaking change? (decide this first)
- Remove or rename fields/endpoints.
- Change types/semantics (string → number, units, enum values).
- Tighten validation in a way that rejects previously valid requests.
- Change defaults (pagination/sort) that alter results for existing clients.
- Rename error codes or envelopes in a way clients can’t parse.
Non‑breaking (usually): adding optional fields, adding new endpoints, widening enum values, adding new error codes while keeping the same error shape.
2) Versioning strategies (with pros/cons)
A) Path version (recommended for most external APIs)
GET /v1/orders/123
GET /v2/orders/123
Pros: obvious to humans and proxies; trivial routing and docs; cache‑friendly.
Cons: URL churn; multiple docs to maintain.
B) Media type / header version
GET /orders/123
Accept: application/vnd.example.orders+json;version=2
Pros: fine‑grained, can version resources independently; no URL change.
Cons: more complex to cache/proxy; clients must remember headers; doc tooling harder.
C) Query param (use sparingly)
GET /orders/123?version=2
Works, but hurts caches and can leak ACLs/SEO.
D) Subdomain (niche)
GET https://v2.api.example.com/orders/123
Operationally heavier; okay for “big bang” eras.
Pick exactly one public strategy and enforce it consistently.
3) Deprecation & migration signals
- Headers
Deprecation: trueSunset: <HTTP-date>(when the old version stops)Link: <https://docs.example.com/migrate-v1-to-v2>; rel="deprecation"
- Docs: changelog + migration guide + code examples.
- Phasing: deprecate → warn → freeze features → security‑only → sunset.
- Monitoring: identify active clients on old versions and contact them.
4) Compatibility rules that keep you out of trouble
- Treat your error envelope as part of the public contract; add new
codes but keep the shape stable. - Prefer objects to positional arrays so you can add fields safely.
- Use explicit nullable/optional semantics; don’t repurpose fields.
- For pagination, prefer cursor/keyset so adding rows doesn’t reshuffle pages.
- Use feature flags on the server to run v1/v2 side by side while you validate.
5) Routing & policy snippets
Nginx (edge split for /v1 vs /v2)
server {
listen 443 ssl http2;
server_name api.example.com;
location ~ ^/v1/ { proxy_pass http://api-v1; }
location ~ ^/v2/ { proxy_pass http://api-v2; }
}
Envoy (header‑based versioning)
- match:
prefix: /orders
headers:
- name: accept
safe_regex_match: { regex: ".*version=2.*" }
route: { cluster: orders-v2 }
- match: { prefix: /orders }
route: { cluster: orders-v1 }
AWS API Gateway (two stages)
Routes:
- RouteKey: "ANY /v1/{proxy+}"
Target: "integrations/V1"
- RouteKey: "ANY /v2/{proxy+}"
Target: "integrations/V2"
Kong (path + deprecation header plugin sketch)
services:
- name: orders-v1
url: http://orders-v1:8080
routes: [{ paths: ["/v1"] }]
plugins:
- name: response-transformer
config: { add: { headers: ["Deprecation:true", "Sunset: Sun, 01 Dec 2025 00:00:00 GMT"] } }
- name: orders-v2
url: http://orders-v2:8080
routes: [{ paths: ["/v2"] }]
6) GraphQL vs REST (quick guidance)
- REST: Use
/vNor media types. Additive changes are non‑breaking if your envelope stays stable. - GraphQL: Prefer never breaking—add new fields/types; mark old fields with
@deprecated; don’t remove until usage is zero. Version the schema (docs) rather than the endpoint.
7) SDKs & clients
- Generate clients from OpenAPI/SDL; pin major versions.
- Add runtime capability checks (e.g., feature discovery endpoints) when feasible.
- Emit warnings when an SDK is used against a deprecated API version.
8) Pitfalls & fast fixes
| Pitfall | Why it hurts | Fix | |---|---|---| | Versioning every tiny change | Version sprawl | Version only breaking changes | | Silent breaking change | Client outages | Phase with headers + migration guides | | Multiple public styles | Confusion | Pick one strategy and document it | | No coexistence period | Downtime | Run v1 and v2 in parallel at the edge | | Changing error envelope | Parser failures | Keep the shape; add new codes only | | “Big bang” rewrites | Risk & delays | Ship small, additive steps first |
Quick checklist
- [ ] Declare what counts as breaking for your API.
- [ ] Choose path or header versioning and standardize it.
- [ ] Send Deprecation/Sunset headers and link migration docs.
- [ ] Keep error envelope stable; add fields additively.
- [ ] Run versions in parallel; monitor old clients.
- [ ] Provide SDKs and test sandboxes for new versions.
One‑minute adoption plan
- Publish a short versioning policy (breaking vs additive).
- Implement routing for /v1 and /v2 at the edge.
- Add Deprecation/Sunset headers to v1 and a migration guide link.
- Keep v1 and v2 live together; monitor usage, reach out to stragglers.
- After the sunset date, retire v1 and clean up docs & SDKs.