API Design Cheat Sheet
API design patterns for resources, errors, versioning, and operational guarantees.
Good API design is less about fashionable URL shapes and more about making integration predictable under real production conditions. Clients need to understand what the API exposes, how state changes, which failures are retryable, and what guarantees exist around authentication, ordering, and compatibility.
Start with the resource model
An API is easiest to use when its surface matches the domain. Resources should represent durable concepts such as users, invoices, deployments, or messages. Actions then become state transitions on those resources. That keeps the interface legible for both humans and machines.
REST works well when the domain naturally looks like entities and collections. GraphQL is useful when clients need flexible reads over related data. RPC-style APIs fit workflows where the operation matters more than the noun, such as fraud scoring or video transcoding. The important question is not which style is trendy. It is whether the interface makes common tasks simple without hiding important system constraints.
Define behavioural contracts, not just endpoints
A usable API documents more than request and response fields. It states whether writes are idempotent, whether reads are strongly consistent, how pagination behaves while data changes, and what happens when clients retry after a timeout. These details determine whether integrations remain correct under failure.
Idempotency matters most for operations that create money movement, jobs, or other side effects. Stable resource identifiers and request ids let clients retry safely. Pagination needs an explicit strategy. Offset pagination is simple but weak under concurrent writes. Cursor pagination is better for large or fast-moving datasets because it preserves traversal order and avoids skipped or duplicated records.
Security and change management are part of design
Authentication, authorisation, and rate limiting should be visible in the design from day one. An API that is easy to call but hard to secure becomes an operational liability. Use scoped tokens or signed credentials, separate read and write privileges where possible, and make sensitive fields opt-in rather than default.
Versioning should also be deliberate. Backwards-compatible changes usually mean adding optional fields, not renaming or repurposing existing ones. Breaking changes should be rare because the cost is paid by every client team. Clear deprecation windows and telemetry on endpoint usage help retire old behaviour without guesswork.
Error handling should reduce ambiguity
Strong APIs make failures easy to classify. Validation errors should tell the caller what to fix. Authentication failures should be distinct from authorisation failures. Rate limits should include enough metadata for a client to back off intelligently. Transient server errors should avoid pretending the write either definitely happened or definitely did not.
Consistent status codes and machine-readable error bodies matter because many clients will automate around them. A human-friendly message alone is not enough.
A practical checklist
Before shipping an API, confirm five things. First, the nouns match the business domain. Second, the write paths are safe to retry. Third, errors communicate whether the caller should fix input, wait, or escalate. Fourth, the authentication model supports least privilege. Fifth, the evolution path avoids surprising existing clients.
If those basics are strong, the interface will usually feel straightforward even as the system behind it grows more complex.