API Design Best Practices
A good API feels boring in the best way: predictable, consistent, and hard to misuse. The patterns below make your API easier to ship, safer to evolve, and cheaper to support — whether you build it with REST, GraphQL, or RPC.
The throughline is consistency. Clients can guess how an unfamiliar endpoint behaves when every endpoint names, paginates, errors, and authorizes the same way.
TL;DR
- Design around resources and workflows, not the endpoint you happen to need today.
- Be consistent: naming, status codes, error shapes, pagination, and auth.
- Prefer additive changes; treat breaking changes as a last resort.
- Make the right usage easy (good defaults) and the wrong usage obvious.
Quick Example
A create endpoint that states its contract clearly — correct method, 201, and a Location header:
Start With Contracts
Before choosing REST vs GraphQL, decide what your API must guarantee.
Resource Modeling
Use nouns for resources and HTTP verbs for actions.
Consistent Error Shapes
Pick one error envelope and use it everywhere, with a stable machine-readable code:
See Error Handling.
Pagination (Cursor-First)
Offset pagination breaks under inserts and slows down at scale. Prefer cursors.
Idempotency
Network timeouts happen, and clients retry. Don't let retries create duplicates:
- Accept an
Idempotency-Keyheader on create endpoints and de-dupe on it. - Treat
PUTas idempotent by design.
Versioning
Avoid versioning until you need it, then pick a strategy that fits how clients update.
If you need /v2, you also need a client migration plan. See API Versioning.
Authorization & Multi-Tenancy
Authorization is a data constraint, not a middleware checkbox.
Authenticate, authorize at the resource level, always scope queries by tenant, and audit writes. See API Security.
Best Practices
- Docs as contract: maintain an OpenAPI spec; keep examples close to real behavior (tests, fixtures, codegen).
- Good defaults: safe pagination limits, sensible timeouts, least-privilege scopes.
- Observability: request IDs, structured logs, latency/error metrics.
Common Mistakes
Inconsistent shapes across endpoints
Authorization by obscurity
FAQ
REST or GraphQL?
REST shines when endpoints map cleanly to resources and HTTP caching matters. GraphQL shines when clients need flexible response shapes across many UI surfaces — at the cost of more schema and cost-control work. See REST API Design and GraphQL.
When should I introduce versioning?
Only for an unavoidable breaking change. Additive changes (new optional fields, new endpoints) don't need a version bump. When you must version, be consistent — URL versioning is the simplest.
How do I keep an API backward compatible?
Add, don't change or remove: new optional fields, new endpoints, and tolerant readers. Never repurpose an existing field's meaning, and never make a previously optional field required.
What's the single highest-leverage design choice?
Consistency. A predictable, uniform API is easier to learn, document, test, and evolve than a clever but irregular one.
Related Topics
- REST API Design — REST specifics
- GraphQL — The main alternative
- Error Handling — Consistent failures
- Rate Limiting — Protecting endpoints
- API Security — Authz and tenant scoping
- API Documentation — OpenAPI as contract