An API is a promise. Once a mobile app, a partner, or another team depends on it, every awkward name, inconsistent response, and missing limit becomes very expensive to change. Many API problems aren't caused by hard technical challenges; they come from a handful of common design mistakes made early and repeated across dozens of endpoints. Here are the ones we see most often in production systems, and how to avoid them.
1. Using Verbs in URLs Instead of Resources
Endpoints like /getUsers, /createOrder, and /deleteProduct?id=5 duplicate what HTTP methods already express. Model resources as nouns and let the method carry the action: GET /users, POST /orders, DELETE /products/5. Use plural nouns consistently, and nest only where the relationship is real, such as /orders/42/items.
2. Ignoring HTTP Methods and Status Codes
Returning 200 OK with {"success": false} forces every client to parse the body to learn whether a request failed, and breaks caching, monitoring, and retries. Use status codes for what they mean:
- 200 / 201 / 204 for success, created, and no content.
- 400 for malformed requests and 422 for validation failures.
- 401 when authentication is missing and 403 when the user isn't allowed.
- 404 for missing resources and 409 for conflicts.
- 429 for rate limits and 5xx only for real server failures.
Also keep GET safe: it must never change data.
3. Inconsistent Error Responses
When one endpoint returns {"msg": "oops"}, another returns {"errors": [...]}, and a third returns an HTML stack trace, client code turns into a pile of special cases. Define one error format and use it everywhere, with a stable machine-readable code, a human message, and optional field details. Standards such as RFC 9457 Problem Details are a good starting point. Never leak stack traces or SQL errors to clients.
4. No Versioning Strategy
Teams often ship v1 with no plan for v2, then discover they can't rename a field without breaking released mobile apps. Decide early how you'll version, for example /v1/ in the path or a header, and follow a simple rule: additive changes are fine, breaking changes need a new version and a deprecation timeline. Contracts that stay stable are a big part of building software that survives years of new requirements.
5. Returning Everything at Once
An endpoint that returns all 80,000 orders works fine in development and times out in production. Paginate every list endpoint from day one. Cursor-based pagination is more stable than offset pagination for large or frequently changing data. Add filtering and sorting through clear query parameters, and set a sensible maximum page size.
6. Leaking the Database Schema
Exposing table names, internal IDs, and every column ties clients to your storage design. When you rename a column or split a table, the API breaks. Map database models to deliberate response objects (DTOs or resources), return only what clients need, and keep naming consistent, for example always camelCase or always snake_case. Keeping that mapping out of controllers is easier when responsibilities are clear, as covered in Controller vs Service vs Repository.
7. Weak Authentication and Authorization
Authentication tells you who the caller is; authorization decides what they can do. A common and serious bug is checking login but not ownership, so GET /invoices/123 returns any user's invoice. Check permissions on every resource access, use short-lived tokens, require HTTPS, and never put secrets or tokens in URLs where they end up in logs.
8. Forgetting Idempotency and Retries
Networks fail, and clients retry. If POST /payments runs twice, a customer may be charged twice. Support an Idempotency-Key header for operations that create or charge, store the result, and return the same response for repeated keys. Make PUT and DELETE naturally idempotent.
9. No Rate Limiting or Caching
Without rate limits, one buggy client or scraper can take down your service. Return 429 with a Retry-After header when limits are hit. For read-heavy endpoints, use HTTP caching headers like ETag and Cache-Control, and cache expensive results on the server where it makes sense. Our guide on when to use Redis caching explains where server-side caching helps and where it hurts.
10. Missing or Outdated Documentation
An API without accurate docs is an API people misuse. Describe it with OpenAPI, generate reference docs from that spec, include real request and response examples, and document errors, limits, and authentication. Keep the spec in the same repository and check it in CI so it can't drift from the code.
A Quick API Review Checklist
- Are URLs resource nouns with correct HTTP methods?
- Do status codes reflect the actual outcome?
- Is there one consistent error format?
- Is there a versioning and deprecation plan?
- Are all list endpoints paginated?
- Are responses decoupled from database tables?
- Is authorization checked on every resource?
- Are create and payment operations idempotent?
- Are rate limits and caching headers in place?
- Is there an up-to-date OpenAPI spec with examples?
Conclusion
Good API design is mostly about consistency and respect for the people who integrate with you. Use resources and HTTP correctly, return predictable errors, version and paginate from the start, hide your internals, secure every resource, handle retries safely, and keep documentation honest. Fix these early and your API becomes something clients trust instead of something they work around.
