The Choices Nobody Notices Until They Hurt

A good API is invisible. Clients get what they asked for, errors say something useful, and nothing breaks when you ship a new feature. The bad ones haunt you: a versioning scheme bolted on six months late, error payloads that change shape between endpoints, field names you'd never choose today but can't change because twelve clients depend on them.

Most of these problems have nothing to do with technology. They're design decisions made (or deferred) on day one.

Start with versioning before you need it. The cheapest versioning strategy is a URL prefix — /v1/, /v2/ — because every HTTP client and proxy in existence understands it without custom header logic. Header-based versioning (API-Version: 2) is cleaner in theory but consistently surprises consumers who expect URLs to be stable identifiers. Pick the boring approach: it will outlast your opinions about REST purity. Add the prefix on day one, even if you only ever ship one version. The discipline of thinking "what would a v2 change look like?" surfaces backward-compatibility risks early.

Make errors a first-class citizen. An HTTP status code tells you the category of failure; it doesn't tell you why. Standardise on a consistent error shape — something like RFC 9457 Problem Details — and use it everywhere from day one. A response that says {"type": "validation_error", "detail": "email must be a valid address", "field": "email"} lets client code branch on type without parsing human prose. Inconsistent error shapes force consumers to write defensive spaghetti and generate angry Slack messages.

Additive changes are free; deletive changes are expensive. You can add a new field to a response payload without breaking anything. You cannot remove one, rename one, or change its type without a version bump or a very apologetic deprecation notice. This asymmetry should inform how you design your fields upfront. Prefer names that are specific (created_at over date, user_id over id), and resist the temptation to return everything you have — the fields you publish become obligations.

Document the contract, not the implementation. An OpenAPI spec written alongside the code — not after — forces you to treat the API as a product with consumers, not a convenience wrapper around a database. Tooling like Scalar or Stoplight can render it; more importantly, generating a spec from your routes catches drift before it ships.

None of this is glamorous work. It's the kind of thing that gets skipped when velocity is the only metric. But every hour spent on these decisions early buys back weeks of backwards-compatibility archaeology later.