APIs are contracts. Once external consumers depend on them, changing anything becomes expensive. The decisions you make early—often quickly, under deadline pressure—become permanent constraints that shape every future feature.
Most API design mistakes aren’t obvious when you make them. They reveal themselves months or years later, when you need to add functionality and discover your earlier choices painted you into a corner.
Exposing Internal Structure
The most common mistake is designing APIs that mirror your database schema or internal object model. It feels efficient—you already have the data structures, why not expose them directly?
The problem emerges when your internal structure needs to change. Database normalization that made sense for storage becomes awkward for API consumers. Internal field names that made sense to your team confuse external developers. Refactoring your data model becomes impossible without breaking every integration.
Design APIs around use cases, not implementation. What do consumers actually need to accomplish? What data do they need together? The API should answer those questions, with a translation layer between the external interface and your internal structures. That translation layer is the cost of flexibility—it lets your internals evolve without breaking the contract.
This applies to error responses too. Exposing raw database errors or internal exception messages leaks implementation details that become implicit contracts. Consumers start parsing error messages, building logic around specific strings, and suddenly you can’t change your error handling without breaking integrations.
Inconsistent Naming and Conventions
APIs develop over time, often by different developers with different preferences. Without strong conventions enforced from the start, inconsistencies accumulate.
One endpoint uses created_at, another uses createdAt, a third uses creation_date. Some endpoints return arrays directly, others wrap them in an object. Some use HTTP status codes semantically, others return 200 for everything with error details in the body. Pagination uses page and limit in one place, offset and count somewhere else.
Each inconsistency is minor. Together, they create an API that feels unprofessional and is harder to use. Developers building integrations can’t rely on patterns—they have to read documentation for every endpoint because nothing is predictable.
Establish conventions early and enforce them. Document the standards: naming conventions (camelCase vs snake_case), date formats, pagination approach, error response structure, authentication patterns. Review new endpoints against these standards before they ship. The cost of consistency is ongoing attention; the benefit is an API that developers can learn once and apply everywhere.
Versioning Avoidance
Teams avoid API versioning because it seems like unnecessary complexity. They plan to just make backwards-compatible changes, add new fields without removing old ones, deprecate gradually.
This works until it doesn’t. Eventually you need to make a breaking change—rename a field that was poorly named, restructure a response that doesn’t fit new requirements, fix a behavior that was always wrong but that consumers now depend on. Without versioning, you’re stuck with a choice between breaking existing integrations or living with bad design forever.
Build versioning in from the start, even if you don’t think you’ll need it. URL path versioning (/v1/users) is explicit and easy to understand. Header-based versioning is cleaner for URLs but less discoverable. Either works—what matters is having the mechanism in place before you need it.
The overhead of maintaining multiple versions is real, but it’s manageable with clear deprecation policies. Support the current version and one previous version. Give consumers time to migrate—six months, a year, whatever fits your context. Then actually deprecate the old version. The discipline of planned deprecation is better than the chaos of unplanned breaking changes.
Overly Granular Endpoints
REST purists sometimes design APIs with maximum granularity—every resource gets its own endpoint, every relationship is navigated separately. To display a user’s dashboard, the client makes fifteen requests: one for the user, one for their settings, one for each widget, one for the data behind each widget.
This creates chatty APIs that perform poorly, especially on mobile networks where latency dominates. It pushes complexity to the client, which now needs to orchestrate multiple requests, handle partial failures, and combine data that the server could have assembled more efficiently.
Design for client use cases, not resource purity. If clients consistently need certain data together, provide an endpoint that returns it together. Aggregation endpoints, compound documents, or GraphQL-style queries let clients get what they need in fewer round trips. The server knows its data better than clients do—use that knowledge to optimize common access patterns.
This doesn’t mean abandoning resource-oriented design entirely. Fine-grained endpoints have value for specific operations. But don’t force clients into inefficient patterns because of theoretical purity.
Ignoring Pagination from the Start
Early APIs often return complete collections. The user list returns all users. The order history returns all orders. With test data, this works fine. With production data, it creates responses that are slow to generate, expensive to transfer, and difficult for clients to process.
Retrofitting pagination is painful. Clients built against the unpaginated API expect complete results. Adding pagination changes the response structure. Existing integrations break.
Paginate from the beginning, even for small collections. Use consistent pagination across all collection endpoints—same parameter names, same response structure with total counts and next page links. The overhead for small collections is negligible; the flexibility for growing collections is essential.
Consider your pagination strategy carefully. Offset-based pagination is simple but performs poorly for large offsets and has consistency issues when data changes between requests. Cursor-based pagination handles these cases better but is more complex to implement. Choose based on your actual access patterns, but choose early.
Poor Error Design
APIs often treat errors as an afterthought. They return generic messages, inconsistent structures, or status codes that don’t match the actual problem. Debugging becomes a guessing game.
Good error responses tell the developer what went wrong, why it went wrong, and what they can do about it. They use appropriate status codes. They include error codes that can be programmatically handled. They provide enough detail to diagnose issues without exposing sensitive implementation details.
Design your error responses as carefully as your success responses. Define a consistent error structure: an error code for programmatic handling, a human-readable message, a field indicating which input caused the problem for validation errors, and optionally a documentation link with more details.
Distinguish between client errors (4xx) and server errors (5xx). Distinguish between authentication failures (401) and authorization failures (403). Use 404 for missing resources, not for failed searches that legitimately returned nothing. These distinctions matter for clients that need to handle errors appropriately—retry logic for server errors, user prompts for authentication, input correction for validation failures.
Authentication as an Afterthought
Security requirements often come late in development. The API starts with no authentication, then adds basic auth quickly, then needs to support multiple auth methods, then needs scoped permissions, then needs rate limiting per client.
Each addition is awkward because the foundation wasn’t designed for it. Token handling is inconsistent. Permission checks are scattered. Rate limiting doesn’t integrate cleanly with client identification.
Design authentication and authorization from the start. Choose an auth standard (OAuth 2.0, API keys, JWT) appropriate for your use case. Design your permission model before building endpoints—what scopes exist, how they map to operations, how they’ll be communicated in tokens. Build rate limiting infrastructure that can distinguish between clients and apply appropriate limits.
The authentication system you build first will need to last. Changing authentication schemes for an existing API is one of the most disruptive changes possible—every client needs to update, and you’ll likely need to support both old and new methods during a long transition.
What Good Looks Like
Good API design anticipates growth. It provides consistency that developers can rely on. It handles errors gracefully. It performs well for real-world use cases, not just demo scenarios.
None of this requires perfection on the first try. What it requires is thinking beyond the immediate feature—considering who will use this API, how they’ll use it, and what they’ll need six months from now that you haven’t thought of yet.
The best API designs come from teams that use their own APIs. When you build a client against your API, you feel the pain of inconsistencies, the frustration of missing error details, the inefficiency of chatty endpoints. That feedback loop drives better design more effectively than any design document.
Build something reasonable, use it yourself, and evolve it. But evolve it carefully—because once others depend on it, every decision becomes permanent.