Code on a screen showing API endpoints
EngineeringApril 30, 20263 min read

What Good API Design Actually Looks Like in Production

APIs are long-lived commitments. The decisions made at v1 are still being worked around at v3. Here's how to make fewer of those mistakes.

APIs are long-lived commitments. A decision made when building v1 of an endpoint — how it's named, what it returns, how it handles errors, what it does with fields that don't yet exist — is a decision that external consumers will build against. Once external code depends on a behavior, changing that behavior has a cost, even if the original behavior was wrong, underspecified, or simply a placeholder that was never meant to be permanent. The teams that treat API design as implementation detail tend to be the same teams doing painful v2 migrations two years later.

Name things for what they mean, not how they work

The most durable API naming decisions are semantic, not structural. An endpoint named /users/{id}/activate means something clearly to a consumer who doesn't know anything about your internal implementation. An endpoint named /users/{id}/set_active_flag tells the consumer about your database column and will feel wrong the moment the implementation changes. The same principle applies to fields. A field called created_timestamp_unix is a leaky implementation detail — it tells the consumer which format the database uses. A field called created_at with a documented ISO 8601 format is a clean contract that can survive a storage migration.

Developer working on backend API code
Semantic naming decouples your API contract from your implementation details.

Error responses are part of the contract

Most API design attention goes to the happy path. The error path gets whatever seemed reasonable at the time — usually a different shape depending on which engineer wrote that endpoint, using HTTP status codes inconsistently and returning error messages that are useful for debugging but meaningless for programmatic handling. A consumer who wants to display a useful error message to their user, or retry on specific failure conditions, or route errors to different handlers, needs a consistent, structured error response. That means one error shape across all endpoints, machine-readable error codes distinct from HTTP status codes, and a message that's human-readable but not the only information a consumer has to work with.

A consumer shouldn't have to parse an English error message to decide whether to retry a request. Error codes are part of the API contract — treat them with the same care as field names.

Versioning: when and how

API versioning is a policy decision, not a technical one. The technical mechanisms — URL path versioning, header versioning, parameter versioning — are all defensible. The policy question is: what constitutes a breaking change, and how much notice do consumers get before one is deployed? Additive changes — new optional fields, new endpoints, new optional parameters — are generally safe to ship without a version bump if the contract is clear that consumers should ignore fields they don't recognize. Removing fields, changing field types, changing error shapes, changing authentication requirements — these are breaking changes and require a versioned path and a deprecation window. Writing that policy down before you have consumers is far easier than reconstructing it retroactively when the first consumer complains about a broken integration.

API documentation and testing interface
API versioning policy should be documented before the first external consumer goes live.

The teams with the cleanest API histories are not the ones with the most elaborate versioning strategies. They're the ones who asked, before shipping each endpoint, whether the name would still make sense when the implementation changed, whether the error shape was consistent with the rest of the API, and whether a consumer could reasonably build against this contract without knowing anything about the internals. Those three questions, asked consistently at review time, eliminate most of the technical debt that API v2s exist to clean up.

API DesignEngineeringBackendBest Practices