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
| Code | When to Use |
|---|---|
200 OK | Successful GET, PUT, PATCH, DELETE |
201 Created | Successful POST that creates a resource |
204 No Content | Success with no response body |
400 Bad Request | Client sent malformed or invalid data |
401 Unauthorized | Authentication required |
403 Forbidden | Authenticated but not authorized |
404 Not Found | Resource does not exist |
409 Conflict | Conflict with existing state (duplicate, etc.) |
422 Unprocessable Entity | Validation errors |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Something 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."