API Designer
Design clean, consistent REST APIs. Follow established conventions. Provide predictable interfaces.
URL Structure
Resource Naming
✅ Use nouns, not verbs:
GET /users (not /getUsers)
GET /users/123 (not /getUserById/123)
POST /users (not /createUser)
PUT /users/123 (not /updateUser)
DELETE /users/123 (not /deleteUser)
✅ Use plural nouns:
/users, /orders, /products
❌ Not singular:
/user, /order, /product
✅ Nest for relationships:
/users/123/orders (orders belonging to user 123)
/users/123/orders/456 (specific order)
❌ Don't nest more than 2 levels:
/users/123/orders/456/items/789/comments
✅ Use: /order-items/789/comments
Versioning
✅ URL path versioning (simplest, most common):
/api/v1/users
/api/v2/users
✅ Header versioning (cleaner URLs):
Accept: application/vnd.myapi.v1+json
❌ Query parameter versioning:
/api/users?version=1
HTTP Methods & Status Codes
Method Semantics
| Method | Idempotent | Purpose | Returns |
|---|---|---|---|
| GET | Yes | Read resource | 200 |
| POST | No | Create resource | 201 |
| PUT | Yes | Replace resource entirely | 200 |
| PATCH | No | Partial update | 200 |
| DELETE | Yes | Remove resource | 204 |
Status Codes
200 OK - Successful GET, PUT, PATCH
201 Created - Successful POST (resource created)
204 No Content - Successful DELETE, or PUT with no body
400 Bad Request - Invalid input / validation error
401 Unauthorized - Not authenticated
403 Forbidden - Authenticated but not authorized
404 Not Found - Resource doesn't exist
409 Conflict - Duplicate resource, version conflict
422 Unprocessable Entity - Valid JSON but semantic error
429 Too Many Requests - Rate limited
500 Internal Server Error - Server bug
❌ Wrong status codes:
200 for errors with {"error": "not found"}
200 for creation without returning the created resource
404 for "user has no orders" (should be 200 with empty array)
500 for validation errors
✅ Correct:
POST /users with invalid data → 400
GET /users/999 (doesn't exist) → 404
POST /users (duplicate email) → 409
GET /users/123/orders (no orders) → 200 {"data": []}
Request & Response Format
Success Response
{
"data": {
"id": "usr_abc123",
"name": "Alice",
"email": "alice@example.com",
"created_at": "2025-01-15T10:30:00Z"
}
}
Collection Response with Pagination
{
"data": [
{ "id": "usr_1", "name": "Alice" },
{ "id": "usr_2", "name": "Bob" }
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 45,
"total_pages": 3
}
}
Error Response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"value": "not-an-email"
},
{
"field": "age",
"message": "Must be between 0 and 150",
"value": -5
}
]
}
}
❌ Bad error responses:
{"error": "bad request"} // No details
{"message": "Something went wrong"} // No error code
{"errors": ["field1 is bad", "field2 bad"]} // No structure
<html>500 Internal Server Error</html> // HTML for API
✅ Good error response:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [{ "field": "email", "message": "Invalid format" }]
}
}
Filtering, Sorting, Pagination
Filtering
GET /users?status=active&role=admin
GET /orders?created_after=2025-01-01&total_gte=100
GET /products?category=electronics&in_stock=true
Sorting
GET /users?sort=created_at (ascending, default)
GET /users?sort=-created_at (descending, prefix with -)
GET /users?sort=role,-created_at (multiple fields)
Pagination
✅ Page-based (simple):
GET /users?page=2&per_page=20
✅ Cursor-based (for real-time data):
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
Response includes: "next_cursor": "eyJpZCI6MTIwfQ"
Authentication Patterns
✅ Bearer token (most common):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
✅ API key (for server-to-server):
X-API-Key: abc123def456
❌ Don't put tokens in URLs:
/api/users?token=abc123
Rate Limiting Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705312800
Retry-After: 60 (when 429)
Idempotency
For non-idempotent operations that need safety:
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
// Server stores key → response mapping
// Same key returns same response without re-executing
Webhook Design
POST to registered URL with:
{
"id": "evt_abc123",
"type": "order.completed",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"order_id": "ord_xyz789",
"total": 99.99
}
}
Include:
- Signature header: X-Webhook-Signature: sha256=...
- Retry with exponential backoff (3 attempts)
- Idempotent event processing (include event ID)
Naming Conventions
JSON fields: snake_case (most common in APIs)
{"user_id": 123, "created_at": "..."}
Dates: ISO 8601 with timezone
"2025-01-15T10:30:00Z"
NOT: "01/15/2025", "Jan 15", 1705312800
IDs: Prefixed strings for type safety
"usr_abc123", "ord_xyz789", "pay_def456"
NOT: 123, 456 (ambiguous type)
Booleans: is_ / has_ prefix
{"is_active": true, "has_permission": false}
Money: Integer cents, not float
{"amount_cents": 9999, "currency": "USD"}
NOT: {"amount": 99.99} (floating point error)
Decision Checklist
Before finalizing an API design:
- URLs use plural nouns, no verbs
- Correct HTTP method for each operation
- Appropriate status codes (not 200 for everything)
- Consistent error response format
- Pagination on list endpoints
- Filtering and sorting support
- Authentication required on sensitive endpoints
- Rate limiting configured
- Input validation with clear error messages
- No sensitive data in URLs (tokens, passwords)
- CORS configured for browser clients
- API versioning strategy defined