I'm always excited to take on new projects and collaborate with innovative minds.

Social Links

5 Common REST API Design Mistakes (and how to avoid them)

A practical guide to the most common REST API design mistakes developers make — from inconsistent naming and poor versioning to weak security and unclear error handling. Learn how to build cleaner, more reliable, and scalable APIs with intentional design choices.

Building a REST-style API seems straightforward: set up endpoints, expose resources, and off you go. But in practice, the devil is in the details. Tiny mis-steps add friction, frustrate developers, and make future changes painful. Here are five of the most frequent mistakes I’ve seen — and how you can dodge them.

1. Inconsistent naming & structure

One of the first things API consumers see is your URL structure and naming conventions. If you’re inconsistent, you force everybody to check documentation every time. One day the end-point is /users, next day it’s /userList, or perhaps /order-list. This inconsistency kills the developer experience. 

What works better:

  • Use plural nouns for collections: e.g., /users, /products.
  • Avoid deep nesting unless it really reflects ownership. For example, only nest if the child absolutely cannot exist without the parent (e.g., /users/{id}/settings).
  • If it can exist independently (say, a comment), don’t force it under another chain; instead flatten by using filters: e.g., GET /entries?userId={userId}&habitId={habitId} rather than GET /users/{userId}/habits/{habitId}/entries
  • Wrap arrays inside a metadata object so you can add pagination, cursors, or extra info later without breaking clients. For example:

    {
      "data": [
        { ... },
        { ... }
      ],
      "total": 42,
      "hasMore": true,
      "nextCursor": "cursor_01J9KaBcd"
    }
    ``` :contentReference[oaicite:3]{index=3}  
    

2. Poor versioning strategy

Versioning APIs is tricky. Many teams default to something like /v1/users, /v2/users, etc. The intention is good — you want to evolve without breaking clients — but in practice it becomes a maintenance nightmare. You end up supporting multiple versions, patches in three places, diverging docs, confused support tickets… and worse, you often use versioning as an excuse for laziness: rather than thinking how to evolve the API without breaking, you just bump the version and force clients to rewrite.

Better approach:

  • Instead of breaking existing endpoints, extend them: keep old fields, mark them deprecated in documentation, and add new fields. Example:

    // original
    { "id": 1, "name": "John Doe" }
    
    // later version
    {
      "id": 1,
      "name": "John Doe",        // still there
      "firstName": "John",
      "lastName": "Doe"
    }
    
  • For optional features or add-ons, use query parameters: e.g., GET /users/{id}?include=habits,entries or ?format=detailed rather than creating a full v2.
  • If you absolutely must make a breaking change (and this should be rare), consider introducing a new resource rather than versioning the same one: for example: /users/{id} stays the same; you introduce /userProfiles/{id} for the new format. In any case, when you deprecate something you owe your consumers good notice (6-12 months) and a migration guide. 

3. Neglecting pagination, filtering, and search

An endpoint might work fine when your dataset is small (say 10 entries). But soon enough you have thousands or tens of thousands. If a client calls GET /entries and you respond with the full list, you’ll kill performance, exhaust mobile users’ data plans, and frustrate everyone. 

What to design from the start:

  • Filtering: enable clients to specify criteria, e.g., GET /entries?habitId=123&date=2025-08-01&status=completed. That way they don’t download the whole dataset and sift through it client-side.
  • Searching: when they don’t know exact ID or criteria, something like GET /entries/search?q=morning+run+park. Use separate endpoints or logic so your engine optimises for full-text search rather than mixing with heavy filtering.
  • Pagination: Don’t plan to “add pagination later” — do it from day one. Two main styles:
    • Offset/limit: GET /entries?offset=100&limit=50 (simple, widely understood—but has problems: if items are added/deleted mid-pagination you get duplicates or misses; also offsetting high values is expensive)
    • Cursor-based: GET /entries?limit=50&cursor=eyJpZCI6MTIzfQ== — more complex to implement but more reliable for large dynamic datasets.

4. Unclear or inconsistent error handling

When things go wrong, how your API responds matters a lot to the clients consuming it. A vague error message like {"error":"An error occurred"} is useless — it leaves client developers guessing.

Best practices to adopt:

  • Use a structured error format that clearly indicates what happened, why it happened, and how to fix it. For example:

    {
      "type": "https://api.example.com/errors/validation-failed",
      "title": "Validation Failed",
      "status": 400,
      "detail": "The request body contains invalid fields",
      "instance": "/habits/123",
      "errors": [
        {
          "field": "name",
          "reason": "Must be between 1 and 100 characters",
          "value": ""
        },
        {
          "field": "frequency",
          "reason": "Must be one of: daily, weekly, monthly",
          "value": "sometimes"
        }
      ]
    }
    
  • Use correct HTTP status codes (they matter!). Example:
    • 400 Bad Request – your input is garbage
    • 401 Unauthorized – you’re not logged in / unauthenticated
    • 403 Forbidden – you’re authenticated but not allowed
    • 404 Not Found – resource doesn’t exist
    • 409 Conflict – resource state conflict
    • 429 Too Many Requests – you hit rate limit
    • 500 Internal Server Error – your fault (server)
    • 503 Service Unavailable – come back later
  • Don’t leak stack traces or internal server details to end-users in production. Log those internally, give friendly messages externally.

5. Ignoring security until it matters

Security is not an “extra feature” you add in phase two. If you wait, you risk data leaks, compliance failures, poor user trust — and retrofitting security is harder than building it in from the start. 

Security fundamentals to bake in early:

  • Distinguish authentication (who are you?) from authorization (what are you allowed to do?). Many APIs check “logged in” but skip the “should you access this data?” question. That’s dangerous.
  • Always use HTTPS, even for internal or “safe” endpoints. Encryption isn’t optional anymore.
  • Implement rate limiting from day one (e.g., 1000 requests/hour per API key) and respond with 429 Too Many Requests when thresholds are exceeded. You can later fine-tune limits per endpoint or user tier.
  • Think about the trade-offs: security adds complexity and can impact performance. But risks of ignoring it are far worse.

Final thoughts

Good API design isn’t about perfection or blindly following all “best practices”. It’s about making intentional, thoughtful decisions about your trade-offs. You’ll always face constraints: consistency might reduce flexibility; security might slow things; stability might hamper innovation. What matters is that you know why you made each decision, document it, stay consistent, and design for evolution rather than trying to predict some perfect future. 

Your API is a promise to the developers who use it. Every time you break that promise — whether by changing without warning, returning inconsistent patterns, or giving useless errors — you weaken trust. Build the kind of API you yourself would want to use: your fellow developers will thank you, your support team will breathe easier — and future you will be grateful.

6 min read
Nov 19, 2025
By Dheer Gupta
Share

Leave a comment

Your email address will not be published. Required fields are marked *