We have reviewed hundreds of API architectures. The ones that scale to millions of users share common design principles. The ones that collapse share common mistakes. Here are the principles, distilled from real production systems.
Principle 1: Design for the Consumer, Not the Database
The most common API mistake is exposing your database schema as your API. Your users do not care about your normalized tables. They care about completing tasks. Design your API around use cases, not entities.
Bad: GET /users/123 then GET /users/123/orders then GET /orders/456/items — three calls to show one page.
Good: GET /users/123/dashboard — one call that returns everything the consumer needs for that view. Yes, this means your API has some denormalization. That is the point.
Principle 2: Pagination Is Not Optional
Every list endpoint must be paginated from day one. Not "we will add it when we need it." By the time you need it, you have clients depending on the unpaginated response, and adding pagination is a breaking change.
Use cursor-based pagination, not offset-based. Offset pagination breaks when data is inserted or deleted between requests. Cursor pagination is stable, performant, and works correctly with real-time data.
Principle 3: Idempotency Keys for All Mutations
Network requests fail. Clients retry. Without idempotency keys, a retried POST creates a duplicate order, a duplicate payment, a duplicate user. Every write endpoint should accept an idempotency key. If the same key is sent twice, return the original response without re-executing the action.
Stripe does this. Shopify does this. If your payment API does not, you will eventually charge someone twice and learn this lesson the expensive way.
Principle 4: Rate Limiting with Empathy
Rate limit everything. But do it with clear communication. Return 429 Too Many Requests with a Retry-After header telling the client exactly when to try again. Include rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset) on every response so clients can self-regulate.
Principle 5: Version from Day One
Put the version in the URL: /v1/users. Not in headers. Not in query parameters. In the URL. It is visible, cacheable, and unambiguous. When you release v2, v1 keeps working. Your existing clients do not break. This is non-negotiable.
Principle 6: Errors Are a Feature
Your error responses should be as well-designed as your success responses. Every error needs: a machine-readable error code, a human-readable message, a link to documentation, and the request ID for debugging. {"error": "something went wrong"} is not an error response. It is a cry for help.
The Compound Effect
Each principle seems simple in isolation. Together, they compound into an API that is a pleasure to integrate with, scales predictably, and creates a moat of developer loyalty. The APIs that developers love become the platforms that win.