Good APIs are invisible. You use them without thinking. Bad APIs make you read docs for every call. Here's how to build the good kind.
Be Predictable
If GET /users returns a list of users, then GET /posts should return a list of posts. Same pattern, same structure.
// Consistent response shape
{
"data": [...],
"meta": {
"total": 100,
"page": 1,
"per_page": 20
}
}Once developers learn your patterns, they can guess endpoints they've never seen.
Use Standard HTTP
Methods mean things:
GET— Read (safe, cacheable)POST— CreatePUT— Replace entirelyPATCH— Update partiallyDELETE— Remove
Status codes mean things:
200— Success201— Created400— Client error (bad request)401— Not authenticated403— Not authorized404— Not found500— Server error
Don't return 200 OK with {"error": "Something failed"}. Use status codes correctly.
Error Responses
Errors should help developers fix the problem:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "age",
"message": "Must be a positive integer"
}
]
}
}Include:
- Machine-readable error code
- Human-readable message
- Specific field errors for validation
- Request ID for debugging
Naming Conventions
Use nouns, not verbs:
GET /users ✓
GET /getUsers ✗
POST /users ✓
POST /createUser ✗
Plural for collections:
GET /users ✓
GET /user ✗
GET /users/123 ✓
Lowercase, hyphen-separated:
/user-profiles ✓
/userProfiles ✗
/user_profiles ✗
Filtering and Pagination
Filter with query params:
GET /users?status=active&role=admin
GET /posts?created_after=2024-01-01
Paginate everything that could grow:
GET /users?page=2&per_page=50
Return pagination metadata:
{
"data": [...],
"meta": {
"total": 500,
"page": 2,
"per_page": 50,
"total_pages": 10
}
}Versioning
Version from day one. APIs change; clients can't always update immediately.
URL versioning (my preference):
/v1/users
/v2/users
Header versioning:
Accept: application/vnd.myapi.v1+json
URL versioning is more visible and easier to test.
Authentication
Use standard patterns:
Bearer tokens:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
API keys (for server-to-server):
X-API-Key: sk_live_abc123
Don't invent custom auth schemes. Security is hard; use proven patterns.
Rate Limiting
Protect your API and communicate limits clearly:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 60
Document limits. Return headers showing current usage. Give clear retry guidance.
Documentation
Good docs include:
- Authentication guide
- Every endpoint with request/response examples
- Error code reference
- Rate limit information
- Changelog
Great docs include:
- Quick start guide
- Code examples in multiple languages
- Sandbox environment
- Postman/OpenAPI collections
My Checklist
Before shipping an endpoint:
- Uses correct HTTP method
- Returns appropriate status codes
- Error responses are helpful
- Follows existing naming conventions
- Has pagination if returning lists
- Is authenticated/authorized properly
- Is rate limited
- Is documented
The Test
The best test: can a developer integrate without asking you questions?
If they can read your docs and build something in an hour, you've designed a good API. If they need a meeting to understand it, something's wrong.