API Design Decisions That Age Well (And the Ones That Don't)
An API is a promise. The decisions you make when you ship version one will constrain every version after it. Here's how to make promises you can keep.
In 2008, Twitter shipped an API that was simple, powerful, and used by thousands of developers within months. By 2012, that API had become one of the most significant constraints on Twitter's engineering roadmap — nearly every product decision that touched the API surface required a careful analysis of backward compatibility implications, a deprecation timeline, and a migration path for the apps that relied on the old behavior. The API that made Twitter's ecosystem possible also made Twitter's product evolution significantly slower and more expensive.
This is not a story about Twitter making mistakes. It's a story about what happens when a product succeeds: the API decisions that were pragmatic at launch become architectural constraints that must be honored indefinitely. The cost of those decisions is paid by every engineer who works on the API surface, forever.
The Backward Compatibility Tax
Public APIs impose a backward compatibility obligation that internal code does not. You can refactor internal code whenever the benefit justifies the cost — nobody outside your team is building on it. Public API consumers build integrations that represent weeks or months of their own engineering work, and they expect those integrations to keep working as your product evolves.
This means that every decision you make in a public API — field names, response shapes, error codes, pagination models, authentication mechanisms — is a decision you will likely be honoring for years. The cost of a poorly-named field in an internal module is a refactor. The cost of a poorly-named field in a public API is years of that field living in the contract alongside whatever better name you eventually introduce.
Decisions That Age Well
Resource-oriented design. APIs organized around resources (nouns) rather than operations (verbs) tend to age better because resources map more naturally to how the underlying data model evolves. An endpoint like /users/{id}/reviews remains semantically coherent as the product evolves around it. An endpoint like /getReviewsForUser starts to feel dated and becomes awkward to extend.
Explicit versioning. Whether through URL path versioning (/v1/, /v2/) or header versioning, explicit versioning allows you to make breaking changes without breaking existing consumers. The teams that resist versioning because it feels like premature complexity universally regret it when they need to make the first breaking change.
Pagination from day one. Collections that return all results today will return too many results tomorrow. Building pagination into collection endpoints from the beginning — even when the collection is small enough that it doesn't matter yet — avoids the painful retrofitting that comes when a collection grows past the threshold where returning everything is no longer acceptable.
Rich error responses. Error responses that include a machine-readable error code, a human-readable message, and a link to documentation are dramatically easier for API consumers to handle than generic HTTP status codes. Investing in error taxonomy early means API consumers can build reliable error handling rather than guessing at status code semantics.
Decisions That Don't Age Well
Exposing your internal data model directly. API responses that mirror your database schema directly couple your public contract to your internal implementation. When you need to change the internal model — splitting a table, renaming a concept, restructuring a relationship — you're simultaneously forced to change the public API or maintain a translation layer. Designing API responses as a presentation layer separate from the data model adds implementation complexity upfront and prevents costly constraint coupling later.
Implicit ordering and filtering defaults. Implicit defaults that aren't documented become implicit contracts. If your collection endpoint returns results sorted by creation date by default and consumers rely on that ordering without explicitly requesting it, changing the default sort becomes a breaking change even though you never promised it. Be explicit about defaults, document them, and version them if they need to change.
Code Review for API Design
API-facing changes deserve a different review standard than internal changes. The review checklist for a new API endpoint should include: Is this backward compatible with existing consumers? Are the field names and response shapes consistent with the rest of the API? Are errors well-defined and documented? Is pagination handled for any collection that could grow unboundedly? Is the authentication model consistent? These questions don't require deep code expertise — they require a different kind of attention than typical implementation review, and they're worth treating as a distinct review step for any change that touches the public API surface.
Try CodeMouse on your next PR
Free AI code review on every pull request. Bring your own API key — no subscription needed.
Install on GitHub — Free