Skip to main content

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 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)
Terminal window
# Check cURL version
curl --version
# β†’ curl 8.1.2
# Test API endpoint
curl -X GET https://api.example.com/users
# Install HTTPie (macOS)
brew install httpie
# Install HTTPie (Linux)
apt install httpie # Debian/Ubuntu
yum install httpie # RHEL/CentOS

Common Content Types:

  • application/json - JSON data (most common)
  • application/xml - XML data
  • application/x-www-form-urlencoded - Form data
  • multipart/form-data - File uploads
  • text/plain - Plain text

HTTP Methods

Core Methods 🎯

# GET - Retrieve resource(s)
GET /api/users
GET /api/users/123
# POST - Create new resource
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
# PUT - Replace entire resource (idempotent)
PUT /api/users/123
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
# PATCH - Partial update (idempotent)
PATCH /api/users/123
Content-Type: application/json
{
"email": "newemail@example.com"
}
# DELETE - Remove resource (idempotent)
DELETE /api/users/123

Method Characteristics πŸ“Š

MethodIdempotentSafeRequest BodyResponse 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 πŸ’‘

Terminal window
# GET - Fetch all users
curl -X GET https://api.example.com/users \
-H "Authorization: Bearer token123"
# GET - Fetch single user
curl -X GET https://api.example.com/users/123
# POST - Create user
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@example.com"}'
# PUT - Replace entire user
curl -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 only
curl -X PATCH https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"email":"newemail@example.com"}'
# DELETE - Remove user
curl -X DELETE https://api.example.com/users/123

Status Codes

Success Codes (2xx) βœ…

# 200 OK - Standard success response
GET /api/users/123
β†’ 200 OK
# 201 Created - Resource created successfully
POST /api/users
β†’ 201 Created
Location: /api/users/123
# 202 Accepted - Request accepted for processing (async)
POST /api/jobs
β†’ 202 Accepted
# 204 No Content - Success but no content to return
DELETE /api/users/123
β†’ 204 No Content

Client Error Codes (4xx) ❌

# 400 Bad Request - Invalid request syntax
POST /api/users
Content-Type: application/json
{"invalid": "json"
β†’ 400 Bad Request
{
"error": "Invalid JSON syntax"
}
# 401 Unauthorized - Authentication required
GET /api/users
β†’ 401 Unauthorized
WWW-Authenticate: Bearer
# 403 Forbidden - Authenticated but not authorized
GET /api/admin/users
β†’ 403 Forbidden
{
"error": "Insufficient permissions"
}
# 404 Not Found - Resource doesn't exist
GET /api/users/999
β†’ 404 Not Found
{
"error": "User not found"
}
# 405 Method Not Allowed - HTTP method not supported
PATCH /api/users
β†’ 405 Method Not Allowed
Allow: 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 errors
POST /api/users
{"email": "invalid-email"}
β†’ 422 Unprocessable Entity
{
"error": "Validation failed",
"details": {
"email": "Invalid email format"
}
}
# 429 Too Many Requests - Rate limit exceeded
GET /api/users
β†’ 429 Too Many Requests
Retry-After: 60
{
"error": "Rate limit exceeded"
}

Server Error Codes (5xx) ⚠️

# 500 Internal Server Error - Generic server error
GET /api/users
β†’ 500 Internal Server Error
{
"error": "Internal server error"
}
# 502 Bad Gateway - Invalid response from upstream
GET /api/users
β†’ 502 Bad Gateway
# 503 Service Unavailable - Server temporarily unavailable
GET /api/users
β†’ 503 Service Unavailable
Retry-After: 300
# 504 Gateway Timeout - Upstream server timeout
GET /api/users
β†’ 504 Gateway Timeout

Status Code Quick Reference πŸ“‹

CodeNameUse CaseResponse Body
200OKSuccessful GET, PUT, PATCHβœ… Yes
201CreatedSuccessful POSTβœ… Yes
202AcceptedAsync processing startedβœ… Optional
204No ContentSuccessful DELETE❌ No
400Bad RequestInvalid request syntaxβœ… Yes
401UnauthorizedMissing/invalid authβœ… Yes
403ForbiddenInsufficient permissionsβœ… Yes
404Not FoundResource doesn’t existβœ… Yes
405Method Not AllowedMethod not supportedβœ… Yes
409ConflictResource conflictβœ… Yes
422Unprocessable EntityValidation errorsβœ… Yes
429Too Many RequestsRate limit exceededβœ… Yes
500Internal Server ErrorServer errorβœ… Yes
502Bad GatewayUpstream errorβœ… Optional
503Service UnavailableTemporary unavailabilityβœ… Optional
504Gateway TimeoutUpstream timeoutβœ… Optional

Request Format

Basic Request Structure πŸ“

# Request line
GET /api/users/123 HTTP/1.1
# Headers
Host: api.example.com
Authorization: Bearer token123
Content-Type: application/json
Accept: application/json
User-Agent: MyApp/1.0
# Body (for POST, PUT, PATCH)
{
"name": "John Doe",
"email": "john@example.com"
}

cURL Examples πŸ”§

Terminal window
# GET request
curl -X GET https://api.example.com/users/123 \
-H "Authorization: Bearer token123" \
-H "Accept: application/json"
# POST request with JSON body
curl -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 upload
curl -X POST https://api.example.com/upload \
-H "Authorization: Bearer token123" \
-F "file=@/path/to/file.jpg" \
-F "description=Profile picture"
# PUT request
curl -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 request
curl -X DELETE https://api.example.com/users/123 \
-H "Authorization: Bearer token123"

HTTPie Examples πŸš€

Terminal window
# GET request (simpler syntax)
http GET api.example.com/users/123 \
Authorization:"Bearer token123"
# POST request
http POST api.example.com/users \
name="John Doe" \
email="john@example.com" \
Authorization:"Bearer token123"
# POST with JSON file
http 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 headers
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/users/123
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
# Error response headers
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1609459200

Consistent 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 negotiation
Accept: application/json
Accept: application/json, application/xml
Accept: application/json;q=0.9, application/xml;q=0.8
# Content type (for POST, PUT, PATCH)
Content-Type: application/json
Content-Type: application/x-www-form-urlencoded
Content-Type: multipart/form-data
# Authentication
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Authorization: ApiKey abc123xyz
# Conditional requests
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
# CORS
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

Common Response Headers πŸ“₯

# Content type
Content-Type: application/json; charset=utf-8
Content-Type: application/xml
# Caching
Cache-Control: max-age=3600
Cache-Control: no-cache, no-store, must-revalidate
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
# CORS
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
# Rate limiting
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200
# Pagination
Link: <https://api.example.com/users?page=2>; rel="next",
<https://api.example.com/users?page=10>; rel="last"
# Request tracking
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000

Header Examples πŸ”§

Terminal window
# Custom headers
curl -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 negotiation
curl -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 token
GET /api/users HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# cURL example
curl -X GET https://api.example.com/users \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

API Key πŸ”‘

# API Key in header
GET /api/users HTTP/1.1
X-API-Key: abc123xyz789
# API Key in query parameter (less secure)
GET /api/users?api_key=abc123xyz789 HTTP/1.1
# cURL example
curl -X GET https://api.example.com/users \
-H "X-API-Key: abc123xyz789"

Basic Authentication πŸ‘€

Terminal window
# Basic auth (username:password base64 encoded)
curl -X GET https://api.example.com/users \
-u username:password
# Or manually
curl -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 server
GET /oauth/authorize?client_id=abc123&redirect_uri=https://app.com/callback&response_type=code&scope=read write
# Step 2: Exchange code for token
POST /oauth/token HTTP/1.1
Content-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 token
GET /api/users HTTP/1.1
Authorization: Bearer access_token_here

Authentication Best Practices βœ…

# βœ… DO: Use HTTPS for all authentication
https://api.example.com/users # βœ… Secure
http://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 tokens
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or expired token"
}
}
# ❌ DON'T: Put sensitive data in query params
GET /api/users?token=secret123 # ❌ Visible in logs, URLs

Query Parameters

Basic Query Parameters πŸ”

# Single parameter
GET /api/users?status=active
# Multiple parameters
GET /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]=admin

Common Query Patterns πŸ“‹

Terminal window
# Pagination
curl "https://api.example.com/users?page=1&per_page=20"
# Filtering
curl "https://api.example.com/users?status=active&role=admin"
# Sorting
curl "https://api.example.com/users?sort=name&order=asc"
curl "https://api.example.com/users?sort=-created_at" # Descending
# Searching
curl "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 resources
curl "https://api.example.com/users?include=posts,comments"

URL Encoding πŸ”€

Terminal window
# 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 needed
curl -G "https://api.example.com/users" \
--data-urlencode "q=John Doe" \
--data-urlencode "filter=active"

Path Parameters

Resource Identification 🎯

# Single resource by ID
GET /api/users/123
# Nested resources
GET /api/users/123/posts
GET /api/users/123/posts/456
# Multiple IDs (comma-separated)
GET /api/users/123,124,125
# UUIDs
GET /api/users/550e8400-e29b-41d4-a716-446655440000
# Slugs
GET /api/posts/getting-started-with-rest-api

Path Parameter Examples πŸ’‘

Terminal window
# Single resource
curl https://api.example.com/users/123
# Nested resource
curl https://api.example.com/users/123/posts/456
# With query parameters
curl "https://api.example.com/users/123/posts?status=published"
# Multiple resources (if supported)
curl https://api.example.com/users/123,124,125

Path Design Best Practices βœ…

# βœ… DO: Use nouns, not verbs
GET /api/users # βœ… Good
GET /api/getUsers # ❌ Bad
# βœ… DO: Use plural nouns
GET /api/users # βœ… Good
GET /api/user # ❌ Bad
# βœ… DO: Use consistent nesting
GET /api/users/123/posts # βœ… Good
GET /api/posts?user_id=123 # βœ… Also good (flatter)
# ❌ DON'T: Mix singular/plural
GET /api/user/123/posts # ❌ Inconsistent
GET /api/users/123/post # ❌ Inconsistent

Request Body

JSON Body πŸ“„

// POST - Create resource
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30,
"tags": ["developer", "javascript"]
}
// PUT - Replace resource
PUT /api/users/123
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
// PATCH - Partial update
PATCH /api/users/123
Content-Type: application/json
{
"email": "newemail@example.com"
}

Form Data πŸ“

application/x-www-form-urlencoded
POST /api/users HTTP/1.1
Content-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.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"
John Doe
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="avatar.jpg"
Content-Type: image/jpeg
[binary data]
------WebKitFormBoundary7MA4YWxkTrZu0gW--

cURL Body Examples πŸ”§

Terminal window
# JSON body from string
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@example.com"}'
# JSON body from file
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @user.json
# Form data
curl -X POST https://api.example.com/users \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=John&email=john@example.com"
# File upload
curl -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 syntax
HTTP/1.1 400 Bad Request
{
"error": {
"code": "BAD_REQUEST",
"message": "Invalid JSON syntax"
}
}
# 401 Unauthorized - Missing/invalid auth
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing authentication token"
}
}
# 403 Forbidden - Insufficient permissions
HTTP/1.1 403 Forbidden
{
"error": {
"code": "FORBIDDEN",
"message": "You don't have permission to access this resource"
}
}
# 404 Not Found - Resource doesn't exist
HTTP/1.1 404 Not Found
{
"error": {
"code": "NOT_FOUND",
"message": "User with ID 123 not found"
}
}
# 422 Unprocessable Entity - Validation errors
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"email": "Invalid email format"
}
}
}
# 429 Too Many Requests - Rate limit
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please try again in 60 seconds."
}
}
# 500 Internal Server Error
HTTP/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 array

API Design Principles

RESTful Principles 🎯

# βœ… Stateless - Each request contains all information needed
GET /api/users/123 HTTP/1.1
Authorization: Bearer token123 # Auth in every request
# βœ… Resource-based URLs
GET /api/users # Collection
GET /api/users/123 # Single resource
GET /api/users/123/posts # Nested resource
# βœ… Use HTTP methods correctly
GET /api/users # Retrieve
POST /api/users # Create
PUT /api/users/123 # Replace
PATCH /api/users/123 # Update
DELETE /api/users/123 # Delete
# βœ… Use proper status codes
201 Created # After POST
200 OK # After GET, PUT, PATCH
204 No Content # After DELETE
404 Not Found # Resource missing

Resource Naming Conventions πŸ“

# βœ… DO: Use nouns (plural for collections)
GET /api/users
GET /api/posts
GET /api/comments
# βœ… DO: Use consistent naming
GET /api/users/123/posts # Nested
GET /api/posts?user_id=123 # Filtered
# βœ… DO: Use kebab-case or snake_case
GET /api/user-profiles # kebab-case
GET /api/user_profiles # snake_case
# ❌ DON'T: Use verbs in URLs
GET /api/getUsers # ❌ Bad
GET /api/createUser # ❌ Bad
GET /api/users/123/delete # ❌ Bad
# ❌ DON'T: Mix naming conventions
GET /api/userProfiles # ❌ camelCase (inconsistent)
GET /api/user-profiles # βœ… kebab-case

HTTP 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 204

Versioning

URL Versioning 🌐

# Version in URL path
GET /api/v1/users
GET /api/v2/users
# Version in subdomain
GET https://v1.api.example.com/users
GET https://v2.api.example.com/users
# cURL examples
curl https://api.example.com/v1/users
curl https://api.example.com/v2/users

Header Versioning πŸ“‹

# Version in custom header
GET /api/users HTTP/1.1
X-API-Version: v2
# Version in Accept header
GET /api/users HTTP/1.1
Accept: application/vnd.example.v2+json
# cURL examples
curl -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 start
GET /api/v1/users # βœ… Good - versioned from day 1
# βœ… DO: Use semantic versioning
GET /api/v1.0/users # Major.minor
GET /api/v1.1/users # Minor changes
# βœ… DO: Maintain backward compatibility
# v1 still works when v2 is released
GET /api/v1/users # βœ… Still supported
GET /api/v2/users # βœ… New version
# βœ… DO: Document version deprecation
HTTP/1.1 200 OK
X-API-Version: v1
X-API-Deprecated: true
X-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 integrations

Pagination

Offset-Based Pagination πŸ“„

# Basic pagination
GET /api/users?page=1&per_page=20
# Response
{
"data": [ /* users */ ],
"pagination": {
"page": 1,
"per_page": 20,
"total": 100,
"total_pages": 5
}
}
# cURL example
curl "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 page
GET /api/users?cursor=eyJpZCI6MTQzfQ&limit=20
# Pagination via Link header (RFC 5988)
HTTP/1.1 200 OK
Link: <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 πŸ’‘

Terminal window
# Offset pagination
curl "https://api.example.com/users?page=1&per_page=20"
# Cursor pagination
curl "https://api.example.com/users?cursor=abc123&limit=20"
# With filters
curl "https://api.example.com/users?status=active&page=1&per_page=20"

Filtering & Sorting

Filtering πŸ”

# Simple filters
GET /api/users?status=active
GET /api/users?role=admin&status=active
# Range filters
GET /api/users?age_min=18&age_max=65
GET /api/users?created_after=2024-01-01&created_before=2024-12-31
# Array filters
GET /api/users?tags=javascript&tags=react
GET /api/users?tags=javascript,react,nodejs
# Nested filters (depends on API design)
GET /api/users?filter[status]=active&filter[role]=admin
GET /api/users?filter[age][gte]=18&filter[age][lte]=65
# Search
GET /api/users?q=john&search_fields=name,email

Sorting πŸ“Š

# Single field sorting
GET /api/users?sort=name
GET /api/users?sort=name&order=asc
GET /api/users?sort=name&order=desc
# Descending with minus prefix
GET /api/users?sort=-created_at
# Multiple field sorting
GET /api/users?sort=status,name
GET /api/users?sort=status,-created_at
# cURL examples
curl "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 fields
GET /api/users?fields=id,name,email
# Exclude fields
GET /api/users?exclude=password,secret_token
# Response
{
"data": [
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
]
}
# Include related resources
GET /api/users/123?include=posts,comments
# Nested includes
GET /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 headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200
# Rate limit exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-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: 1000
X-RateLimit-Window: 3600 # 1 hour in seconds
# Sliding window
X-RateLimit-Limit: 1000
X-RateLimit-Window: 3600
# Token bucket
X-RateLimit-Limit: 1000
X-RateLimit-Burst: 100 # Allow bursts
# Per-endpoint limits
X-RateLimit-Limit: 1000 # General API
X-RateLimit-Limit-Search: 100 # Search endpoint

Rate Limit Examples πŸ’‘

Terminal window
# Check rate limit headers
curl -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 response
HTTP/1.1 429 Too Many Requests
Retry-After: 60
# Wait 60 seconds before retrying

Best Practices

API Design βœ…

# βœ… DO: Use HTTPS for all endpoints
https://api.example.com/users # βœ… Secure
# βœ… DO: Use consistent naming
GET /api/users # βœ… Plural nouns
GET /api/users/123 # βœ… Resource ID
GET /api/users/123/posts # βœ… Nested resources
# βœ… DO: Use proper HTTP methods
GET /api/users # βœ… Retrieve
POST /api/users # βœ… Create
PUT /api/users/123 # βœ… Replace
PATCH /api/users/123 # βœ… Update
DELETE /api/users/123 # βœ… Delete
# βœ… DO: Return appropriate status codes
201 Created # βœ… After POST
200 OK # βœ… After GET, PUT, PATCH
204 No Content # βœ… After DELETE
404 Not Found # βœ… Resource missing
# βœ… DO: Use consistent response format
{
"data": { /* resource */ },
"meta": { /* metadata */ }
}
# βœ… DO: Include request IDs for debugging
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
# βœ… DO: Version your API
GET /api/v1/users
GET /api/v2/users

Security πŸ”’

# βœ… DO: Use HTTPS only
https://api.example.com/users # βœ… Secure
http://api.example.com/users # ❌ Insecure
# βœ… DO: Validate and sanitize all input
POST /api/users
{
"email": "user@example.com" # βœ… Validate format
}
# βœ… DO: Use authentication tokens
Authorization: Bearer eyJhbGci... # βœ… Secure token
# βœ… DO: Implement rate limiting
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
# βœ… DO: Use CORS properly
Access-Control-Allow-Origin: https://example.com # βœ… Specific origin
Access-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 URLs
GET /api/users?token=secret123 # ❌ Visible in logs
GET /api/users
Authorization: Bearer secret123 # βœ… In header

Performance ⚑

# βœ… DO: Implement pagination
GET /api/users?page=1&per_page=20
# βœ… DO: Support field selection
GET /api/users?fields=id,name,email
# βœ… DO: Use caching headers
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# βœ… DO: Support conditional requests
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
# βœ… DO: Compress responses
Content-Encoding: gzip
# βœ… DO: Use connection pooling
# Handled by HTTP client libraries

Documentation πŸ“š

# βœ… DO: Provide OpenAPI/Swagger documentation
GET /api/docs
GET /api/swagger.json
# βœ… DO: Include examples in documentation
# Example request
POST /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-Remaining

Common Pitfalls

Design Mistakes ❌

# ❌ DON'T: Use verbs in URLs
GET /api/getUsers # ❌ Bad
GET /api/users # βœ… Good
# ❌ DON'T: Mix singular/plural
GET /api/user/123/posts # ❌ Inconsistent
GET /api/users/123/posts # βœ… Consistent
# ❌ DON'T: Use wrong HTTP methods
GET /api/users/123/delete # ❌ Bad
DELETE /api/users/123 # βœ… Good
# ❌ DON'T: Return wrong status codes
POST /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 idempotency
POST /api/users # ❌ Creates new user each time
PUT /api/users/123 # βœ… Same request = same result

Security Mistakes πŸ”’

# ❌ DON'T: Use HTTP instead of HTTPS
http://api.example.com/users # ❌ Insecure
# ❌ DON'T: Put tokens in query parameters
GET /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 origin
Access-Control-Allow-Origin: * # ❌ Too permissive
# ❌ DON'T: Skip input validation
POST /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 pagination
GET /api/users
β†’ Returns 10,000 users # ❌ Too much data
# ❌ DON'T: Return unnecessary fields
GET /api/users
β†’ Returns password, secret_token # ❌ Security + performance issue
# ❌ DON'T: Ignore caching
# No Cache-Control headers # ❌ Missed optimization
# ❌ DON'T: N+1 query problem
GET /api/users
β†’ Each user makes separate request for posts # ❌ Inefficient

Error 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 codes
GET /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