Files
claude-skills-reference/engineering/api-design-reviewer/references/api_antipatterns.md
Leo 612f2a63fc merge: resolve conflict with dev, keep POWERFUL-tier SKILL.md
Kept our SKILL.md (POWERFUL-tier, 669 lines) over the codex-synced version.
Accepted all new files from dev (additional scripts, references, assets).
2026-02-16 13:13:28 +00:00

13 KiB

Common API Anti-Patterns and How to Avoid Them

Introduction

This document outlines common anti-patterns in REST API design that can lead to poor developer experience, maintenance nightmares, and scalability issues. Each anti-pattern is accompanied by examples and recommended solutions.

1. Verb-Based URLs (The RPC Trap)

Anti-Pattern

Using verbs in URLs instead of treating endpoints as resources.

❌ Bad Examples:
POST /api/getUsers
POST /api/createUser
GET  /api/deleteUser/123
POST /api/updateUserPassword
GET  /api/calculateOrderTotal/456

Why It's Bad

  • Violates REST principles
  • Makes the API feel like RPC instead of REST
  • HTTP methods lose their semantic meaning
  • Reduces cacheability
  • Harder to understand resource relationships

Solution

✅ Good Examples:
GET    /api/users                    # Get users
POST   /api/users                    # Create user
DELETE /api/users/123               # Delete user
PATCH  /api/users/123/password      # Update password
GET    /api/orders/456/total        # Get order total

2. Inconsistent Naming Conventions

Anti-Pattern

Mixed naming conventions across the API.

 Bad Examples:
{
  "user_id": 123,           // snake_case
  "firstName": "John",      // camelCase
  "last-name": "Doe",       // kebab-case
  "EMAIL": "john@example.com", // UPPER_CASE
  "IsActive": true          // PascalCase
}

Why It's Bad

  • Confuses developers
  • Increases cognitive load
  • Makes code generation difficult
  • Reduces API adoption

Solution

 Choose one convention and stick to it (camelCase recommended):
{
  "userId": 123,
  "firstName": "John",
  "lastName": "Doe",
  "email": "john@example.com",
  "isActive": true
}

3. Ignoring HTTP Status Codes

Anti-Pattern

Always returning HTTP 200 regardless of the actual result.

 Bad Example:
HTTP/1.1 200 OK
{
  "status": "error",
  "code": 404,
  "message": "User not found"
}

Why It's Bad

  • Breaks HTTP semantics
  • Prevents proper error handling by clients
  • Breaks caching and proxies
  • Makes monitoring and debugging harder

Solution

 Good Example:
HTTP/1.1 404 Not Found
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User with ID 123 not found",
    "requestId": "req-abc123"
  }
}

4. Overly Complex Nested Resources

Anti-Pattern

Creating deeply nested URL structures that are hard to navigate.

❌ Bad Example:
/companies/123/departments/456/teams/789/members/012/projects/345/tasks/678/comments/901

Why It's Bad

  • URLs become unwieldy
  • Creates tight coupling between resources
  • Makes independent resource access difficult
  • Complicates authorization logic

Solution

✅ Good Examples:
/tasks/678                    # Direct access to task
/tasks/678/comments          # Task comments
/users/012/tasks             # User's tasks
/projects/345?team=789       # Project filtering

5. Inconsistent Error Response Formats

Anti-Pattern

Different error response structures across endpoints.

 Bad Examples:
# Endpoint 1
{"error": "Invalid email"}

# Endpoint 2  
{"success": false, "msg": "User not found", "code": 404}

# Endpoint 3
{"errors": [{"field": "name", "message": "Required"}]}

Why It's Bad

  • Makes error handling complex for clients
  • Reduces code reusability
  • Poor developer experience

Solution

 Standardized Error Format:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Email address is not valid"
      }
    ],
    "requestId": "req-123456",
    "timestamp": "2024-02-16T13:00:00Z"
  }
}

6. Missing or Poor Pagination

Anti-Pattern

Returning all results in a single response or inconsistent pagination.

 Bad Examples:
# No pagination (returns 10,000 records)
GET /api/users

# Inconsistent pagination parameters
GET /api/users?page=1&size=10
GET /api/orders?offset=0&limit=20
GET /api/products?start=0&count=50

Why It's Bad

  • Can cause performance issues
  • May overwhelm clients
  • Inconsistent pagination parameters confuse developers
  • No way to estimate total results

Solution

 Good Example:
GET /api/users?page=1&pageSize=10

{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 10,
    "total": 150,
    "totalPages": 15,
    "hasNext": true,
    "hasPrev": false
  }
}

7. Exposing Internal Implementation Details

Anti-Pattern

URLs and field names that reflect database structure or internal architecture.

❌ Bad Examples:
/api/user_table/123
/api/db_orders
/api/legacy_customer_data
/api/temp_migration_users

Response fields:
{
  "user_id_pk": 123,
  "internal_ref_code": "usr_abc",
  "db_created_timestamp": 1645123456
}

Why It's Bad

  • Couples API to internal implementation
  • Makes refactoring difficult
  • Exposes unnecessary technical details
  • Reduces API longevity

Solution

✅ Good Examples:
/api/users/123
/api/orders
/api/customers

Response fields:
{
  "id": 123,
  "referenceCode": "usr_abc",
  "createdAt": "2024-02-16T13:00:00Z"
}

8. Overloading Single Endpoint

Anti-Pattern

Using one endpoint for multiple unrelated operations based on request parameters.

❌ Bad Example:
POST /api/user-actions
{
  "action": "create_user",
  "userData": {...}
}

POST /api/user-actions  
{
  "action": "delete_user",
  "userId": 123
}

POST /api/user-actions
{
  "action": "send_email",
  "userId": 123,
  "emailType": "welcome"
}

Why It's Bad

  • Breaks REST principles
  • Makes documentation complex
  • Complicates client implementation
  • Reduces discoverability

Solution

✅ Good Examples:
POST   /api/users              # Create user
DELETE /api/users/123         # Delete user  
POST   /api/users/123/emails   # Send email to user

9. Lack of Versioning Strategy

Anti-Pattern

Making breaking changes without version management.

❌ Bad Examples:
# Original API
{
  "name": "John Doe",
  "age": 30
}

# Later (breaking change with no versioning)
{
  "firstName": "John",
  "lastName": "Doe", 
  "birthDate": "1994-02-16"
}

Why It's Bad

  • Breaks existing clients
  • Forces all clients to update simultaneously
  • No graceful migration path
  • Reduces API stability

Solution

✅ Good Examples:
# Version 1
GET /api/v1/users/123
{
  "name": "John Doe",
  "age": 30
}

# Version 2 (with both versions supported)
GET /api/v2/users/123
{
  "firstName": "John",
  "lastName": "Doe",
  "birthDate": "1994-02-16",
  "age": 30  // Backwards compatibility
}

10. Poor Error Messages

Anti-Pattern

Vague, unhelpful, or technical error messages.

 Bad Examples:
{"error": "Something went wrong"}
{"error": "Invalid input"}
{"error": "SQL constraint violation: FK_user_profile_id"}
{"error": "NullPointerException at line 247"}

Why It's Bad

  • Doesn't help developers fix issues
  • Increases support burden
  • Poor developer experience
  • May expose sensitive information

Solution

 Good Examples:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The email address is required and must be in a valid format",
    "details": [
      {
        "field": "email",
        "code": "REQUIRED",
        "message": "Email address is required"
      }
    ]
  }
}

11. Ignoring Content Negotiation

Anti-Pattern

Hard-coding response format without considering client preferences.

❌ Bad Example:
# Always returns JSON regardless of Accept header
GET /api/users/123
Accept: application/xml
# Returns JSON anyway

Why It's Bad

  • Reduces API flexibility
  • Ignores HTTP standards
  • Makes integration harder for diverse clients

Solution

✅ Good Example:
GET /api/users/123
Accept: application/xml

HTTP/1.1 200 OK
Content-Type: application/xml

<?xml version="1.0"?>
<user>
  <id>123</id>
  <name>John Doe</name>
</user>

12. Stateful API Design

Anti-Pattern

Maintaining session state on the server between requests.

❌ Bad Example:
# Step 1: Initialize session
POST /api/session/init

# Step 2: Set context (requires step 1)
POST /api/session/set-user/123

# Step 3: Get data (requires steps 1 & 2)
GET /api/session/user-data

Why It's Bad

  • Breaks REST statelessness principle
  • Reduces scalability
  • Makes caching difficult
  • Complicates error recovery

Solution

✅ Good Example:
# Self-contained requests
GET /api/users/123/data
Authorization: Bearer jwt-token-with-context

13. Inconsistent HTTP Method Usage

Anti-Pattern

Using HTTP methods inappropriately or inconsistently.

❌ Bad Examples:
GET  /api/users/123/delete    # DELETE operation with GET
POST /api/users/123/get       # GET operation with POST
PUT  /api/users               # Creating with PUT on collection
GET  /api/users/search        # Search with side effects

Why It's Bad

  • Violates HTTP semantics
  • Breaks caching and idempotency expectations
  • Confuses developers and tools

Solution

✅ Good Examples:
DELETE /api/users/123         # Delete with DELETE
GET    /api/users/123         # Get with GET
POST   /api/users             # Create on collection
GET    /api/users?q=search    # Safe search with GET

14. Missing Rate Limiting Information

Anti-Pattern

Not providing rate limiting information to clients.

❌ Bad Example:
HTTP/1.1 429 Too Many Requests
{
  "error": "Rate limit exceeded"
}

Why It's Bad

  • Clients don't know when to retry
  • No information about current limits
  • Difficult to implement proper backoff strategies

Solution

✅ Good Example:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 3600

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "API rate limit exceeded",
    "retryAfter": 3600
  }
}

15. Chatty API Design

Anti-Pattern

Requiring multiple API calls to accomplish common tasks.

❌ Bad Example:
# Get user profile requires 4 API calls
GET /api/users/123           # Basic info
GET /api/users/123/profile   # Profile details
GET /api/users/123/settings  # User settings
GET /api/users/123/stats     # User statistics

Why It's Bad

  • Increases latency
  • Creates network overhead
  • Makes mobile apps inefficient
  • Complicates client implementation

Solution

✅ Good Examples:
# Single call with expansion
GET /api/users/123?include=profile,settings,stats

# Or provide composite endpoints
GET /api/users/123/dashboard

# Or batch operations
POST /api/batch
{
  "requests": [
    {"method": "GET", "url": "/users/123"},
    {"method": "GET", "url": "/users/123/profile"}
  ]
}

16. No Input Validation

Anti-Pattern

Accepting and processing invalid input without proper validation.

 Bad Example:
POST /api/users
{
  "email": "not-an-email",
  "age": -5,
  "name": ""
}

# API processes this and fails later or stores invalid data

Why It's Bad

  • Leads to data corruption
  • Security vulnerabilities
  • Difficult to debug issues
  • Poor user experience

Solution

 Good Example:
POST /api/users
{
  "email": "not-an-email",
  "age": -5,
  "name": ""
}

HTTP/1.1 400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Email must be a valid email address"
      },
      {
        "field": "age",
        "code": "INVALID_RANGE",
        "message": "Age must be between 0 and 150"
      },
      {
        "field": "name",
        "code": "REQUIRED",
        "message": "Name is required and cannot be empty"
      }
    ]
  }
}

17. Synchronous Long-Running Operations

Anti-Pattern

Blocking the client with long-running operations in synchronous endpoints.

❌ Bad Example:
POST /api/reports/generate
# Client waits 30 seconds for response

Why It's Bad

  • Poor user experience
  • Timeouts and connection issues
  • Resource waste on client and server
  • Doesn't scale well

Solution

✅ Good Example:
# Async pattern
POST /api/reports
HTTP/1.1 202 Accepted
Location: /api/reports/job-123
{
  "jobId": "job-123",
  "status": "processing",
  "estimatedCompletion": "2024-02-16T13:05:00Z"
}

# Check status
GET /api/reports/job-123
{
  "jobId": "job-123",
  "status": "completed",
  "result": "/api/reports/download/report-456"
}

Prevention Strategies

1. API Design Reviews

  • Implement mandatory design reviews
  • Use checklists based on these anti-patterns
  • Include multiple stakeholders

2. API Style Guides

  • Create and enforce API style guides
  • Use linting tools for consistency
  • Regular training for development teams

3. Automated Testing

  • Test for common anti-patterns
  • Include contract testing
  • Monitor API usage patterns

4. Documentation Standards

  • Require comprehensive API documentation
  • Include examples and error scenarios
  • Keep documentation up-to-date

5. Client Feedback

  • Regularly collect feedback from API consumers
  • Monitor API usage analytics
  • Conduct developer experience surveys

Conclusion

Avoiding these anti-patterns requires:

  • Understanding REST principles
  • Consistent design standards
  • Regular review and refactoring
  • Focus on developer experience
  • Proper tooling and automation

Remember: A well-designed API is an asset that grows in value over time, while a poorly designed API becomes a liability that hampers development and adoption.