DEV Community

Alex Spinov
Alex Spinov

Posted on

API Design Mistakes That Will Haunt You (I've Made All of Them)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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": {}
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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"}}
Enter fullscreen mode Exit fullscreen mode

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):
    ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)