What you need to do
The practical activities and techniques you need to master – what to practice, what to get good at, and what to keep doing.
Choosing API type
The first design decision isn't endpoint names – it's which API paradigm fits your use case. REST for public APIs and simple CRUD – universally understood, excellent tooling, cacheable by default. RPC (gRPC, tRPC) for internal service-to-service communication or when your frontend and backend share a TypeScript codebase. GraphQL when clients need flexible queries across complex, nested data. Webhooks for event-driven integrations where your system pushes data to external consumers. Most products use REST for 90% of their needs.
Authentication and authorization
API keys for server-to-server communication – simple, no user context needed. Session cookies for web applications where the browser handles authentication automatically. JWTs for stateless authentication in distributed systems. OAuth 2.0 when third parties need delegated access. Don't invent your own auth scheme. Pick the pattern that matches your use case and use a well-tested library. The most common mistake is starting with JWTs when session cookies would be simpler and more secure.
Pagination
Every list endpoint needs pagination – unbounded result sets kill performance and break clients. Cursor-based pagination (pass the last item's ID as a cursor) is the most robust approach: stable results when data changes, no performance degradation on deep pages, and works with real-time data. Offset-based pagination (page=3&limit=20) is simpler but breaks when items are inserted or deleted between requests. Use consistent field names across every endpoint: cursor, limit, hasMore. Clients should write the pagination logic once and it works for every resource.
Errors
Every API error should return a consistent shape – a status code, an error code (machine-readable), a message (human-readable), and optionally a details array for field-level validation errors. Never return 200 with an error message in the body. Never expose stack traces or internal implementation details. Use problem+json (RFC 9457) as a standard error format. Document every error code your API can return. Consistent errors are what separate an API that developers love from one that makes them write workaround after workaround.
Rate limiting
Rate limiting protects your API from abuse and ensures fair access. Use token bucket or sliding window algorithms – most frameworks have middleware for this. Return 429 (Too Many Requests) with a Retry-After header when limits are exceeded. Include rate limit headers in every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Differentiate limits by tier – free users get 100 requests/minute, paid users get 1,000.
API evolution
Never make breaking changes to a published API. Add new fields, don't rename or remove existing ones. Add new endpoints, don't change the behavior of existing ones. When you must make breaking changes, use API versioning – URL path versioning (/v1/users, /v2/users) is the simplest and most visible approach. Deprecate before removing: mark the old version as deprecated, give clients a migration timeline, monitor usage, and remove only when traffic drops to zero.
Frameworks to know
The mental models and structured approaches that guide how you think about this skill – not rules to follow, but lenses to see through.
REST
Representational State Transfer – the dominant API paradigm for web applications and public APIs. Design around resources (nouns), not actions (verbs): /orders, /users, /products. Use HTTP methods as intended: GET reads, POST creates, PUT replaces, PATCH updates, DELETE removes. Return appropriate status codes: 200 for success, 201 for created, 400 for client errors, 404 for not found, 500 for server errors. Make operations idempotent where possible. The goal is an API that a developer can guess correctly before reading the docs. The Richardson Maturity Model defines four levels: Level 0 (one endpoint), Level 1 (resources), Level 2 (HTTP methods), Level 3 (hypermedia). Most production APIs sit at Level 2.
RPC
Remote Procedure Call – APIs designed as function calls rather than resource operations. gRPC uses Protocol Buffers for schema-enforced contracts with binary serialization – ideal for internal service-to-service communication where performance matters. tRPC provides full type safety when your frontend and backend share a TypeScript codebase – zero schema overhead, types inferred directly from server code. RPC shines when your API represents actions ('processPayment', 'sendNotification') rather than resources, or when you need streaming support.
GraphQL
A query language that lets clients request exactly the data they need in a single request. Clients define the shape of the response – no over-fetching (getting fields you don't need) and no under-fetching (making multiple requests for related data). GraphQL uses a strongly-typed schema that defines every type, query, and mutation. Best suited for complex UIs that aggregate data from multiple entities – mobile apps, dashboards, and client-heavy applications. Trade-offs: caching is harder than REST, N+1 query problems require DataLoader, and authorization per field adds complexity.
Webhooks
HTTP callbacks that push data to external systems when events occur – the foundation of event-driven integrations. When something happens in your system, you POST a payload to a URL the client configured. Design them like events, not commands: 'order.created', 'payment.failed', 'user.updated'. Include a timestamp, event type, and the full resource in the payload. Implement retry logic with exponential backoff for failed deliveries. Sign every payload with HMAC so clients can verify authenticity. Provide a webhook testing endpoint so developers can verify their integration without triggering real events.
Good vs. bad
Common mistakes paired with what experienced practitioners do instead – and why the product builder approach changes the game.
API design is where product builders make or break the developer experience of their product. A well-designed API is a force multiplier – it enables integrations, third-party apps, and automations that you never planned for. A poorly designed API becomes a support burden that consumes more engineering time than the features it exposes.
REST is the right default for almost everything. I've watched teams adopt GraphQL because it sounded modern, then spend months solving problems they wouldn't have had with REST – caching, N+1 queries, authorization per field, query cost analysis. GraphQL solves a real problem (over-fetching in complex UIs), but if your API is CRUD over a dozen resources, REST with well-designed endpoints is faster to build, easier to cache, and simpler to secure.
tRPC is the most underrated tool in the TypeScript product builder's toolkit. When your frontend and backend share a codebase, tRPC gives you full type safety with zero schema overhead – no OpenAPI spec, no code generation, no separate type definitions. You change a return type on the server and the client shows a type error immediately. For internal APIs in a monorepo, it's strictly better than REST.
The Stripe API is the best teacher of API design. Study their naming conventions (always plural resources, always snake_case), their error handling (every error tells you what went wrong AND how to fix it), their pagination (cursor-based, consistent everywhere), and their idempotency keys (safe retries built into the protocol). You don't need to read a book on API design – just study Stripe's API reference and apply the same principles.
I've seen more production incidents caused by API versioning mistakes than by any other API design decision. The rule is simple: never make breaking changes to a published endpoint. Add new fields, add new endpoints, deprecate old ones. If you must make a breaking change, version the URL (/v1/users → /v2/users) and support both until migration is complete. The expand-and-contract pattern from database migrations applies perfectly to API evolution.
Go deeper
Books to deepen your understanding beyond what fits on this page.
The most comprehensive guide to API design – naming conventions, resource identification, standard methods, pagination, filtering, and error handling. Written by a Google API design expert, distilling Google's internal API guidelines.
The chapter on data encoding and API evolution is essential reading – how to evolve schemas without breaking clients, the trade-offs between schema-on-read and schema-on-write, and why backward and forward compatibility matter.
Explore related
Skills that complement this one – practice them together to build a stronger product builder toolkit.