Audience
This guide is for developers who work with REST APIs and want to understand HTTP properly:
- Backend developers building APIs who want to use HTTP correctly
- Frontend developers consuming APIs who need to understand response semantics
- API designers making decisions about methods, status codes, and headers
- Anyone who has been confused by why an API uses PUT vs PATCH, or 400 vs 422
You should already understand what an API is. If not, start with How a REST API Works.
Goal
After reading this guide, you’ll understand:
- Why HTTP is the foundation of REST APIs (not just a transport)
- How to choose the right HTTP method for each operation
- What status codes mean and when to use them
- Which headers matter and why
- What breaks clients when HTTP is misused
This guide builds criteria—the ability to make correct HTTP decisions confidently.
1. HTTP Is Not Just Transport
Many developers treat HTTP as a “pipe” to send JSON back and forth. This is a mistake.
HTTP Has Semantics
HTTP is not just a way to move bytes. It’s a semantic protocol with built-in meaning:
- Methods tell the server what action to perform
- Status codes tell the client what happened
- Headers carry metadata about the request and response
- Caching, retries, and proxies all depend on these semantics
When you ignore HTTP semantics, you lose:
graph LR
A[Ignoring HTTP Semantics] --> B[No automatic caching]
A --> C[Unsafe retries]
A --> D[Broken proxy behavior]
A --> E[Confusing error handling]
A --> F[Poor tooling support]
style A fill:#ffccbcThe Cost of Getting It Wrong
If you use POST for everything:
- Browsers and CDNs won’t cache responses
- Retry logic becomes dangerous (might duplicate actions)
- API documentation tools can’t infer behavior
- Developers have to read docs for every endpoint
If you return 200 OK for errors:
- Monitoring systems can’t detect failures
- Load balancers can’t route around unhealthy servers
- Clients have to parse the body to know if it worked
HTTP semantics exist for a reason. Use them.
2. HTTP Methods: What They Really Mean
HTTP defines several methods. Each has a specific meaning and expected behavior.
The Core Methods
| Method | Purpose | Has Body? | Idempotent? | Safe? |
|---|---|---|---|---|
| GET | Retrieve a resource | No | Yes | Yes |
| POST | Create a resource or trigger an action | Yes | No | No |
| PUT | Replace a resource entirely | Yes | Yes | No |
| PATCH | Update part of a resource | Yes | No* | No |
| DELETE | Remove a resource | No | Yes | No |
| HEAD | Get headers only (no body) | No | Yes | Yes |
| OPTIONS | Get allowed methods | No | Yes | Yes |
*PATCH can be idempotent if designed carefully.
GET: Retrieve Without Side Effects
GET requests should never modify data. They’re “safe”—you can call them repeatedly without changing anything.
GET /users/123 HTTP/1.1
Host: api.example.com
What GET guarantees:
- Safe to cache
- Safe to retry
- Safe for browsers to prefetch
- Safe for crawlers to follow
What breaks when you violate this:
# WRONG: GET with side effects
GET /users/123/delete HTTP/1.1
If a crawler follows this link, it deletes the user. If a browser prefetches it, data disappears. Never use GET for destructive operations.
POST: Create or Trigger Actions
POST is the “do something” method. It’s not idempotent—calling it twice might create two resources.
POST /users HTTP/1.1
Content-Type: application/json
{
"name": "Alice",
"email": "[email protected]"
}
Use POST when:
- Creating a new resource
- Triggering an action that isn’t idempotent
- Submitting data that shouldn’t be in a URL
Why POST is dangerous to retry:
If a payment fails with a network timeout, did it actually process? Retrying POST /payments might charge the customer twice. This is why idempotency matters (covered in advanced courses).
PUT: Replace Entirely
PUT replaces the entire resource. Send the complete representation, not just changes.
PUT /users/123 HTTP/1.1
Content-Type: application/json
{
"name": "Alice Smith",
"email": "[email protected]",
"phone": "+1-555-1234"
}
PUT is idempotent: Calling it 10 times with the same data has the same effect as calling it once. The resource ends up in the same state.
Common mistake:
# WRONG: Sending only changed fields
PUT /users/123 HTTP/1.1
{
"name": "Alice Smith"
}
This would set email and phone to null (or whatever your API does with missing fields). For partial updates, use PATCH.
PATCH: Update Partially
PATCH updates specific fields without replacing the entire resource.
PATCH /users/123 HTTP/1.1
Content-Type: application/json
{
"name": "Alice Smith"
}
Only name changes. Other fields remain unchanged.
PUT vs PATCH decision:
flowchart TD
A[Need to update resource] --> B{Sending complete
representation?}
B -->|Yes| C[Use PUT]
B -->|No| D{Sending partial
changes?}
D -->|Yes| E[Use PATCH]
D -->|No| F[Reconsider your design]
style C fill:#e8f5e9
style E fill:#e3f2fdDELETE: Remove a Resource
DELETE removes the resource at the given URL.
DELETE /users/123 HTTP/1.1
DELETE is idempotent: Deleting a resource that doesn’t exist should return success (usually 204 or 404, depending on your design). The end state is the same: the resource is gone.
Common question: Should DELETE have a body?
Technically allowed, but many proxies and clients don’t support it. Avoid DELETE with a body.
Methods You’ll Rarely Use
- HEAD: Like GET but returns only headers. Used for checking if a resource exists or getting metadata without downloading the body.
- OPTIONS: Returns allowed methods for a resource. Used by CORS preflight requests.
3. Status Codes: Telling Clients What Happened
Status codes are not just numbers—they’re a communication protocol between server and client.
The Five Families
| Range | Category | Meaning |
|---|---|---|
| 1xx | Informational | Request received, continuing process |
| 2xx | Success | Request succeeded |
| 3xx | Redirection | Further action needed |
| 4xx | Client Error | Client made a mistake |
| 5xx | Server Error | Server failed to fulfill valid request |
Success Codes (2xx)
200 OK — The request succeeded.
Use for: GET that returns data, PUT/PATCH that returns updated resource, POST that returns created resource.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Alice"
}
201 Created — A new resource was created.
Use for: Successful POST that creates a resource. Include Location header with the new resource URL.
HTTP/1.1 201 Created
Location: /users/123
Content-Type: application/json
{
"id": 123,
"name": "Alice"
}
204 No Content — Success, but nothing to return.
Use for: DELETE that succeeded, PUT/PATCH when you don’t need to return the updated resource.
HTTP/1.1 204 No Content
202 Accepted — Request accepted for processing, but not yet completed.
Use for: Async operations where the result isn’t immediately available.
HTTP/1.1 202 Accepted
Location: /jobs/456
{
"jobId": 456,
"status": "processing"
}
Client Error Codes (4xx)
These say: “The client did something wrong.”
400 Bad Request — The request is malformed or invalid.
Use for: Invalid JSON, missing required fields, validation errors.
HTTP/1.1 400 Bad Request
{
"error": "Validation failed",
"details": [
{"field": "email", "message": "Invalid email format"}
]
}
401 Unauthorized — Authentication required or failed.
Use for: Missing token, invalid credentials, expired session.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
{
"error": "Authentication required"
}
403 Forbidden — Authenticated, but not authorized.
Use for: User is logged in but doesn’t have permission.
HTTP/1.1 403 Forbidden
{
"error": "You don't have permission to access this resource"
}
401 vs 403:
flowchart TD
A[Request with
protected resource] --> B{Token present?}
B -->|No| C[401 Unauthorized]
B -->|Yes| D{Token valid?}
D -->|No| C
D -->|Yes| E{Has permission?}
E -->|No| F[403 Forbidden]
E -->|Yes| G[200 OK]
style C fill:#ffccbc
style F fill:#ffccbc
style G fill:#c8e6c9404 Not Found — The resource doesn’t exist.
Use for: Requested resource not found at this URL.
HTTP/1.1 404 Not Found
{
"error": "User not found"
}
405 Method Not Allowed — The method is not supported for this resource.
Use for: POST to a read-only endpoint, DELETE when deletion isn’t allowed.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
{
"error": "DELETE not allowed on this resource"
}
409 Conflict — The request conflicts with current state.
Use for: Duplicate creation, concurrent modification, state conflicts.
HTTP/1.1 409 Conflict
{
"error": "User with this email already exists"
}
422 Unprocessable Entity — Request is well-formed but semantically invalid.
Use for: Valid JSON but business logic rejection.
HTTP/1.1 422 Unprocessable Entity
{
"error": "Cannot transfer negative amount"
}
429 Too Many Requests — Rate limit exceeded.
Use for: Client is sending too many requests.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"error": "Rate limit exceeded",
"retryAfter": 60
}
Server Error Codes (5xx)
These say: “The server failed.”
500 Internal Server Error — Something went wrong on the server.
Use for: Unhandled exceptions, bugs, unexpected failures.
HTTP/1.1 500 Internal Server Error
{
"error": "An unexpected error occurred",
"requestId": "abc123"
}
502 Bad Gateway — Upstream server returned invalid response.
Use for: API gateway received bad response from backend.
503 Service Unavailable — Server temporarily unavailable.
Use for: Overload, maintenance, temporary outage.
HTTP/1.1 503 Service Unavailable
Retry-After: 300
{
"error": "Service temporarily unavailable"
}
504 Gateway Timeout — Upstream server didn’t respond in time.
Use for: Backend timeout.
Why 4xx vs 5xx Matters
The distinction is critical for retries:
- 4xx errors: Don’t retry automatically—the request is broken
- 5xx errors: May retry—the server might recover
flowchart TD
A[Request Failed] --> B{Status Code?}
B -->|4xx| C[Client Error]
B -->|5xx| D[Server Error]
C --> E[Fix the request
Don't retry]
D --> F[May retry
with backoff]
style C fill:#fff9c4
style D fill:#ffccbc4. Headers That Matter
Headers carry metadata. Some are essential for REST APIs.
Request Headers
Content-Type — Format of the request body.
Content-Type: application/json
Without this, the server might not know how to parse your body.
Accept — Format you want in the response.
Accept: application/json
Tells the server you want JSON (not HTML, XML, etc.).
Authorization — Authentication credentials.
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Carries the access token for authenticated requests.
User-Agent — Client identification.
User-Agent: MyApp/1.0.0 (iOS 16.0)
Helps with debugging and analytics.
Response Headers
Content-Type — Format of the response body.
Content-Type: application/json; charset=utf-8
Tells the client how to parse the response.
Location — URL of a created or redirected resource.
Location: /users/123
Used with 201 Created and 3xx redirects.
Cache-Control — Caching instructions.
Cache-Control: max-age=3600, public
Tells clients and proxies how to cache the response.
Retry-After — When to retry after rate limiting or errors.
Retry-After: 60
Used with 429 and 503. Value in seconds.
Headers for API Metadata
X-Request-Id — Unique identifier for the request.
X-Request-Id: abc123-def456
Essential for debugging and log correlation.
X-RateLimit-* — Rate limit information.
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1609459200
Tells clients their quota status.
5. What Breaks Clients
Understanding what breaks clients helps you avoid common mistakes.
Breaking Pattern 1: Using GET for Side Effects
# Server code
GET /[email protected]
What breaks:
- Browser prefetching sends emails
- Crawlers trigger actions
- Caching returns old response but action already happened
Fix: Use POST for actions with side effects.
Breaking Pattern 2: Returning 200 for Errors
HTTP/1.1 200 OK
{
"success": false,
"error": "User not found"
}
What breaks:
- Monitoring systems think everything is fine
- HTTP error handlers don’t trigger
- Retry logic doesn’t work
Fix: Use appropriate 4xx/5xx status codes.
Breaking Pattern 3: Inconsistent Content-Type
# Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{"user": {...}}
# Error response (suddenly HTML!)
HTTP/1.1 500 Internal Server Error
Content-Type: text/html
<html><body>Error occurred</body></html>
What breaks:
- JSON parsing fails on errors
- Error handling becomes complex
- Clients can’t reliably process responses
Fix: Always return the same format (JSON for APIs).
Breaking Pattern 4: Missing Content-Type
HTTP/1.1 200 OK
{"user": {...}}
No Content-Type header!
What breaks:
- Clients may guess the wrong format
- Some HTTP clients require explicit Content-Type
- Browsers might try to “sniff” the content type incorrectly
Fix: Always include Content-Type header.
Breaking Pattern 5: Wrong Status Codes
# Wrong: 200 for validation error
POST /users HTTP/1.1
{"email": "invalid"}
---
HTTP/1.1 200 OK
{"error": "Invalid email"}
What breaks:
- Clients think the request succeeded
- Error handling logic isn’t triggered
- Monitoring shows false positives
Fix: Use 400 for validation errors, 4xx/5xx for failures.
Breaking Pattern 6: Changing Behavior Without Notice
# Before: PUT replaces resource
PUT /users/123
{"name": "Alice"}
# Other fields remain
# After: PUT now clears missing fields
PUT /users/123
{"name": "Alice"}
# email and phone are now null!
What breaks:
- Existing clients lose data
- Integrations silently corrupt data
Fix: Document behavior clearly. Version APIs for breaking changes.
6. Quick Reference
Method Selection
| You want to… | Use |
|---|---|
| Get a resource | GET |
| Create a resource | POST |
| Replace a resource entirely | PUT |
| Update part of a resource | PATCH |
| Delete a resource | DELETE |
| Check if resource exists | HEAD |
| Get allowed methods | OPTIONS |
Status Code Selection
| Situation | Use |
|---|---|
| Success with data | 200 OK |
| Created new resource | 201 Created |
| Success, no data to return | 204 No Content |
| Async processing started | 202 Accepted |
| Invalid request format | 400 Bad Request |
| Not authenticated | 401 Unauthorized |
| Not authorized | 403 Forbidden |
| Resource doesn’t exist | 404 Not Found |
| Method not supported | 405 Method Not Allowed |
| Conflict with current state | 409 Conflict |
| Valid format, invalid data | 422 Unprocessable Entity |
| Too many requests | 429 Too Many Requests |
| Server bug | 500 Internal Server Error |
| Upstream failure | 502 Bad Gateway |
| Temporarily unavailable | 503 Service Unavailable |
Essential Headers
| Header | Direction | Purpose |
|---|---|---|
| Content-Type | Both | Body format |
| Accept | Request | Desired response format |
| Authorization | Request | Authentication |
| Location | Response | New resource URL |
| Cache-Control | Response | Caching rules |
| Retry-After | Response | When to retry |
What’s Next
This guide covered HTTP semantics at the criteria level—how to think about HTTP correctly.
For deeper topics like:
- Edge cases in status code selection
- Idempotency keys and retry strategies
- Timeout handling and circuit breakers
- Advanced caching strategies
See the upcoming course: HTTP for REST APIs in Production.
Related Vocabulary Terms
Deepen your understanding:
- HTTP - The protocol powering REST APIs
- HTTP Methods - GET, POST, PUT, PATCH, DELETE
- HTTP Status Codes - The language of success and errors
- Idempotency - Why some methods are safe to retry
- Safe Methods - Methods that don’t modify resources
- Content Negotiation - Format selection via headers