11 lessons

Versioning & Evolution

The longest-lived part of the contract. Additive change, enum evolution, deprecation as a process, consumer telemetry before removal, schema-first vs code-first, docs and SDKs.

RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

Every lesson below names the consumers, the design question and the guarantee before recommending anything. Recommendations come with what they cost, when not to use them, and how they evolve.

Versioning: What a Version Even Promises

URI versions, header versions, date-pinned versions, or no versions at all — the strategies differ less than the arguments suggest. What matters is what a version promises, what minting one costs, and why additive evolution is the strategy every good API uses between versions.

Q · What does a version number actually promise consumers, and which changes are worth the price of minting one?
Backward Compatibility: The Real Rules
▶ lab

The safe list and the breaking list are shorter and stranger than intuition says. Adding an optional field is safe; making an optional field required is not; tightening validation, changing a default, or changing what a value means breaks clients without touching a single field name.

Q · Which changes to this contract can ship today without breaking a single existing consumer — and which only look like they can?
Enum Evolution: The New Value That Broke Old Clients
▶ lab

You add `suspended` to a status enum — additive, surely safe. Every old client that switched exhaustively over the closed set now throws, hides the record, or worse, treats it as `active`. Enums are the sharpest edge of compatibility, and the fix is a contract clause, not a code change.

Q · When the server returns a value this client has never heard of, what does the contract say the client must do?
Removing Fields Without Removing Consumers
▶ lab

Addition is a deploy; removal is a program. Introduce the replacement, measure who still reads the old field, deprecate it visibly, run a real migration window, and remove only when telemetry — not hope — says zero. The steps are boring; skipping any of them is an outage.

Q · What has to be true — and measured — before a field that consumers once read can safely disappear?
Deprecation as a Process, Not a Label

Marking something deprecated changes nothing; deprecation is a campaign with artifacts — announcement, migration guide, machine-readable signals, telemetry, a deadline someone will enforce — and a finish line. A deprecation nobody plans to complete is just an apology in advance.

Q · When this contract element must go away, how does every affected consumer find out, migrate, and confirm — before the deadline does it for them?
Consumer-Driven Evolution: Telemetry Before Breakage

"Can we remove this?" is a telemetry query, not a debate. Per-consumer, per-field usage attribution turns evolution decisions from opinions into evidence — and the 12% of mobile users on an old build stop being invisible exactly when you can count them.

Q · Before this contract element changes, do we know — per consumer, with numbers — who depends on it and how much?
API Migration: Running the Change End to End

Every breaking change, whatever its label, runs the same program: ship the new surface, support both, move consumers with telemetry and deadlines, deprecate, remove. The compatibility matrix — which client works against which API — is the map; the burn-down is the engine.

Q · How does every consumer get from the old contract to the new one without an outage on either side of the change?
Schema-First vs Code-First

Whether the contract file or the handler code comes first matters less than which one is the enforced source of truth. Schema-first buys review-before-build and cross-team parallelism; code-first buys iteration speed; drift — where the served API and the described API diverge — is the failure mode both must engineer away.

Q · Which artifact is the source of truth for this contract, and what mechanically prevents the served API from drifting away from it?
OpenAPI: Describing the Contract, Not Designing It

OpenAPI captures paths, operations, schemas and security schemes in a machine-readable file — which earns you linting, diffing, mocks, generated clients and always-current reference docs. What it cannot capture is most of what this domain teaches: guarantees live in prose, and the spec is the skeleton they hang on.

Q · What can a machine-readable description of this API do for its consumers and its governance — and which parts of the contract will it never contain?
Documentation Is Part of the Contract

For every consumer you never meet, the docs are the API. What must be documented is exactly what consumers are forced to assume — auth, errors, pagination, rate limits, idempotency, guarantees — and the examples are the most-executed code you ship. Undocumented behavior gets reverse-engineered and depended on anyway.

Q · Can a developer who will never talk to your team integrate correctly — including failure handling — from the docs alone?
SDK Design: The Contract's User Interface

`payments.create({...})` versus hand-rolled HTTP is the visible part. The invisible part is what the SDK owns on behalf of every consumer — retries with idempotency keys, pagination iterators, typed errors, timeouts — and how it is built to survive the API evolving underneath it. A strict SDK turns your safe changes into their crashes.

Q · What should the client library do for every consumer — and how must it be built so the API can keep evolving underneath it?