All Posts
API DesignRESTBackendWeb Development

Designing REST APIs That Developers Actually Love

A well-designed API is a gift to every developer who integrates with it. Here are the principles that separate forgettable APIs from the ones you reference in talks.

Designing REST APIs That Developers Actually Love

I have integrated with dozens of APIs over my career. Some of them were a joy — intuitive, consistent, and well-documented. Others made me question my career choices. The difference almost always came down to a handful of design decisions made (or ignored) early in the project.

Here is what I have learned about building APIs that developers are happy to work with.

Principle 1: Predictability Over Cleverness

The best APIs are boring. You look at one endpoint and you can predict every other endpoint in the system. Consistency is the single most important quality in an API.

Use Nouns, Not Verbs

Resources are things, not actions. Use HTTP methods to express the action.

# Bad
GET /getUser/123
POST /createBlogPost
DELETE /removeComment/456

# Good
GET /users/123
POST /blog-posts
DELETE /comments/456

Be Consistent with Casing

Pick a convention and stick to it everywhere. Most REST APIs use kebab-case for URLs and camelCase or snake_case for JSON properties. Whatever you choose, be consistent.

// snake_case — common in Python ecosystems
{
  "user_id": "abc123",
  "created_at": "2025-02-20T10:00:00Z"
}

// camelCase — common in JavaScript ecosystems
{
  "userId": "abc123",
  "createdAt": "2025-02-20T10:00:00Z"
}

Principle 2: Use HTTP Status Codes Correctly

This is where many APIs fail. Using 200 OK for everything — including errors — is an antipattern that forces clients to parse response bodies just to know if a request succeeded.

The Essential Status Codes

CodeWhen to Use
200 OKSuccessful GET, PUT, PATCH, DELETE
201 CreatedSuccessful POST that creates a resource
204 No ContentSuccess with no response body
400 Bad RequestClient sent malformed or invalid data
401 UnauthorizedAuthentication required
403 ForbiddenAuthenticated but not authorized
404 Not FoundResource does not exist
409 ConflictConflict with existing state (duplicate, etc.)
422 Unprocessable EntityValidation errors
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorSomething went wrong on the server

Principle 3: Error Responses Should Be Actionable

When something goes wrong, your error response should tell the developer exactly what happened and ideally how to fix it.

// Bad — forces guessing
{
  "error": true,
  "message": "Bad request"
}

// Good — specific and actionable
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "received": "not-an-email"
      },
      {
        "field": "age",
        "message": "Must be a positive integer",
        "received": -5
      }
    ]
  }
}

Principle 4: Version Your API From Day One

Breaking changes are inevitable. The question is how you handle them. Versioning in the URL path is the most explicit and easiest to debug:

/v1/users
/v2/users

Start with v1. When you need to make breaking changes, introduce v2 and give consumers a deprecation timeline. Do not make breaking changes in existing versions.

Principle 5: Pagination, Filtering, and Sorting

Any endpoint that can return multiple items needs pagination. Returning 50,000 records because a client forgot to add a limit is a production incident waiting to happen.

# Cursor-based pagination (preferred for large, real-time datasets)
GET /posts?cursor=eyJpZCI6MTAwfQ&limit=20

# Offset-based pagination (simpler, fine for small datasets)
GET /posts?page=3&limit=20

# Filtering
GET /posts?status=published&author=jay

# Sorting
GET /posts?sort=created_at&order=desc

Your responses should include metadata:

{
  "data": [...],
  "pagination": {
    "total": 847,
    "page": 3,
    "limit": 20,
    "hasNext": true,
    "nextCursor": "eyJpZCI6NjB9"
  }
}

Principle 6: Documentation Is Part of the Product

An undocumented API is a half-built API. Great documentation includes:

  • Overview: What does this API do? What problem does it solve?
  • Authentication: How do you get credentials? How do you include them?
  • Endpoints: Request/response examples for every endpoint with real data, not placeholder strings
  • Error codes: A complete list of every error code with explanations
  • Rate limits: What are the limits and what happens when you exceed them?
  • Changelog: What changed in each version?

Consider using OpenAPI (Swagger) to generate interactive documentation automatically from your code. Tools like Stoplight, Redoc, and Swagger UI make this beautiful and explorable.

Principle 7: Think About Idempotency

GET, PUT, and DELETE should be idempotent — calling them multiple times with the same input should produce the same result. For POST requests that create resources, consider accepting a client-generated idempotency key in the request header:

POST /payments
Idempotency-Key: a8098c1a-f86e-11da-bd1a-00112444be1e

{
  "amount": 5000,
  "currency": "USD"
}

If the client retries due to a network error, the server returns the original response instead of creating a duplicate payment.

Conclusion

Great API design is a skill that compounds. Every developer who integrates smoothly with your API is spending time building products instead of deciphering documentation and debugging unexpected behavior. Consistency, meaningful status codes, good errors, versioning, and solid documentation are the foundation. Build these habits early and your API becomes an asset rather than a liability.

The best compliment an API can receive is: "It just worked."