I've built 50+ APIs. Here are the mistakes I keep seeing — including ones I made myself.
1. Inconsistent Naming
# BAD — mixing styles
GET /getUsers
GET /user_list
GET /fetch-all-products
POST /CreateOrder
# GOOD — consistent kebab-case, resource-based
GET /users
GET /products
POST /orders
GET /users/{id}/orders
Pick ONE naming convention and stick to it across every endpoint.
2. Not Using Proper HTTP Methods
# BAD
POST /deleteUser?id=123
GET /createOrder
POST /getUsers
# GOOD
DELETE /users/123
POST /orders
GET /users
3. Returning Inconsistent Error Formats
// BAD — different error format every time
{"error": "Not found"}
{"message": "Unauthorized", "code": 401}
{"err": true, "msg": "Bad input"}
// GOOD — consistent error envelope
{
"error": {
"code": "NOT_FOUND",
"message": "User with ID 123 not found",
"details": {}
}
}
4. Not Versioning from Day 1
# BAD — no version, breaking changes break all clients
GET /users
# GOOD — versioned from the start
GET /v1/users
# OR via header
Accept: application/vnd.myapi.v1+json
You WILL need to make breaking changes. Version from the start.
5. Over-Fetching (No Field Selection)
# BAD — returns 50 fields when client needs 3
GET /users/123
{"id": 123, "name": "John", "email": "...", "address": {...}, "preferences": {...}, ...50 more fields}
# GOOD — field selection
GET /users/123?fields=id,name,email
6. No Pagination
# BAD — returns 100,000 rows
GET /users
# GOOD — cursor-based pagination
GET /users?limit=20&cursor=abc123
{
"data": [...],
"pagination": {
"next_cursor": "def456",
"has_more": true
}
}
Cursor pagination > offset pagination for large datasets.
7. Using 200 for Everything
// BAD — 200 OK but it's actually an error
HTTP/1.1 200 OK
{"success": false, "error": "User not found"}
// GOOD — proper status codes
HTTP/1.1 404 Not Found
{"error": {"code": "NOT_FOUND", "message": "User not found"}}
Essential status codes:
- 200 OK, 201 Created, 204 No Content
- 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests
- 500 Internal Server Error
8. No Rate Limiting
# FastAPI rate limiting
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)
@app.get("/users")
@limiter.limit("100/minute")
async def get_users(request: Request):
...
Without rate limiting, one client can take down your entire API.
9. Exposing Internal IDs
# BAD — sequential IDs reveal info
/users/1, /users/2, /users/3
# Attacker knows: there are only 3 users
# GOOD — UUIDs or public IDs
/users/550e8400-e29b-41d4-a716-446655440000
The Checklist
- [ ] Consistent naming convention
- [ ] Proper HTTP methods
- [ ] Consistent error format
- [ ] API versioning
- [ ] Pagination on all list endpoints
- [ ] Proper status codes
- [ ] Rate limiting
- [ ] Input validation
- [ ] Authentication on protected routes
- [ ] CORS configured
API testing tools: API Testing Toolkit | FastAPI Starter
What's the worst API design you've encountered? I once used an API where every endpoint was POST and the action was in the body. 👇
API articles at dev.to/0012303
Top comments (0)