Engineering February 1, 2026 · 6 min read

Designing APIs That Last

APIs are contracts. Unlike internal code that you can refactor freely, a published API creates expectations that are expensive to break. Consumers build applications, integrations, and business processes on top of your API, and every breaking change forces them to invest time and effort in adaptation. Designing APIs that last means making decisions today that will still make sense in three years, even as your system evolves significantly beneath the surface.

Consumer-First Design

The most common API design mistake is building the API as a thin wrapper around your internal data model. This couples consumers to your implementation details, making it impossible to refactor your internals without breaking the API. Instead, design your API from the consumer's perspective: what do they need to accomplish, what information do they need, and what's the most natural way to express those needs?

We start every API design with use case documentation — not schema definitions or endpoint lists, but narrative descriptions of what each consumer type needs to do. A mobile app fetching a user profile has different needs than a batch processing system syncing user data. The API should serve both well without forcing either to work around unnecessary complexity.

Resource Modeling

Good resource modeling is the foundation of a lasting API. Resources should represent business concepts that are stable over time, not database tables or internal service boundaries that may change. A "customer" resource should expose the information consumers need about a customer, regardless of whether that data lives in one database or seven microservices internally.

We follow several principles for resource modeling: resources should be self-describing (a consumer should be able to understand a resource representation without consulting external documentation), relationships between resources should be navigable (use hyperlinks or explicit relationship fields, not implicit ID conventions), and resource granularity should match consumer access patterns (don't force consumers to make five calls when one would suffice, but don't return megabytes of data when they need a single field).

Versioning Strategy

Every API needs a versioning strategy, even if you hope to never use it. The debate between URL-based versioning (e.g., /v2/users), header-based versioning, and content negotiation has been thoroughly discussed elsewhere. Our recommendation: use URL-based versioning for its simplicity and debuggability, with major versions only. Minor changes that are backward-compatible don't require a version bump.

The more important question is: what constitutes a breaking change? We maintain a clear definition: adding new fields to response objects is not breaking. Adding new optional query parameters is not breaking. Removing fields, changing field types, altering error response formats, or changing the semantic meaning of existing fields — these are breaking changes that require a new version.

The Additive-Only Rule

Within a major version, we follow the additive-only rule: you can add new endpoints, add new fields to responses, add new optional parameters, and add new enum values, but you cannot remove or modify anything that existing consumers might depend on. This rule is simple to understand, easy to enforce in code review, and provides a clear guarantee to consumers: your integration won't break as long as you stay on the same major version.

Error Handling as a Feature

Error responses are part of your API's contract, and they deserve the same design attention as success responses. A well-designed error response tells the consumer exactly what went wrong, why, and what they can do about it. A poorly designed one returns a generic 500 with no actionable information, forcing the consumer to contact support.

We use a consistent error response structure across all endpoints: a machine-readable error code (not the HTTP status code, which is too coarse for programmatic handling), a human-readable message suitable for logging, and when applicable, a pointer to the specific field or parameter that caused the error and a link to relevant documentation. This structure enables consumers to build robust error handling without guessing at the meaning of cryptic error strings.

Validation and Helpful Feedback

Input validation errors should be specific and comprehensive. Rather than returning on the first validation failure, collect all validation errors and return them together. Each error should identify the offending field, explain the constraint that was violated, and ideally suggest the correct format. Consumers shouldn't need to play whack-a-mole, fixing one validation error at a time through repeated submissions.

Pagination, Filtering, and Sorting

List endpoints are where API design most often goes wrong. The initial implementation returns all records, which works fine with 50 test records and breaks catastrophically with 50,000 production records. Design list endpoints with pagination from day one — retrofitting pagination onto an existing endpoint is a breaking change.

We prefer cursor-based pagination over offset-based for most use cases. Cursor pagination is more performant for large datasets, provides stable results when data is being modified concurrently, and naturally handles the "where was I?" problem for consumers that process data in batches. The trade-off is that consumers can't jump to an arbitrary page, which is rarely needed for API consumers (as opposed to UI pagination).

Filtering and sorting should follow consistent conventions across all list endpoints. Define a clear syntax for filter expressions, document the available fields and operators, and ensure that the same field name and filter syntax works the same way everywhere in your API.

Documentation That Stays Current

API documentation that's out of date is worse than no documentation — it actively misleads consumers. We generate documentation from the API specification (OpenAPI/Swagger) rather than maintaining it separately, ensuring that the documentation always reflects the current implementation. The specification is the source of truth, and it's validated in CI to ensure it matches the actual API behavior.

Beyond reference documentation, we invest in guides that explain common integration patterns, provide working code examples in popular languages, and walk through end-to-end workflows. These guides are versioned alongside the API and updated as part of the release process, not as an afterthought.

Rate Limiting and Fair Use

Rate limiting protects your API from abuse and ensures fair access for all consumers. But rate limiting is also part of your API contract and needs the same design thought as any other feature. Communicate rate limits clearly in documentation and response headers. Provide different tiers for different consumer classes. Return informative 429 responses that tell the consumer exactly when they can retry. And never apply rate limiting silently — consumers should always know when they've been throttled and why.

Key Takeaways

  • Design APIs from the consumer's perspective, not as a mirror of your internal data model
  • Follow the additive-only rule within major versions — add freely, never remove or modify
  • Invest in error responses that are specific, actionable, and consistently structured
  • Implement cursor-based pagination from day one — retrofitting it later is a breaking change
  • Generate documentation from your API specification to ensure it stays current
  • Treat rate limiting as a feature with clear communication, not a silent enforcement mechanism

An API that lasts is one that respects its consumers. Every design decision should be evaluated through the lens of: "will this still make sense for someone integrating with our API a year from now?" That discipline — thinking beyond the immediate use case to the long-term consumer experience — is what separates APIs that endure from those that accumulate workarounds until they're replaced.