DEV Community

Alex Spinov
Alex Spinov

Posted on

Your REST API Is Probably Broken (7 Mistakes I See in Every Codebase)

I've reviewed 50+ REST APIs this year (for market research tools, scraping services, and internal tools). The same mistakes show up in almost every one.

Here are the 7 worst offenders.

1. Returning 200 for Errors

// BAD: Status 200
{"success": false, "error": "User not found"}

// GOOD: Status 404
{"error": "User not found", "code": "USER_NOT_FOUND"}
Enter fullscreen mode Exit fullscreen mode

If you return 200 for errors, every client has to parse the body to know if the request worked. HTTP status codes exist for a reason.

2. No Pagination (or Bad Pagination)

// BAD: Returns all 50,000 records
GET /api/users

// GOOD: Cursor-based pagination
GET /api/users?limit=50&cursor=abc123
Enter fullscreen mode Exit fullscreen mode

Offset pagination breaks at scale (try OFFSET 1000000 on a large table). Use cursor-based pagination from day one.

3. Nested URLs 4 Levels Deep

// BAD
GET /api/companies/123/departments/456/employees/789/reviews

// GOOD
GET /api/reviews?employee_id=789
Enter fullscreen mode Exit fullscreen mode

Deep nesting makes URLs fragile and hard to cache. Flat is better.

4. No Rate Limiting

If your API has no rate limiting, it's not a matter of IF someone will DDoS you, but WHEN. Even a simple token bucket with 100 req/min is better than nothing.

# Redis rate limiter in 5 lines
import redis
r = redis.Redis()

def is_rate_limited(user_id, limit=100, window=60):
    key = f"rate:{user_id}"
    current = r.incr(key)
    if current == 1:
        r.expire(key, window)
    return current > limit
Enter fullscreen mode Exit fullscreen mode

5. Inconsistent Response Formats

// Endpoint 1 returns:
{"data": [{...}], "total": 100}

// Endpoint 2 returns:
{"results": [{...}], "count": 100}

// Endpoint 3 returns:
[{...}]  // Just a raw array
Enter fullscreen mode Exit fullscreen mode

Pick ONE format and use it everywhere:

{"data": [...], "meta": {"total": 100, "page": 1, "limit": 50}}
Enter fullscreen mode Exit fullscreen mode

6. No Versioning

The day you need to change a response format, you'll break every client. Version from day one:

GET /api/v1/users    ← current
GET /api/v2/users    ← new format
Enter fullscreen mode Exit fullscreen mode

Or use headers: Accept: application/vnd.myapi.v2+json

7. Exposing Internal IDs

// BAD: Sequential IDs leak information
{"id": 12847}
// Competitor can guess: you have ~12,847 users

// GOOD: UUIDs or hashids
{"id": "usr_a1b2c3d4e5"}
Enter fullscreen mode Exit fullscreen mode

Sequential IDs tell competitors your user count, enable enumeration attacks, and leak business metrics.

Bonus: No CORS Headers

If your API is meant to be called from browsers and you forgot CORS headers, nothing works. Save yourself the debugging:

# Flask
from flask_cors import CORS
CORS(app)

# Express
app.use(cors())
Enter fullscreen mode Exit fullscreen mode

The Quick Checklist

  • [ ] Correct HTTP status codes
  • [ ] Cursor-based pagination
  • [ ] Flat URL structure
  • [ ] Rate limiting
  • [ ] Consistent response format
  • [ ] API versioning
  • [ ] No sequential IDs exposed
  • [ ] CORS headers

What API mistakes would you add to this list?

I build API wrappers and data tools. Check my latest: Alpha Vantage Client | CoinGecko Client | All repos

Top comments (0)