REST API Cheatsheet
Comprehensive quick reference for REST API design, HTTP methods, status codes, headers, authentication, and best practices. Essential guide for building and consuming RESTful APIs.
Table of Contents
- Prerequisites
- HTTP Methods
- Status Codes
- Request Format
- Response Format
- Headers
- Authentication
- Query Parameters
- Path Parameters
- Request Body
- Error Handling
- API Design Principles
- Versioning
- Pagination
- Filtering & Sorting
- Rate Limiting
- Best Practices
- Common Pitfalls
Prerequisites
HTTP Protocol: REST APIs use HTTP/1.1 or HTTP/2. This cheatsheet assumes HTTP/1.1+.
Tools:
- cURL: Command-line HTTP client (pre-installed on macOS/Linux)
- Postman/Insomnia: GUI API testing tools
- HTTPie: Modern command-line HTTP client (
brew install httpie)
# Check cURL versioncurl --version# β curl 8.1.2
# Test API endpointcurl -X GET https://api.example.com/users
# Install HTTPie (macOS)brew install httpie
# Install HTTPie (Linux)apt install httpie # Debian/Ubuntuyum install httpie # RHEL/CentOSCommon Content Types:
application/json- JSON data (most common)application/xml- XML dataapplication/x-www-form-urlencoded- Form datamultipart/form-data- File uploadstext/plain- Plain text
HTTP Methods
Core Methods π―
# GET - Retrieve resource(s)GET /api/usersGET /api/users/123
# POST - Create new resourcePOST /api/usersContent-Type: application/json
{ "name": "John Doe", "email": "john@example.com"}
# PUT - Replace entire resource (idempotent)PUT /api/users/123Content-Type: application/json
{ "name": "John Doe", "email": "john@example.com", "age": 30}
# PATCH - Partial update (idempotent)PATCH /api/users/123Content-Type: application/json
{ "email": "newemail@example.com"}
# DELETE - Remove resource (idempotent)DELETE /api/users/123Method Characteristics π
| Method | Idempotent | Safe | Request Body | Response Body |
|---|---|---|---|---|
| GET | β Yes | β Yes | β No | β Yes |
| POST | β No | β No | β Yes | β Yes |
| PUT | β Yes | β No | β Yes | β Optional |
| PATCH | β Yes* | β No | β Yes | β Optional |
| DELETE | β Yes | β No | β Optional | β Optional |
*PATCH idempotency depends on implementation
Method Usage Examples π‘
# GET - Fetch all userscurl -X GET https://api.example.com/users \ -H "Authorization: Bearer token123"
# GET - Fetch single usercurl -X GET https://api.example.com/users/123
# POST - Create usercurl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"john@example.com"}'
# PUT - Replace entire usercurl -X PUT https://api.example.com/users/123 \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"john@example.com","age":30}'
# PATCH - Update user email onlycurl -X PATCH https://api.example.com/users/123 \ -H "Content-Type: application/json" \ -d '{"email":"newemail@example.com"}'
# DELETE - Remove usercurl -X DELETE https://api.example.com/users/123Status Codes
Success Codes (2xx) β
# 200 OK - Standard success responseGET /api/users/123β 200 OK
# 201 Created - Resource created successfullyPOST /api/usersβ 201 CreatedLocation: /api/users/123
# 202 Accepted - Request accepted for processing (async)POST /api/jobsβ 202 Accepted
# 204 No Content - Success but no content to returnDELETE /api/users/123β 204 No ContentClient Error Codes (4xx) β
# 400 Bad Request - Invalid request syntaxPOST /api/usersContent-Type: application/json{"invalid": "json"β 400 Bad Request{ "error": "Invalid JSON syntax"}
# 401 Unauthorized - Authentication requiredGET /api/usersβ 401 UnauthorizedWWW-Authenticate: Bearer
# 403 Forbidden - Authenticated but not authorizedGET /api/admin/usersβ 403 Forbidden{ "error": "Insufficient permissions"}
# 404 Not Found - Resource doesn't existGET /api/users/999β 404 Not Found{ "error": "User not found"}
# 405 Method Not Allowed - HTTP method not supportedPATCH /api/usersβ 405 Method Not AllowedAllow: GET, POST, PUT, DELETE
# 409 Conflict - Resource conflict (e.g., duplicate)POST /api/users{"email": "existing@example.com"}β 409 Conflict{ "error": "Email already exists"}
# 422 Unprocessable Entity - Valid syntax but semantic errorsPOST /api/users{"email": "invalid-email"}β 422 Unprocessable Entity{ "error": "Validation failed", "details": { "email": "Invalid email format" }}
# 429 Too Many Requests - Rate limit exceededGET /api/usersβ 429 Too Many RequestsRetry-After: 60{ "error": "Rate limit exceeded"}Server Error Codes (5xx) β οΈ
# 500 Internal Server Error - Generic server errorGET /api/usersβ 500 Internal Server Error{ "error": "Internal server error"}
# 502 Bad Gateway - Invalid response from upstreamGET /api/usersβ 502 Bad Gateway
# 503 Service Unavailable - Server temporarily unavailableGET /api/usersβ 503 Service UnavailableRetry-After: 300
# 504 Gateway Timeout - Upstream server timeoutGET /api/usersβ 504 Gateway TimeoutStatus Code Quick Reference π
| Code | Name | Use Case | Response Body |
|---|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH | β Yes |
| 201 | Created | Successful POST | β Yes |
| 202 | Accepted | Async processing started | β Optional |
| 204 | No Content | Successful DELETE | β No |
| 400 | Bad Request | Invalid request syntax | β Yes |
| 401 | Unauthorized | Missing/invalid auth | β Yes |
| 403 | Forbidden | Insufficient permissions | β Yes |
| 404 | Not Found | Resource doesnβt exist | β Yes |
| 405 | Method Not Allowed | Method not supported | β Yes |
| 409 | Conflict | Resource conflict | β Yes |
| 422 | Unprocessable Entity | Validation errors | β Yes |
| 429 | Too Many Requests | Rate limit exceeded | β Yes |
| 500 | Internal Server Error | Server error | β Yes |
| 502 | Bad Gateway | Upstream error | β Optional |
| 503 | Service Unavailable | Temporary unavailability | β Optional |
| 504 | Gateway Timeout | Upstream timeout | β Optional |
Request Format
Basic Request Structure π
# Request lineGET /api/users/123 HTTP/1.1
# HeadersHost: api.example.comAuthorization: Bearer token123Content-Type: application/jsonAccept: application/jsonUser-Agent: MyApp/1.0
# Body (for POST, PUT, PATCH){ "name": "John Doe", "email": "john@example.com"}cURL Examples π§
# GET requestcurl -X GET https://api.example.com/users/123 \ -H "Authorization: Bearer token123" \ -H "Accept: application/json"
# POST request with JSON bodycurl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token123" \ -d '{ "name": "John Doe", "email": "john@example.com" }'
# POST request with file uploadcurl -X POST https://api.example.com/upload \ -H "Authorization: Bearer token123" \ -F "file=@/path/to/file.jpg" \ -F "description=Profile picture"
# PUT requestcurl -X PUT https://api.example.com/users/123 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer token123" \ -d '{ "name": "John Doe", "email": "john@example.com", "age": 30 }'
# DELETE requestcurl -X DELETE https://api.example.com/users/123 \ -H "Authorization: Bearer token123"HTTPie Examples π
# GET request (simpler syntax)http GET api.example.com/users/123 \ Authorization:"Bearer token123"
# POST requesthttp POST api.example.com/users \ name="John Doe" \ email="john@example.com" \ Authorization:"Bearer token123"
# POST with JSON filehttp POST api.example.com/users \ < user.json \ Authorization:"Bearer token123"Response Format
Standard Response Structure π¦
// Success response (200 OK){ "data": { "id": 123, "name": "John Doe", "email": "john@example.com" }}
// Success response - array (200 OK){ "data": [ { "id": 123, "name": "John Doe", "email": "john@example.com" }, { "id": 124, "name": "Jane Smith", "email": "jane@example.com" } ], "meta": { "total": 2, "page": 1, "per_page": 20 }}
// Error response (400 Bad Request){ "error": { "code": "VALIDATION_ERROR", "message": "Invalid input", "details": { "email": "Invalid email format" } }}Response Headers π
# Success response headersHTTP/1.1 201 CreatedContent-Type: application/jsonLocation: /api/users/123ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"Cache-Control: max-age=3600X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
# Error response headersHTTP/1.1 429 Too Many RequestsContent-Type: application/jsonRetry-After: 60X-RateLimit-Limit: 1000X-RateLimit-Remaining: 0X-RateLimit-Reset: 1609459200Consistent Response Format β
// β
DO: Consistent structure{ "data": { /* resource */ }, "meta": { /* metadata */ }, "links": { /* pagination links */ }}
// β
DO: Consistent error format{ "error": { "code": "ERROR_CODE", "message": "Human-readable message", "details": { /* additional info */ } }}
// β DON'T: Inconsistent structures{ "user": { /* ... */ } } // Sometimes "user"{ "data": { /* ... */ } } // Sometimes "data"{ "result": { /* ... */ } } // Sometimes "result"Headers
Common Request Headers π€
# Content negotiationAccept: application/jsonAccept: application/json, application/xmlAccept: application/json;q=0.9, application/xml;q=0.8
# Content type (for POST, PUT, PATCH)Content-Type: application/jsonContent-Type: application/x-www-form-urlencodedContent-Type: multipart/form-data
# AuthenticationAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=Authorization: ApiKey abc123xyz
# Conditional requestsIf-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
# CORSOrigin: https://example.comAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: Content-TypeCommon Response Headers π₯
# Content typeContent-Type: application/json; charset=utf-8Content-Type: application/xml
# CachingCache-Control: max-age=3600Cache-Control: no-cache, no-store, must-revalidateETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
# CORSAccess-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, POST, PUT, DELETEAccess-Control-Allow-Headers: Content-Type, AuthorizationAccess-Control-Max-Age: 86400
# Rate limitingX-RateLimit-Limit: 1000X-RateLimit-Remaining: 999X-RateLimit-Reset: 1609459200
# PaginationLink: <https://api.example.com/users?page=2>; rel="next", <https://api.example.com/users?page=10>; rel="last"
# Request trackingX-Request-ID: 550e8400-e29b-41d4-a716-446655440000Header Examples π§
# Custom headerscurl -X GET https://api.example.com/users \ -H "X-API-Version: v2" \ -H "X-Client-ID: myapp-123"
# Conditional GET (only fetch if changed)curl -X GET https://api.example.com/users/123 \ -H "If-None-Match: \"abc123\"" \ -H "If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT"
# Content negotiationcurl -X GET https://api.example.com/users \ -H "Accept: application/json" \ -H "Accept-Language: en-US,en;q=0.9"Authentication
Bearer Token (JWT) π
# Request with Bearer tokenGET /api/users HTTP/1.1Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# cURL examplecurl -X GET https://api.example.com/users \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."API Key π
# API Key in headerGET /api/users HTTP/1.1X-API-Key: abc123xyz789
# API Key in query parameter (less secure)GET /api/users?api_key=abc123xyz789 HTTP/1.1
# cURL examplecurl -X GET https://api.example.com/users \ -H "X-API-Key: abc123xyz789"Basic Authentication π€
# Basic auth (username:password base64 encoded)curl -X GET https://api.example.com/users \ -u username:password
# Or manuallycurl -X GET https://api.example.com/users \ -H "Authorization: Basic $(echo -n 'username:password' | base64)"OAuth 2.0 π
# OAuth 2.0 Authorization Code Flow# Step 1: Redirect to authorization serverGET /oauth/authorize?client_id=abc123&redirect_uri=https://app.com/callback&response_type=code&scope=read write
# Step 2: Exchange code for tokenPOST /oauth/token HTTP/1.1Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=xyz789&redirect_uri=https://app.com/callback&client_id=abc123&client_secret=secret456
# Step 3: Use access tokenGET /api/users HTTP/1.1Authorization: Bearer access_token_hereAuthentication Best Practices β
# β
DO: Use HTTPS for all authenticationhttps://api.example.com/users # β
Securehttp://api.example.com/users # β Insecure
# β
DO: Include token expiration{ "access_token": "eyJhbGci...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "refresh_token_here"}
# β
DO: Return 401 for invalid/missing tokensHTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer{ "error": { "code": "UNAUTHORIZED", "message": "Invalid or expired token" }}
# β DON'T: Put sensitive data in query paramsGET /api/users?token=secret123 # β Visible in logs, URLsQuery Parameters
Basic Query Parameters π
# Single parameterGET /api/users?status=active
# Multiple parametersGET /api/users?status=active&role=admin
# Array parameters (multiple values)GET /api/users?tags=javascript&tags=react&tags=nodejs
# Array parameters (comma-separated)GET /api/users?tags=javascript,react,nodejs
# Nested parameters (depends on API design)GET /api/users?filter[status]=active&filter[role]=adminCommon Query Patterns π
# Paginationcurl "https://api.example.com/users?page=1&per_page=20"
# Filteringcurl "https://api.example.com/users?status=active&role=admin"
# Sortingcurl "https://api.example.com/users?sort=name&order=asc"curl "https://api.example.com/users?sort=-created_at" # Descending
# Searchingcurl "https://api.example.com/users?q=john&search_fields=name,email"
# Field selection (sparse fieldsets)curl "https://api.example.com/users?fields=id,name,email"
# Including related resourcescurl "https://api.example.com/users?include=posts,comments"URL Encoding π€
# Special characters must be URL encoded# Space β %20 or +# & β %26# = β %3D# ? β %3F
# Example: Search for "John Doe"curl "https://api.example.com/users?q=John%20Doe"curl "https://api.example.com/users?q=John+Doe" # Also works
# cURL automatically encodes, but be explicit if neededcurl -G "https://api.example.com/users" \ --data-urlencode "q=John Doe" \ --data-urlencode "filter=active"Path Parameters
Resource Identification π―
# Single resource by IDGET /api/users/123
# Nested resourcesGET /api/users/123/postsGET /api/users/123/posts/456
# Multiple IDs (comma-separated)GET /api/users/123,124,125
# UUIDsGET /api/users/550e8400-e29b-41d4-a716-446655440000
# SlugsGET /api/posts/getting-started-with-rest-apiPath Parameter Examples π‘
# Single resourcecurl https://api.example.com/users/123
# Nested resourcecurl https://api.example.com/users/123/posts/456
# With query parameterscurl "https://api.example.com/users/123/posts?status=published"
# Multiple resources (if supported)curl https://api.example.com/users/123,124,125Path Design Best Practices β
# β
DO: Use nouns, not verbsGET /api/users # β
GoodGET /api/getUsers # β Bad
# β
DO: Use plural nounsGET /api/users # β
GoodGET /api/user # β Bad
# β
DO: Use consistent nestingGET /api/users/123/posts # β
GoodGET /api/posts?user_id=123 # β
Also good (flatter)
# β DON'T: Mix singular/pluralGET /api/user/123/posts # β InconsistentGET /api/users/123/post # β InconsistentRequest Body
JSON Body π
// POST - Create resourcePOST /api/usersContent-Type: application/json
{ "name": "John Doe", "email": "john@example.com", "age": 30, "tags": ["developer", "javascript"]}
// PUT - Replace resourcePUT /api/users/123Content-Type: application/json
{ "name": "John Doe", "email": "john@example.com", "age": 30}
// PATCH - Partial updatePATCH /api/users/123Content-Type: application/json
{ "email": "newemail@example.com"}Form Data π
POST /api/users HTTP/1.1Content-Type: application/x-www-form-urlencoded
name=John+Doe&email=john%40example.com&age=30
# multipart/form-data (for file uploads)POST /api/users HTTP/1.1Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data; name="name"
John Doe------WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data; name="avatar"; filename="avatar.jpg"Content-Type: image/jpeg
[binary data]------WebKitFormBoundary7MA4YWxkTrZu0gW--cURL Body Examples π§
# JSON body from stringcurl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"john@example.com"}'
# JSON body from filecurl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d @user.json
# Form datacurl -X POST https://api.example.com/users \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "name=John&email=john@example.com"
# File uploadcurl -X POST https://api.example.com/upload \ -F "file=@/path/to/file.jpg" \ -F "description=Profile picture"Error Handling
Standard Error Response Format π¨
// Standard error structure{ "error": { "code": "ERROR_CODE", "message": "Human-readable error message", "details": { "field": "Additional error details" }, "request_id": "550e8400-e29b-41d4-a716-446655440000" }}
// Validation errors (422){ "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "email": ["Invalid email format", "Email is required"], "age": ["Age must be a positive number"] } }}
// Not found (404){ "error": { "code": "NOT_FOUND", "message": "User not found", "details": { "resource": "user", "id": "123" } }}Error Status Codes π
# 400 Bad Request - Invalid syntaxHTTP/1.1 400 Bad Request{ "error": { "code": "BAD_REQUEST", "message": "Invalid JSON syntax" }}
# 401 Unauthorized - Missing/invalid authHTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer{ "error": { "code": "UNAUTHORIZED", "message": "Invalid or missing authentication token" }}
# 403 Forbidden - Insufficient permissionsHTTP/1.1 403 Forbidden{ "error": { "code": "FORBIDDEN", "message": "You don't have permission to access this resource" }}
# 404 Not Found - Resource doesn't existHTTP/1.1 404 Not Found{ "error": { "code": "NOT_FOUND", "message": "User with ID 123 not found" }}
# 422 Unprocessable Entity - Validation errorsHTTP/1.1 422 Unprocessable Entity{ "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "email": "Invalid email format" } }}
# 429 Too Many Requests - Rate limitHTTP/1.1 429 Too Many RequestsRetry-After: 60X-RateLimit-Limit: 1000X-RateLimit-Remaining: 0{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Please try again in 60 seconds." }}
# 500 Internal Server ErrorHTTP/1.1 500 Internal Server Error{ "error": { "code": "INTERNAL_ERROR", "message": "An internal error occurred", "request_id": "550e8400-e29b-41d4-a716-446655440000" }}Error Handling Best Practices β
// β
DO: Consistent error structure{ "error": { "code": "ERROR_CODE", "message": "Human-readable message", "details": {} }}
// β
DO: Include request ID for debugging{ "error": { "code": "INTERNAL_ERROR", "message": "An error occurred", "request_id": "550e8400-e29b-41d4-a716-446655440000" }}
// β
DO: Provide actionable error messages{ "error": { "code": "VALIDATION_ERROR", "message": "Email format is invalid. Please use format: user@example.com" }}
// β DON'T: Expose internal details{ "error": { "message": "SQL Error: Column 'email' cannot be null" }}
// β DON'T: Inconsistent error formats{ "error": "Something went wrong" } // Sometimes string{ "error": { "message": "Error" } } // Sometimes object{ "errors": [{ "field": "email" }] } // Sometimes arrayAPI Design Principles
RESTful Principles π―
# β
Stateless - Each request contains all information neededGET /api/users/123 HTTP/1.1Authorization: Bearer token123 # Auth in every request
# β
Resource-based URLsGET /api/users # CollectionGET /api/users/123 # Single resourceGET /api/users/123/posts # Nested resource
# β
Use HTTP methods correctlyGET /api/users # RetrievePOST /api/users # CreatePUT /api/users/123 # ReplacePATCH /api/users/123 # UpdateDELETE /api/users/123 # Delete
# β
Use proper status codes201 Created # After POST200 OK # After GET, PUT, PATCH204 No Content # After DELETE404 Not Found # Resource missingResource Naming Conventions π
# β
DO: Use nouns (plural for collections)GET /api/usersGET /api/postsGET /api/comments
# β
DO: Use consistent namingGET /api/users/123/posts # NestedGET /api/posts?user_id=123 # Filtered
# β
DO: Use kebab-case or snake_caseGET /api/user-profiles # kebab-caseGET /api/user_profiles # snake_case
# β DON'T: Use verbs in URLsGET /api/getUsers # β BadGET /api/createUser # β BadGET /api/users/123/delete # β Bad
# β DON'T: Mix naming conventionsGET /api/userProfiles # β camelCase (inconsistent)GET /api/user-profiles # β
kebab-caseHTTP Method Semantics π
# GET - Safe and idempotent (no side effects)GET /api/users/123# β Always returns same result, doesn't modify anything
# POST - Not idempotent (creates new resource)POST /api/users# β Each call creates new user (different ID)
# PUT - Idempotent (replaces resource)PUT /api/users/123# β Same request multiple times = same result
# PATCH - Idempotent (partial update)PATCH /api/users/123# β Same request multiple times = same result
# DELETE - Idempotent (removes resource)DELETE /api/users/123# β First call deletes, subsequent calls still return 204Versioning
URL Versioning π
# Version in URL pathGET /api/v1/usersGET /api/v2/users
# Version in subdomainGET https://v1.api.example.com/usersGET https://v2.api.example.com/users
# cURL examplescurl https://api.example.com/v1/userscurl https://api.example.com/v2/usersHeader Versioning π
# Version in custom headerGET /api/users HTTP/1.1X-API-Version: v2
# Version in Accept headerGET /api/users HTTP/1.1Accept: application/vnd.example.v2+json
# cURL examplescurl -X GET https://api.example.com/users \ -H "X-API-Version: v2"
curl -X GET https://api.example.com/users \ -H "Accept: application/vnd.example.v2+json"Versioning Best Practices β
# β
DO: Version from the startGET /api/v1/users # β
Good - versioned from day 1
# β
DO: Use semantic versioningGET /api/v1.0/users # Major.minorGET /api/v1.1/users # Minor changes
# β
DO: Maintain backward compatibility# v1 still works when v2 is releasedGET /api/v1/users # β
Still supportedGET /api/v2/users # β
New version
# β
DO: Document version deprecationHTTP/1.1 200 OKX-API-Version: v1X-API-Deprecated: trueX-API-Sunset: 2024-12-31{ "warning": "API v1 is deprecated. Please migrate to v2 by 2024-12-31"}
# β DON'T: Break existing clients# Removing v1 immediately breaks existing integrationsPagination
Offset-Based Pagination π
# Basic paginationGET /api/users?page=1&per_page=20
# Response{ "data": [ /* users */ ], "pagination": { "page": 1, "per_page": 20, "total": 100, "total_pages": 5 }}
# cURL examplecurl "https://api.example.com/users?page=2&per_page=20"Cursor-Based Pagination π―
# Cursor pagination (better for large datasets)GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20
# Response{ "data": [ /* users */ ], "pagination": { "cursor": "eyJpZCI6MTQzfQ", "has_more": true, "limit": 20 }}
# Next pageGET /api/users?cursor=eyJpZCI6MTQzfQ&limit=20Link Header Pagination π
# Pagination via Link header (RFC 5988)HTTP/1.1 200 OKLink: <https://api.example.com/users?page=2>; rel="next", <https://api.example.com/users?page=5>; rel="last", <https://api.example.com/users?page=1>; rel="first"
# Response body{ "data": [ /* users */ ], "meta": { "page": 1, "per_page": 20, "total": 100 }}Pagination Examples π‘
# Offset paginationcurl "https://api.example.com/users?page=1&per_page=20"
# Cursor paginationcurl "https://api.example.com/users?cursor=abc123&limit=20"
# With filterscurl "https://api.example.com/users?status=active&page=1&per_page=20"Filtering & Sorting
Filtering π
# Simple filtersGET /api/users?status=activeGET /api/users?role=admin&status=active
# Range filtersGET /api/users?age_min=18&age_max=65GET /api/users?created_after=2024-01-01&created_before=2024-12-31
# Array filtersGET /api/users?tags=javascript&tags=reactGET /api/users?tags=javascript,react,nodejs
# Nested filters (depends on API design)GET /api/users?filter[status]=active&filter[role]=adminGET /api/users?filter[age][gte]=18&filter[age][lte]=65
# SearchGET /api/users?q=john&search_fields=name,emailSorting π
# Single field sortingGET /api/users?sort=nameGET /api/users?sort=name&order=ascGET /api/users?sort=name&order=desc
# Descending with minus prefixGET /api/users?sort=-created_at
# Multiple field sortingGET /api/users?sort=status,nameGET /api/users?sort=status,-created_at
# cURL examplescurl "https://api.example.com/users?sort=name&order=asc"curl "https://api.example.com/users?sort=-created_at"Field Selection π―
# Sparse fieldsets - return only specified fieldsGET /api/users?fields=id,name,email
# Exclude fieldsGET /api/users?exclude=password,secret_token
# Response{ "data": [ { "id": 123, "name": "John Doe", "email": "john@example.com" } ]}Including Related Resources π
# Include related resourcesGET /api/users/123?include=posts,comments
# Nested includesGET /api/users/123?include=posts.author,posts.comments
# Response{ "data": { "id": 123, "name": "John Doe", "posts": [ /* posts */ ], "comments": [ /* comments */ ] }}Rate Limiting
Rate Limit Headers π
# Rate limit information in headersHTTP/1.1 200 OKX-RateLimit-Limit: 1000X-RateLimit-Remaining: 999X-RateLimit-Reset: 1609459200
# Rate limit exceededHTTP/1.1 429 Too Many RequestsRetry-After: 60X-RateLimit-Limit: 1000X-RateLimit-Remaining: 0X-RateLimit-Reset: 1609459200{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Please try again in 60 seconds." }}Rate Limit Strategies π―
# Fixed window (requests per hour)X-RateLimit-Limit: 1000X-RateLimit-Window: 3600 # 1 hour in seconds
# Sliding windowX-RateLimit-Limit: 1000X-RateLimit-Window: 3600
# Token bucketX-RateLimit-Limit: 1000X-RateLimit-Burst: 100 # Allow bursts
# Per-endpoint limitsX-RateLimit-Limit: 1000 # General APIX-RateLimit-Limit-Search: 100 # Search endpointRate Limit Examples π‘
# Check rate limit headerscurl -I https://api.example.com/users \ -H "Authorization: Bearer token123"
# Response includes:# X-RateLimit-Limit: 1000# X-RateLimit-Remaining: 999# X-RateLimit-Reset: 1609459200
# Handle 429 responseHTTP/1.1 429 Too Many RequestsRetry-After: 60# Wait 60 seconds before retryingBest Practices
API Design β
# β
DO: Use HTTPS for all endpointshttps://api.example.com/users # β
Secure
# β
DO: Use consistent namingGET /api/users # β
Plural nounsGET /api/users/123 # β
Resource IDGET /api/users/123/posts # β
Nested resources
# β
DO: Use proper HTTP methodsGET /api/users # β
RetrievePOST /api/users # β
CreatePUT /api/users/123 # β
ReplacePATCH /api/users/123 # β
UpdateDELETE /api/users/123 # β
Delete
# β
DO: Return appropriate status codes201 Created # β
After POST200 OK # β
After GET, PUT, PATCH204 No Content # β
After DELETE404 Not Found # β
Resource missing
# β
DO: Use consistent response format{ "data": { /* resource */ }, "meta": { /* metadata */ }}
# β
DO: Include request IDs for debuggingX-Request-ID: 550e8400-e29b-41d4-a716-446655440000
# β
DO: Version your APIGET /api/v1/usersGET /api/v2/usersSecurity π
# β
DO: Use HTTPS onlyhttps://api.example.com/users # β
Securehttp://api.example.com/users # β Insecure
# β
DO: Validate and sanitize all inputPOST /api/users{ "email": "user@example.com" # β
Validate format}
# β
DO: Use authentication tokensAuthorization: Bearer eyJhbGci... # β
Secure token
# β
DO: Implement rate limitingX-RateLimit-Limit: 1000X-RateLimit-Remaining: 999
# β
DO: Use CORS properlyAccess-Control-Allow-Origin: https://example.com # β
Specific originAccess-Control-Allow-Origin: * # β Too permissive
# β
DO: Sanitize error messages{ "error": "An error occurred" # β
Generic}# Not: "SQL Error: Column 'email' cannot be null" # β Exposes internals
# β DON'T: Put sensitive data in URLsGET /api/users?token=secret123 # β Visible in logsGET /api/usersAuthorization: Bearer secret123 # β
In headerPerformance β‘
# β
DO: Implement paginationGET /api/users?page=1&per_page=20
# β
DO: Support field selectionGET /api/users?fields=id,name,email
# β
DO: Use caching headersCache-Control: max-age=3600ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# β
DO: Support conditional requestsIf-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
# β
DO: Compress responsesContent-Encoding: gzip
# β
DO: Use connection pooling# Handled by HTTP client librariesDocumentation π
# β
DO: Provide OpenAPI/Swagger documentationGET /api/docsGET /api/swagger.json
# β
DO: Include examples in documentation# Example requestPOST /api/users{ "name": "John Doe", "email": "john@example.com"}
# Example response{ "data": { "id": 123, "name": "John Doe", "email": "john@example.com" }}
# β
DO: Document error codes# 400 - Bad Request# 401 - Unauthorized# 404 - Not Found# 422 - Validation Error# 429 - Rate Limit Exceeded# 500 - Internal Server Error
# β
DO: Document rate limits# Rate Limit: 1000 requests per hour# Headers: X-RateLimit-Limit, X-RateLimit-RemainingCommon Pitfalls
Design Mistakes β
# β DON'T: Use verbs in URLsGET /api/getUsers # β BadGET /api/users # β
Good
# β DON'T: Mix singular/pluralGET /api/user/123/posts # β InconsistentGET /api/users/123/posts # β
Consistent
# β DON'T: Use wrong HTTP methodsGET /api/users/123/delete # β BadDELETE /api/users/123 # β
Good
# β DON'T: Return wrong status codesPOST /api/usersβ 200 OK # β Should be 201 Created
DELETE /api/users/123β 200 OK # β Should be 204 No Content
# β DON'T: Inconsistent response formats{ "user": { /* ... */ } } # Sometimes "user"{ "data": { /* ... */ } } # Sometimes "data"{ "result": { /* ... */ } } # Sometimes "result"
# β DON'T: Ignore idempotencyPOST /api/users # β Creates new user each timePUT /api/users/123 # β
Same request = same resultSecurity Mistakes π
# β DON'T: Use HTTP instead of HTTPShttp://api.example.com/users # β Insecure
# β DON'T: Put tokens in query parametersGET /api/users?token=secret123 # β Visible in logs, URLs
# β DON'T: Expose internal errors{ "error": "SQL Error: Column 'email' cannot be null" # β Exposes internals}
# β DON'T: Allow CORS from any originAccess-Control-Allow-Origin: * # β Too permissive
# β DON'T: Skip input validationPOST /api/users{ "email": "not-an-email" # β Should validate format}
# β DON'T: Return sensitive data{ "password": "plaintext123", # β Never return passwords "secret_key": "abc123" # β Never return secrets}Performance Mistakes β‘
# β DON'T: Return all data without paginationGET /api/usersβ Returns 10,000 users # β Too much data
# β DON'T: Return unnecessary fieldsGET /api/usersβ Returns password, secret_token # β Security + performance issue
# β DON'T: Ignore caching# No Cache-Control headers # β Missed optimization
# β DON'T: N+1 query problemGET /api/usersβ Each user makes separate request for posts # β InefficientError Handling Mistakes π¨
# β DON'T: Inconsistent error formats{ "error": "Something went wrong" } # Sometimes string{ "error": { "message": "Error" } } # Sometimes object{ "errors": [{ "field": "email" }] } # Sometimes array
# β DON'T: Wrong status codesGET /api/users/999β 500 Internal Server Error # β Should be 404 Not Found
# β DON'T: Missing error details{ "error": "Error occurred" # β Not helpful}# Should include:{ "error": { "code": "VALIDATION_ERROR", "message": "Email format is invalid", "details": { "email": "Invalid format" } }}
# β DON'T: Expose stack traces in production{ "error": "Error", "stack": "Error: ...\n at ..." # β Security risk}β οΈ Critical Notes:
- Always use HTTPS in production - never expose APIs over HTTP
- Validate and sanitize all user input - never trust client data
- Implement proper authentication and authorization - donβt rely on security through obscurity
- Use appropriate HTTP status codes - theyβre part of the API contract
- Version your API from the start - breaking changes break integrations
- Implement rate limiting - protect your API from abuse
- Return consistent error formats - makes debugging easier for clients
- Never expose internal errors or stack traces - security risk