
Designing APIs You Won't Regret in Six Months
A few principles we follow when designing APIs that need to evolve without breaking clients.
03
Apr

Every API is a promise. Once external clients or internal services start depending on your endpoints, changing them becomes expensive. The decisions you make in the first few weeks of API design will either give you room to evolve gracefully or lock you into patterns that make every future change a negotiation.
Here are the principles we follow when building APIs that need to last.
Versioning: Pick a Strategy and Commit
There are three common approaches to API versioning: URL-based (/v1/users), header-based (Accept: application/vnd.api+json;version=1), and no explicit versioning with backward-compatible evolution.
URL-based versioning is the simplest to implement and the easiest for consumers to understand. It adds visual clarity when debugging and makes it obvious which version a client is using. The tradeoff is that major version bumps require maintaining parallel codepaths or redirects.
Header-based versioning is cleaner in theory but harder to test and debug - you cannot simply paste a URL into a browser to check a response.
Our default recommendation: start with URL-based versioning. It is pragmatic, well-understood, and avoids unnecessary cleverness.
Consistent Naming Conventions
Choose a naming convention and apply it everywhere without exception. We use snake_case for JSON field names, plural nouns for resource collections (/users, /invoices), and kebab-case for URL paths where multi-word resources are needed.
Inconsistency is the enemy. If some endpoints return created_at and others return createdAt, every consumer has to handle both - and every new developer on the team has to guess which convention to follow.
Document your conventions explicitly. A one-page style guide for your API prevents dozens of inconsistency bugs.
Predictable Error Responses
Every error response from your API should follow the same shape. We use a consistent structure:
A machine-readable error code (not just the HTTP status)
A human-readable message suitable for logging
An optional details field for validation errors with per-field information
Consumers should never have to write different error-handling logic for different endpoints. If your 400 response looks different from /users than it does from /invoices, you have a design problem.
Pagination: Cursor-Based by Default
Offset-based pagination (page=3&limit=20) is simple but breaks when the underlying data changes between requests - items get skipped or duplicated. For any dataset that is actively being written to, cursor-based pagination is more reliable.
Cursor-based pagination uses an opaque token (usually an encoded ID or timestamp) that points to the last item in the previous page. It handles insertions and deletions gracefully and performs better on large datasets because the database does not need to count and skip rows.
Use offset-based pagination only for small, static datasets where simplicity outweighs correctness.
Backward Compatibility Rules
Once an endpoint is published, follow these rules:
Adding new fields to a response is safe
Removing or renaming fields is a breaking change
Adding optional parameters to a request is safe
Making a previously optional parameter required is a breaking change
Changing the type of a field is always a breaking change
If you need to make a breaking change, introduce it under a new version and give consumers a documented migration path with a clear deprecation timeline.
Deprecation Policies
When deprecating an endpoint or field, communicate it through three channels: a Deprecated response header, a note in the documentation, and direct communication to known consumers. Set a concrete sunset date - "deprecated" without a timeline means "we will maintain this forever because no one will bother migrating."
Documentation Alongside, Not After
Write API documentation as you build, not as a separate project after launch. Tools like OpenAPI (Swagger) let you define endpoints in a structured format that generates interactive documentation automatically.
If the documentation does not exist when the endpoint ships, it will never exist. Treat docs as a deliverable, not an afterthought. Future you - and every developer who integrates with your API - will be grateful.
