Back to Blog
APIDesignBest PracticesREST

API Design Guidelines for Modern Applications

Design intuitive APIs. From consistency to error handling to backwards compatibility.

B
Bootspring Team
Engineering
January 12, 2023
5 min read

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-id

Resource 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/refund

Filtering 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-based

Documentation#

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.

Share this article

Help spread the word about Bootspring