Building REST APIs That Don’t Suck

Design Principles for APIs Developers Actually Want to Use

You’ve used bad APIs. We all have. The ones where you need to read 50 pages of docs to make a simple request. Where error messages say “Error 500” and nothing else. Let’s not build those.

The API That Made Me Rage-Quit

I was integrating a payment API. Sent a request with amount: 1000. Got back 500 Internal Server Error with no details.

After 2 hours of trial and error, I discovered the API expected the amount as a string, not a number. "1000", not 1000.

A proper error would have told me: “Amount must be a string representing cents, received number.”

Good error messages are the difference between a good API and a rage-inducing one.

REST Basics

URLs Should Be Nouns, Not Verbs

❌ GET  /getUsers
❌ POST /createUser

✅ GET    /users          (list users)
✅ POST   /users          (create user)
✅ DELETE /users/123      (delete user)

The HTTP method is the verb. The URL is the noun.

HTTP Methods

  • GET: Retrieve (idempotent)
  • POST: Create
  • PUT: Update/Replace (idempotent)
  • PATCH: Partial Update
  • DELETE: Remove (idempotent)

Idempotent means calling it multiple times has the same effect as calling it once.

Resource Nesting

Keep nesting shallow - max 2-3 levels:

✅ GET /users/123/posts
✅ GET /posts/456/comments
❌ GET /users/123/posts/456/comments/789/likes/101

Response Structure

Success Response

{
  "data": {
    "id": "123",
    "name": "John Doe"
  }
}

For lists, include pagination:

{
  "data": [...],
  "pagination": {
    "page": 1,
    "perPage": 20,
    "total": 42
  }
}

Error Response

This is the most important part:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "message": "Email is required"
      }
    ],
    "requestId": "req_abc123"
  }
}

Include a machine-readable error code, human-readable message, field-level details, and a request ID for debugging.

HTTP Status Codes

Use them correctly:

Success (2xx)

  • 200 OK: Request succeeded
  • 201 Created: Resource created
  • 204 No Content: Success, no body

Client Errors (4xx)

  • 400 Bad Request: Invalid syntax
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Not authorized
  • 404 Not Found: Resource doesn’t exist
  • 422 Unprocessable: Validation failed
  • 429 Too Many Requests: Rate limited

Server Errors (5xx)

  • 500 Internal Server Error
  • 503 Service Unavailable

Never return 200 with an error message in the body. Use proper status codes.

Pagination

Offset-based: Simple, lets users jump to any page. Good for most cases.

GET /users?page=1&perPage=20

Cursor-based: Better for feeds. Consistent results even as data changes.

GET /posts?cursor=abc123&limit=20

Filtering and Sorting

Support common query patterns:

GET /products?category=electronics&inStock=true&minPrice=100
GET /users?sortBy=createdAt&order=desc
GET /users?search=john

Versioning

Use URL versioning - it’s the clearest:

https://api.example.com/v1/users
https://api.example.com/v2/users

Authentication

JWT Tokens:

Authorization: Bearer eyJhbGc...

API Keys for server-to-server:

X-API-Key: sk_live_abc123

OAuth 2.0 for third-party integrations.

Rate Limiting

Include headers showing limits:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1635724800

When exceeded, return 429 with a clear error message explaining when they can retry.

Validation

Fail fast with clear messages. Validate early and return all errors at once, not one at a time. Tell users exactly what’s wrong and what they should send instead.

API Design Checklist

  • URLs use nouns, not verbs
  • HTTP methods used correctly
  • Consistent response structure
  • Descriptive error messages with codes
  • Proper HTTP status codes
  • Pagination for lists
  • Filtering and sorting support
  • Versioning strategy in place
  • Authentication implemented
  • Rate limiting configured
  • Input validation
  • API documentation
  • Tests for all endpoints

Good API design is about empathy. Put yourself in the shoes of the developer using your API.

Would you want to integrate with this? Would the error messages help you debug? Is the documentation clear?

Build APIs you’d want to use.