APIs are contracts between systems. Good design makes them intuitive to use and easy to maintain. Here are guidelines for designing APIs that stand the test of time.
Consistency Is Key#
1// Consistent naming across endpoints
2// ✓ Good - predictable patterns
3GET /users
4GET /users/:id
5POST /users
6GET /orders
7GET /orders/:id
8POST /orders
9
10// ✗ Bad - inconsistent patterns
11GET /users
12GET /user/:id
13POST /createUser
14GET /order-list
15GET /orders/:orderId
16
17// Consistent response format
18interface ApiResponse<T> {
19 data: T;
20 meta?: {
21 pagination?: PaginationMeta;
22 requestId: string;
23 timestamp: string;
24 };
25}
26
27interface ErrorResponse {
28 error: {
29 code: string;
30 message: string;
31 details?: any;
32 };
33 meta: {
34 requestId: string;
35 timestamp: string;
36 };
37}Predictable Behavior#
1// Status codes should be predictable
2// 2xx - Success
3201 // Created - POST success, include Location header
4200 // OK - GET, PUT, PATCH success
5204 // No Content - DELETE success
6
7// 4xx - Client errors
8400 // Bad Request - Malformed request
9401 // Unauthorized - No/invalid authentication
10403 // Forbidden - Authenticated but not allowed
11404 // Not Found - Resource doesn't exist
12409 // Conflict - Resource state conflict
13422 // Unprocessable - Validation failed
14
15// 5xx - Server errors
16500 // Internal Error - Unexpected failure
17503 // Service Unavailable - Temporary
18
19// Idempotency
20// GET, PUT, DELETE should be idempotent
21PUT /users/123 { name: "New Name" }
22// Same result no matter how many times called
23
24// POST is not idempotent - use idempotency keys
25POST /orders
26Idempotency-Key: unique-request-idResource Design#
1// Resources are nouns, not verbs
2// ✓ Good
3POST /orders // Create order
4POST /orders/123/ship // Ship order (action as sub-resource)
5
6// ✗ Bad
7POST /createOrder
8POST /shipOrder/123
9
10// Relationships
11GET /users/123/orders // User's orders
12GET /orders?userId=123 // Also valid
13GET /orders/456/items // Order's items
14
15// Keep nesting shallow (max 2 levels)
16// ✓ Good
17GET /orders/456/items
18
19// ✗ Too deep
20GET /users/123/orders/456/items/789/details
21
22// Actions as sub-resources
23POST /orders/123/cancel
24POST /users/123/verify-email
25POST /payments/456/refundFiltering and Pagination#
1// Filtering
2GET /products?category=electronics
3GET /products?category=electronics&brand=apple
4GET /products?price[gte]=100&price[lte]=500
5GET /products?status=active,pending // Multiple values
6
7// Sorting
8GET /products?sort=price // Ascending
9GET /products?sort=-price // Descending
10GET /products?sort=-createdAt,name // Multiple fields
11
12// Pagination - Cursor-based (preferred)
13GET /users?limit=20
14GET /users?limit=20&cursor=eyJpZCI6MTAwfQ
15
16Response:
17{
18 "data": [...],
19 "meta": {
20 "pagination": {
21 "limit": 20,
22 "hasMore": true,
23 "nextCursor": "eyJpZCI6MTIwfQ"
24 }
25 }
26}
27
28// Pagination - Offset-based
29GET /users?limit=20&offset=40
30
31Response:
32{
33 "data": [...],
34 "meta": {
35 "pagination": {
36 "total": 500,
37 "limit": 20,
38 "offset": 40,
39 "hasMore": true
40 }
41 }
42}Field Selection#
1// Sparse fieldsets
2GET /users/123?fields=id,name,email
3
4// Expand related resources
5GET /orders/123?include=customer,items
6GET /orders/123?expand=customer
7
8// Response
9{
10 "data": {
11 "id": "123",
12 "total": 99.99,
13 "customer": {
14 "id": "456",
15 "name": "John Doe"
16 },
17 "items": [
18 { "id": "789", "name": "Widget", "quantity": 2 }
19 ]
20 }
21}Error Responses#
1// Always include:
2// - Error code (machine-readable)
3// - Message (human-readable)
4// - Request ID (for debugging)
5
6// Validation error
7{
8 "error": {
9 "code": "VALIDATION_ERROR",
10 "message": "Request validation failed",
11 "details": [
12 { "field": "email", "message": "Invalid email format" },
13 { "field": "password", "message": "Must be at least 8 characters" }
14 ]
15 },
16 "meta": {
17 "requestId": "req_abc123",
18 "timestamp": "2024-01-15T10:30:00Z"
19 }
20}
21
22// Resource not found
23{
24 "error": {
25 "code": "NOT_FOUND",
26 "message": "User with ID 999 not found"
27 },
28 "meta": {
29 "requestId": "req_def456"
30 }
31}
32
33// Rate limit
34{
35 "error": {
36 "code": "RATE_LIMITED",
37 "message": "Too many requests",
38 "details": {
39 "retryAfter": 30
40 }
41 }
42}Backwards Compatibility#
1// Adding fields is safe
2// v1: { id, name }
3// v2: { id, name, email } // OK - additive
4
5// Removing/renaming fields breaks clients
6// Use deprecation warnings first
7response.setHeader('Deprecation', 'true');
8response.setHeader('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
9
10// Version your API
11/v1/users
12/v2/users
13
14// Or use headers
15Accept: application/vnd.api+json; version=2
16
17// Changelog
18## v2.0 (2024-01-15)
19- Added `email` field to User
20- Deprecated `fullName`, use `firstName` + `lastName`
21- Changed pagination from offset to cursor-basedDocumentation#
1# OpenAPI spec
2openapi: 3.1.0
3info:
4 title: User API
5 version: 1.0.0
6 description: |
7 ## Authentication
8 All endpoints require Bearer token.
9
10 ## Rate Limits
11 - Standard: 100 requests/minute
12 - Authenticated: 1000 requests/minute
13
14paths:
15 /users:
16 get:
17 summary: List users
18 parameters:
19 - name: limit
20 in: query
21 schema:
22 type: integer
23 default: 20
24 maximum: 100
25 responses:
26 '200':
27 description: Success
28 content:
29 application/json:
30 example:
31 data:
32 - id: "123"
33 name: "John"Best Practices Summary#
Consistency:
✓ Uniform naming conventions
✓ Predictable response format
✓ Consistent error handling
✓ Standard HTTP methods
Design:
✓ Resource-oriented URLs
✓ Shallow nesting
✓ Proper status codes
✓ Idempotent operations
Usability:
✓ Clear documentation
✓ Helpful error messages
✓ Request IDs for debugging
✓ Rate limit headers
Evolution:
✓ Version from the start
✓ Deprecate before removing
✓ Changelog maintenance
✓ Sunset headers
Conclusion#
Good API design requires consistency, predictability, and clear documentation. Make APIs intuitive by following REST conventions, provide helpful errors, and plan for evolution. Your API is a product—treat it with the same care as your user interface.