designing-apis-for-change
← Back to Series

SOFTWARE ARCHITECTURE / SYSTEM DESIGN

Designing APIs for Change

Versioning, compatibility and evolution without breaking consumers.

Key Insight: The API you ship on day one is never the API you'll need in year two. The measure of good API design isn't how well it fits today's requirements — it's how gracefully it can change without breaking every consumer who's already depending on it.

An API is a promise to every system that consumes it, and every consumer you don't control directly makes that promise more expensive to break. The teams that handle this well don't avoid change — they design for it explicitly, from the first version.

Additive changes are (mostly) free; breaking changes are expensive

Adding a new, optional field to a response, adding a new endpoint, adding a new optional request parameter with a sensible default — these are changes existing consumers can ignore entirely, because nothing they depend on moved. Removing a field, renaming a field, changing a field's type or meaning, making an optional parameter required — these break every consumer relying on the old shape, whether or not you intended them to depend on it.

The practical discipline: default to additive changes. When a breaking change is genuinely necessary, that's the signal to version, not to just ship it and hope consumers update in time.

Version the contract, not just the code

A version number in the URL or a header (/v2/orders, or an Accept header with a version) gives consumers an explicit, stable contract to depend on, and gives you room to evolve v2 without breaking everyone still on v1. The alternative — one endpoint whose behavior quietly changes over time — forces every consumer to track your internal changelog just to stay working, which nobody does reliably, and the first anyone hears about a break is a support ticket.

This applies as much to event schemas in an event-driven system as it does to REST APIs. An event that other services consume is exactly as much of a contract as an HTTP endpoint, and deserves the same versioning discipline — a surprising number of teams version their REST APIs carefully and let event schemas drift with no such rigor.

Design for backward compatibility from the start, not as a rescue

It's much cheaper to design a schema that can accept new optional fields gracefully than to retrofit that flexibility after several breaking releases have already happened. Practical patterns: make new fields optional with sensible defaults rather than required; avoid encoding meaning into a field's absence (null and "not present" should mean the same predictable thing); use enums additively — new values should be safely ignorable by consumers that don't recognize them yet, rather than causing a parse failure.

Deprecation needs a real process, not a silent removal

When an old version genuinely needs to go away, the process matters as much as the decision. Announce the deprecation with a concrete timeline, not "eventually." Instrument usage of the deprecated version so you actually know who's still calling it, rather than guessing. Communicate directly with known consumers if you can identify them, not just via a changelog nobody reads. Removing a version that still has real traffic, with no warning, converts a planned change into an incident for someone else's system.

Documentation is part of the contract

An API without documentation that stays current isn't fully specified — consumers end up reverse-engineering behavior from observed responses, which means your *actual* undocumented behavior becomes part of the contract whether you intended it or not, and changing it breaks people even though it was never promised. Treat documentation, ideally generated from a schema (OpenAPI, JSON Schema, or the equivalent for your event contracts) rather than hand-maintained separately, as part of what ships with every version.

Key takeaways

Default to additive, backward-compatible changes; version explicitly when a breaking change is genuinely necessary. Apply the same versioning discipline to event schemas as to REST APIs — both are contracts. Design for optional, additive evolution from the first version, not as a retrofit. Deprecate with a real timeline and real visibility into who's still using the old version, not a silent removal. Keep documentation, ideally schema-generated, current as part of what a version actually is.