API Versioning & Evolution#
Problem statement (interviewer prompt)
Design the versioning policy for a public REST API with 500 enterprise customers. New endpoints + new fields ship monthly; once a year you need a breaking change. Pick URI vs header vs evolutionary versioning, write the sunset / deprecation policy, and explain how SDKs migrate.
flowchart LR
V1[v1 contract]
V2[v2 contract]
V3[v3 contract]
V1 -- coexists --> V2
V2 -- coexists --> V3
V1 -. deprecated, sunsets after window .-> X([retired])
classDef p fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef s fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
class V1,V2,V3 s;
class X p;
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class V1,V2,V3,X service;
Public APIs live forever. Version them so you can evolve without breaking clients: ideally additive changes only; if breaking, run multiple versions side-by-side and sunset older ones predictably.
Compatibility kinds#
| Backward compat | Forward compat | |
|---|---|---|
| Definition | new server can read old client | new client can read old server |
| Example | optional field added | client tolerates unknown fields |
| Required by | always | usually |
Versioning strategies#
flowchart TB
subgraph URI[URI versioning]
A1["/v1/orders"]
A2["/v2/orders"]
end
subgraph Header[Header versioning]
B1["Accept: application/vnd.x.v2+json"]
end
subgraph Param[Query param]
C1["?version=2"]
end
subgraph Evol[Evolution - no version]
D1[Add fields only<br/>tolerate unknown<br/>never break]
end
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class A1,A2,B1,C1,D1 service;
| Strategy | Pros | Cons |
|---|---|---|
URI /v1/... |
obvious, cache-friendly, easy mental model | URL churn, multiple impls |
Header Accept |
clean URLs, content-negotiated | hard to test in browser, less visible |
| Query param | trivial to test | not idiomatic, cache-noisy |
| Evolutionary (no version) | no breaking changes, single codebase | slower change cadence |
Most public APIs default to URI versioning; gRPC + Protobuf use evolution.
What's safe to change#
| Change | Safe? |
|---|---|
| Add new optional field | yes |
| Add new endpoint | yes |
| Add new enum value | yes - clients must tolerate |
| Rename field | breaking |
| Remove field | breaking |
| Tighten validation | breaking |
| Loosen validation | safe |
| Change semantic of existing field | breaking |
Sunset window#
sequenceDiagram
participant A as v1 clients
participant Srv as Server
participant B as v2 clients
Note over Srv: v2 launched
A->>Srv: /v1/...
Srv-->>A: 200 + Sunset: Wed, 30 Jun 2027 + Deprecation: true
Note over A: client devs migrate
B->>Srv: /v2/...
Srv-->>B: 200
Note over A,Srv: 12 months later
A->>Srv: /v1/...
Srv-->>A: 410 Gone
- Announce deprecation; emit
Deprecation: true+Sunset:headers (RFC 8594). - 6-12 month window typical for paid customers; 3 months for SDK-owned clients.
gRPC + Protobuf rules#
- Don't reuse field numbers.
- Don't change field types.
- New fields → next free tag, with defaults.
- Removed fields → reserve the tag number forever.
- Reorganise messages only inside the same package version.
REST: error contract#
- Use a structured error envelope (
Problem DetailsRFC 7807): - Stable codes + stable types - adding new codes is OK if old clients already handle "unknown".
Pagination, IDs, filters#
- Pagination: cursor-based, opaque. Stable across additions.
- IDs: prefix to identify type (
ord_abc123) - easy to grep, hard to confuse. - Filters: support a stable subset; new filters add to the spec, never re-purpose.
Anti-patterns#
- "v2 means v1 is dead today" - clients can't react that fast.
- Breaking semantics in a "patch" - never.
- Multiple versions with shared mutable state - fix bugs in one and not the other.
- Header version negotiation behind a CDN that strips headers.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
CDN | edge caching for static assets | cdn |
HLD |
CRDTs | commutative replicated data types | crdts |
HLD |
API versioning & evolution | URI / header versioning, sunsetting | api-versioning-evolution |
LLD |
REST API design | verbs, statuses, pagination, errors | rest-api-design |
LLD |
Behavioural patterns | Strategy, Observer, State, Command, Chain | behavioral-patterns |
Quick reference#
Practical playbook#
- Default to evolutionary changes for as long as possible.
- Bump major version only when behaviour changes or fields are removed.
- Run N + N-1 in production; never N-2.
- Track per-customer adoption per version; only sunset once usage is low.
Tooling#
- OpenAPI / gRPC contracts in a versioned repo.
- Schema diff tools (oasdiff for OpenAPI, buf for Proto).
- Contract tests (Pact) run on PRs to surface accidental breaks.
Deprecation comms#
Deprecation: true+Sunset: <date>response headers (RFC 8594).- API portal banner + changelog feed.
- Email customers at T-90, T-30, T-7 days.
API governance at scale#
- One review group for breaking changes.
- Per-team API owners.
- Lint rules in CI for forbidden changes (renames, type changes).
Refs#
- Stripe API versioning (date-based versions; legacy + adapter pattern).
- GitHub API media-type versioning.
- Google AIPs (API improvement proposals).
- "API Design Patterns" - JJ Geewax.
- RFC 8594 (Sunset header).
FAQ#
What are the common API versioning strategies?#
URI versioning like /v1/users, header versioning with Accept or a custom header, query parameter versioning, and evolutionary versioning that avoids breaking changes by adding fields only.
URI versioning vs header versioning, which is better?#
URI is simpler, discoverable in logs, and easy to cache. Header versioning keeps URLs stable and is favored by hypermedia purists. Most public APIs at scale pick URI for operational simplicity.
What counts as a breaking change in an API?#
Removing or renaming a field, changing a field type, tightening validation, changing an error code semantically, or reordering positional arguments are breaking. Adding optional fields or endpoints is not.
How long should you support an old API version?#
Public APIs typically support at least 12 months after deprecation announcement with a sunset header. Enterprise contracts may extend that to 24 months or longer depending on SLA commitments.
How do you handle API evolution without versioning?#
Use tolerant readers, additive changes only, feature flags per client, and Postel's principle. Many large APIs like Stripe pin clients to a version on first call and run multiple versions side by side.
Related Topics#
- API Gateway: API gateways enforce versioning policies and route traffic across API versions
- HTTP Protocols: HTTP semantics and headers underpin all API versioning strategies
Further reading#
Curated, high-credibility sources for going deeper on this topic.