Introduction to HTTP as an Application Protocol
HTTP (Hypertext Transfer Protocol) forms the foundation of data communication on the web. Originally designed for document transfer, it has evolved into a sophisticated application protocol that powers modern APIs.
In RESTful APIs, HTTP is more than just a transport mechanism—it's a rich protocol whose features are leveraged to create intuitive, standardized interfaces. Understanding HTTP methods and status codes thoroughly is essential for both designing and consuming APIs effectively.
Think of HTTP as a standardized language for communication between clients and servers, with:
- Methods (also called verbs) defining the requested action
- Headers providing metadata about the request and response
- Status codes communicating the outcome of the request
- Message bodies containing the actual data being transferred
This lecture will focus on HTTP methods and status codes, examining their semantic meaning, appropriate usage, and best practices in the context of RESTful APIs.
HTTP Methods in Depth
HTTP methods indicate the desired action to be performed on a resource. In RESTful APIs, they map to CRUD (Create, Read, Update, Delete) operations, but their semantics go deeper than this simple mapping.
GET: Retrieving Resources
GET is used to retrieve data from the server without modifying any resources.
Key Characteristics:
- Safe: Does not alter the state of the server (read-only)
- Idempotent: Making the same request multiple times produces the same result
- Cacheable: Responses can be cached by default
- Data in URL: Request parameters are included in the URL (query string)
- No request body: GET requests should not include a request body
# Simple resource retrieval
GET /products/123 HTTP/1.1
Host: api.example.com
Accept: application/json
# Collection retrieval with filtering
GET /products?category=electronics&price_max=500 HTTP/1.1
Host: api.example.com
Accept: application/json
Real-world analogies: Reading a book from a library, looking up a phone number in a directory, or checking a price tag—all actions that retrieve information without changing it.
Common anti-patterns to avoid:
- Using GET for operations that modify state
- Creating overly complex GET URLs with too many parameters
- Including sensitive data in GET URLs (they can be logged)
- Returning different data for the same GET request (breaking idempotence)
POST: Creating Resources
POST is primarily used to create new resources on the server.
Key Characteristics:
- Not safe: Modifies state on the server
- Not idempotent: Multiple identical requests may create multiple resources
- Request body: Contains the data for the new resource
- Server-assigned identifier: The server typically assigns the new resource's ID
- Response: Usually returns the created resource with a 201 status code
# Creating a new resource
POST /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{
"name": "Ergonomic Keyboard",
"price": 129.99,
"category": "electronics",
"description": "Comfortable typing experience with adjustable height."
}
# Response
HTTP/1.1 201 Created
Location: /products/456
Content-Type: application/json
{
"id": 456,
"name": "Ergonomic Keyboard",
"price": 129.99,
"category": "electronics",
"description": "Comfortable typing experience with adjustable height.",
"created_at": "2025-03-15T14:30:00Z"
}
Real-world analogies: Submitting a form to apply for a service, sending a letter, or placing an order at a restaurant—actions that create new entities or requests.
Secondary uses of POST:
- Triggering operations that don't fit other HTTP methods (when not purely RESTful)
- Submitting large amounts of data (when GET's URL length would be limiting)
- File uploads
- Complex queries with large request bodies
Common anti-patterns to avoid:
- Using POST for idempotent operations (use PUT or PATCH instead)
- Not returning the created resource in the response
- Not providing a Location header with the URI of the new resource
- Using POST for simple retrievals to avoid URL length limitations (use query parameters more efficiently)
PUT: Replacing Resources
PUT is used to update a resource by replacing it entirely with a new representation.
Key Characteristics:
- Not safe: Modifies state on the server
- Idempotent: Multiple identical requests have the same effect as a single request
- Complete replacement: The entire resource is replaced, not just changed fields
- Client-assigned identifier: The client specifies which resource to replace
- Creating or updating: Can create a resource if it doesn't exist (but not required behavior)
# Updating a resource completely
PUT /products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{
"name": "Ergonomic Mechanical Keyboard",
"price": 149.99,
"category": "electronics",
"description": "Mechanical switches with adjustable height for maximum comfort."
}
# Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 456,
"name": "Ergonomic Mechanical Keyboard",
"price": 149.99,
"category": "electronics",
"description": "Mechanical switches with adjustable height for maximum comfort.",
"created_at": "2025-03-15T14:30:00Z",
"updated_at": "2025-03-15T16:45:00Z"
}
Real-world analogies: Replacing a file in a filing cabinet with a new version, or swapping out a part in a machine with a completely new part.
Common anti-patterns to avoid:
- Using PUT for partial updates (use PATCH instead)
- Not requiring all resource fields in the request
- Modifying fields not specified in the request (breaking the replacement semantics)
- Creating side effects beyond the targeted resource
PATCH: Partial Updates
PATCH is used to make partial updates to a resource.
Key Characteristics:
- Not safe: Modifies state on the server
- Not necessarily idempotent: Depends on the patch operations
- Partial modification: Only specified fields are updated
- Various formats: Can use different patch document formats (JSON Patch, JSON Merge Patch, etc.)
# Simple partial update
PATCH /products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{
"price": 139.99,
"description": "Mechanical switches with ergonomic design and RGB lighting."
}
# JSON Patch format (RFC 6902)
PATCH /products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json-patch+json
Accept: application/json
[
{ "op": "replace", "path": "/price", "value": 139.99 },
{ "op": "replace", "path": "/description", "value": "Mechanical switches with ergonomic design and RGB lighting." }
]
Real-world analogies: Editing specific fields on a form, making corrections to a document, or adjusting specific settings on a device.
Patch document formats:
- JSON Merge Patch (RFC 7396): Simple approach where you send only the fields to change
- JSON Patch (RFC 6902): More sophisticated approach with operations like add, remove, replace, copy, move, test
- Custom formats: API-specific formats for specialized update patterns
Common anti-patterns to avoid:
- Using PATCH when the entire resource is being replaced (use PUT instead)
- Not specifying the patch format clearly (with Content-Type)
- Creating an inconsistent resource state through partial updates
- Not properly validating that the resulting resource is valid after patching
DELETE: Removing Resources
DELETE is used to remove a resource from the server.
Key Characteristics:
- Not safe: Modifies state on the server
- Idempotent: Multiple identical requests have the same effect as a single request
- Response options: May return the deleted resource or just a success status
- May be asynchronous: Some systems queue deletions rather than executing them immediately
# Deleting a single resource
DELETE /products/456 HTTP/1.1
Host: api.example.com
# Response (no content)
HTTP/1.1 204 No Content
# Alternative response (with content)
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 456,
"name": "Ergonomic Mechanical Keyboard",
"deleted_at": "2025-03-16T09:20:00Z",
"status": "deleted"
}
Real-world analogies: Removing a file from a filing cabinet, canceling a subscription, or deleting a contact from an address book.
Implementation considerations:
- Hard vs. soft deletes: Some systems mark resources as deleted rather than removing them permanently
- Cascade deletes: Consider whether to delete related resources automatically
- Authorization: Deletion often requires higher privileges than other operations
- Bulk deletes: Consider how to handle deletion of multiple resources
# Bulk deletion with query parameters
DELETE /products?category=discontinued HTTP/1.1
Host: api.example.com
# Bulk deletion with request body
DELETE /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"ids": [123, 456, 789]
}
Common anti-patterns to avoid:
- Using DELETE for logical soft deletes (consider using PUT/PATCH to update status instead)
- Not properly securing DELETE endpoints (common vulnerability)
- Not considering the impact on related resources
- Returning 404 on subsequent DELETE requests to the same resource (breaks idempotence)
Other HTTP Methods
While the five methods above are most common in RESTful APIs, several other HTTP methods exist and may be useful in specific circumstances.
| Method | Purpose | RESTful API Use Cases |
|---|---|---|
| HEAD | Like GET but returns only headers, no body |
|
| OPTIONS | Returns the HTTP methods supported by the resource |
|
| CONNECT | Establishes a tunnel to the server |
|
| TRACE | Performs a message loop-back test |
|
# Example OPTIONS request
OPTIONS /products/456 HTTP/1.1
Host: api.example.com
# Response
HTTP/1.1 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Access-Control-Allow-Methods: GET, PUT, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Method Properties Comparison
| Method | Safe | Idempotent | Cacheable | Request Body | Response Body |
|---|---|---|---|---|---|
| GET | Yes | Yes | Yes | No | Yes |
| POST | No | No | Rarely | Yes | Yes |
| PUT | No | Yes | No | Yes | Yes |
| PATCH | No | No* | No | Yes | Yes |
| DELETE | No | Yes | No | Maybe | Maybe |
| HEAD | Yes | Yes | Yes | No | No |
| OPTIONS | Yes | Yes | No | No | Yes |
* PATCH can be made idempotent through careful design of patch operations.
HTTP Status Codes in Depth
HTTP status codes are three-digit numbers returned by a server in response to a client's request. They indicate the outcome of the request and provide valuable context to the client.
Status codes are grouped into five classes, indicated by the first digit:
- 1xx (Informational): Request received, continuing process
- 2xx (Success): Request successfully received, understood, and accepted
- 3xx (Redirection): Further action needed to complete the request
- 4xx (Client Error): Request contains bad syntax or cannot be fulfilled
- 5xx (Server Error): Server failed to fulfill a valid request
Informational Status Codes (1xx)
These codes indicate a provisional response. They are relatively rare in RESTful APIs.
| Code | Name | Description | API Use Cases |
|---|---|---|---|
| 100 | Continue | Server has received the request headers and the client should proceed with the request body | Large uploads, when the client wants to know if the server will accept the request before sending the entire body |
| 101 | Switching Protocols | Server is switching protocols as requested by the client | Upgrading from HTTP to WebSocket |
| 102 | Processing | Server has received and is processing the request, but no response is available yet | Long-running operations where the response cannot be generated quickly |
| 103 | Early Hints | Used to return some response headers before final HTTP message | Rarely used in APIs, more relevant for HTML document loading optimization |
Success Status Codes (2xx)
These codes indicate that the client's request was successfully received, understood, and processed.
| Code | Name | Description | API Use Cases |
|---|---|---|---|
| 200 | OK | Request succeeded, the response body contains the result | GET requests, successful PUT/PATCH with response, POST operations that don't create a resource |
| 201 | Created | Request succeeded and a new resource was created | Successful POST requests that create new resources |
| 202 | Accepted | Request has been accepted for processing, but the processing has not been completed | Asynchronous operations, batch processing, long-running tasks |
| 204 | No Content | Request succeeded, but there's no content to return in the response body | Successful DELETE operations, operations that don't need to return data |
| 205 | Reset Content | Request processed, the client should reset the document view | Rarely used in APIs, more relevant for HTML form submissions |
| 206 | Partial Content | Server is delivering only part of the resource due to a range header sent by the client | Paginated responses, large file downloads, streaming media |
# Example: 201 Created
POST /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Wireless Mouse",
"price": 49.99
}
HTTP/1.1 201 Created
Location: /products/789
Content-Type: application/json
{
"id": 789,
"name": "Wireless Mouse",
"price": 49.99,
"created_at": "2025-03-16T10:30:00Z"
}
# Example: 202 Accepted
POST /imports HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"file_url": "https://example.com/data.csv",
"options": { "replace_existing": true }
}
HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /imports/job-12345
{
"job_id": "job-12345",
"status": "processing",
"status_url": "/imports/job-12345"
}
# Example: 204 No Content
DELETE /products/789 HTTP/1.1
Host: api.example.com
HTTP/1.1 204 No Content
Redirection Status Codes (3xx)
These codes indicate that the client must take additional action to complete the request. In RESTful APIs, they're less common but still useful in specific scenarios.
| Code | Name | Description | API Use Cases |
|---|---|---|---|
| 301 | Moved Permanently | The resource has been moved permanently to a new URL | API versioning, resource restructuring, domain changes |
| 302 | Found | The resource is temporarily located at a different URL | Temporary redirects, load balancing, rarely used in APIs |
| 303 | See Other | The response can be found at a different URL and should be retrieved using GET | Redirecting after a POST operation to avoid duplicate submissions |
| 304 | Not Modified | The resource hasn't been modified since the version specified by request headers | Conditional GET requests with If-Modified-Since or If-None-Match headers |
| 307 | Temporary Redirect | The request should be repeated with the same method at the given URL | Temporary service migration, preserving the original HTTP method |
| 308 | Permanent Redirect | The resource has been moved permanently, preserve the HTTP method | Permanent API restructuring where the method must be preserved |
# Example: 301 Moved Permanently
GET /api/v1/products/123 HTTP/1.1
Host: api.example.com
HTTP/1.1 301 Moved Permanently
Location: https://api.example.com/api/v2/products/123
# Example: 304 Not Modified
GET /products/123 HTTP/1.1
Host: api.example.com
If-Modified-Since: Sun, 15 Mar 2025 14:30:00 GMT
If-None-Match: "a1b2c3d4e5f6"
HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4e5f6"
Last-Modified: Sun, 15 Mar 2025 14:30:00 GMT
Cache-Control: max-age=3600
Client Error Status Codes (4xx)
These codes indicate that the client seems to have made an error in the request. These are extremely important for providing clear feedback to API consumers.
| Code | Name | Description | API Use Cases |
|---|---|---|---|
| 400 | Bad Request | The server cannot process the request due to a client error | Malformed request syntax, invalid request message framing, or deceptive request routing |
| 401 | Unauthorized | Authentication is required and has failed or has not been provided | Missing or invalid authentication token, expired credentials |
| 403 | Forbidden | Server understood the request but refuses to authorize it | Authenticated user lacks permissions for the requested operation |
| 404 | Not Found | The requested resource could not be found | Resource doesn't exist, invalid IDs, non-existent endpoints |
| 405 | Method Not Allowed | The HTTP method is not allowed for the requested resource | Using DELETE on a read-only resource, using POST where only GET is permitted |
| 406 | Not Acceptable | The server cannot produce a response matching the list of acceptable values in the Accept header | Client requests a format (e.g., XML) that the API doesn't support |
| 409 | Conflict | Request conflicts with the current state of the resource | Concurrent updates, version conflicts, duplicate entries |
| 410 | Gone | The resource requested is no longer available and will not be available again | Accessing retired API versions, permanently removed resources |
| 412 | Precondition Failed | Preconditions in the request header fields are not met | Conditional requests with If-Match or If-Unmodified-Since headers |
| 413 | Payload Too Large | Request entity is larger than limits defined by server | File uploads exceeding size limits, request bodies that are too large |
| 415 | Unsupported Media Type | The media format of the requested data is not supported | Sending XML when only JSON is supported, unsupported Content-Type |
| 422 | Unprocessable Entity | The request was well-formed but was unable to be followed due to semantic errors | Validation failures, logical errors in request data |
| 429 | Too Many Requests | The user has sent too many requests in a given amount of time | Rate limiting, throttling to prevent abuse |
# Example: 400 Bad Request
POST /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
{ "name": "Product" } # Missing required fields
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"code": "bad_request",
"message": "The request could not be processed",
"details": "Required field 'price' is missing"
}
}
# Example: 401 Unauthorized
GET /users HTTP/1.1
Host: api.example.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
{
"error": {
"code": "unauthorized",
"message": "Authentication is required to access this resource"
}
}
# Example: 403 Forbidden
DELETE /users/789 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": {
"code": "forbidden",
"message": "You don't have permission to delete this user"
}
}
# Example: 422 Unprocessable Entity
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "John Doe",
"email": "not-an-email",
"age": 17
}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "validation_failed",
"message": "The request contains invalid data",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
},
{
"field": "age",
"message": "Must be at least 18"
}
]
}
}
Server Error Status Codes (5xx)
These codes indicate that the server failed to fulfill a valid request. While these should be rare, proper error handling is essential for a robust API.
| Code | Name | Description | API Use Cases |
|---|---|---|---|
| 500 | Internal Server Error | A generic error message returned when an unexpected condition was encountered | Uncaught exceptions, database errors, unexpected conditions |
| 501 | Not Implemented | The server does not support the functionality required to fulfill the request | Methods or features not yet implemented in the API |
| 502 | Bad Gateway | The server received an invalid response from an upstream server | API gateway errors, microservice communication failures |
| 503 | Service Unavailable | The server is currently unavailable (because it is overloaded or down for maintenance) | Temporary outages, maintenance windows, overload situations |
| 504 | Gateway Timeout | The server was acting as a gateway or proxy and did not receive a timely response from the upstream server | Timeouts in microservice communication, slow database responses |
# Example: 500 Internal Server Error
POST /orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"product_id": 123,
"quantity": 5
}
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred",
"request_id": "req_abcd1234"
}
}
# Example: 503 Service Unavailable
GET /products HTTP/1.1
Host: api.example.com
HTTP/1.1 503 Service Unavailable
Retry-After: 60
Content-Type: application/json
{
"error": {
"code": "service_unavailable",
"message": "The service is temporarily unavailable due to maintenance",
"estimated_availability": "2025-03-16T12:30:00Z"
}
}
Headers and Content Negotiation
HTTP headers allow clients and servers to exchange additional information about the request or response. They play a crucial role in content negotiation, caching, authentication, and more.
Common Request Headers
| Header | Purpose | Example |
|---|---|---|
| Accept | Indicates which content types the client can process | Accept: application/json |
| Content-Type | Indicates the media type of the request body | Content-Type: application/json |
| Authorization | Contains authentication credentials | Authorization: Bearer eyJhbGciOiJIUzI1NiI... |
| User-Agent | Identifies the client making the request | User-Agent: MyAPIClient/1.0 |
| Accept-Language | Indicates the preferred languages for the response | Accept-Language: en-US, en;q=0.8, fr;q=0.5 |
| If-Modified-Since | Makes the request conditional based on modification date | If-Modified-Since: Sat, 15 Mar 2025 14:00:00 GMT |
| If-None-Match | Makes the request conditional based on ETags | If-None-Match: "a1b2c3d4e5" |
Common Response Headers
| Header | Purpose | Example |
|---|---|---|
| Content-Type | Indicates the media type of the response body | Content-Type: application/json; charset=utf-8 |
| Cache-Control | Directives for caching mechanisms | Cache-Control: max-age=3600, public |
| ETag | Identifier for a specific version of a resource | ETag: "a1b2c3d4e5" |
| Location | URL of a newly created resource or redirect target | Location: /resources/123 |
| WWW-Authenticate | Indicates how to authenticate to access a resource | WWW-Authenticate: Bearer realm="api" |
| X-RateLimit-Limit | Rate limiting information (max requests) | X-RateLimit-Limit: 100 |
| X-RateLimit-Remaining | Rate limiting information (remaining requests) | X-RateLimit-Remaining: 87 |
Content Negotiation
Content negotiation allows clients and servers to agree on the format, language, or encoding of resources. This enables the same resource to be represented in different ways based on client preferences.
Content Type Negotiation
# Client requests JSON
GET /products/123 HTTP/1.1
Host: api.example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 123, "name": "Product", "price": 99.99 }
# Client requests XML
GET /products/123 HTTP/1.1
Host: api.example.com
Accept: application/xml
HTTP/1.1 200 OK
Content-Type: application/xml
<product>
<id>123</id>
<name>Product</name>
<price>99.99</price>
</product>
Language Negotiation
# Client requests English content
GET /products/123 HTTP/1.1
Host: api.example.com
Accept-Language: en-US, en;q=0.8
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: en-US
{
"id": 123,
"name": "Wireless Mouse",
"description": "Comfortable wireless mouse with long battery life."
}
# Client requests French content
GET /products/123 HTTP/1.1
Host: api.example.com
Accept-Language: fr-FR, fr;q=0.8
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: fr-FR
{
"id": 123,
"name": "Souris sans fil",
"description": "Souris sans fil confortable avec une longue durée de vie de la batterie."
}
Encoding Negotiation
# Client indicates it can accept compressed content
GET /products HTTP/1.1
Host: api.example.com
Accept-Encoding: gzip, deflate
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip
[compressed JSON data]
Proper content negotiation enhances API flexibility and client compatibility, allowing different clients to interact with the API in their preferred format.
API Implementation Examples
Let's explore how HTTP methods and status codes work together in a RESTful API through concrete examples.
Example 1: CRUD Operations on a Product Resource
# Create a product
POST /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"name": "Ergonomic Keyboard",
"description": "Comfortable typing experience with adjustable height",
"price": 129.99,
"category_id": 5
}
HTTP/1.1 201 Created
Location: /products/456
Content-Type: application/json
{
"id": 456,
"name": "Ergonomic Keyboard",
"description": "Comfortable typing experience with adjustable height",
"price": 129.99,
"category_id": 5,
"created_at": "2025-03-16T14:30:00Z"
}
# Read a product
GET /products/456 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "abc123"
Last-Modified: Sun, 16 Mar 2025 14:30:00 GMT
{
"id": 456,
"name": "Ergonomic Keyboard",
"description": "Comfortable typing experience with adjustable height",
"price": 129.99,
"category_id": 5,
"created_at": "2025-03-16T14:30:00Z"
}
# Update a product (full replacement)
PUT /products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
If-Match: "abc123"
{
"name": "Ergonomic Mechanical Keyboard",
"description": "Mechanical switches with adjustable height for maximum comfort",
"price": 149.99,
"category_id": 5
}
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "def456"
{
"id": 456,
"name": "Ergonomic Mechanical Keyboard",
"description": "Mechanical switches with adjustable height for maximum comfort",
"price": 149.99,
"category_id": 5,
"created_at": "2025-03-16T14:30:00Z",
"updated_at": "2025-03-16T15:45:00Z"
}
# Partial update for just the price
PATCH /products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"price": 139.99
}
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "ghi789"
{
"id": 456,
"name": "Ergonomic Mechanical Keyboard",
"description": "Mechanical switches with adjustable height for maximum comfort",
"price": 139.99,
"category_id": 5,
"created_at": "2025-03-16T14:30:00Z",
"updated_at": "2025-03-16T16:20:00Z"
}
# Delete a product
DELETE /products/456 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
HTTP/1.1 204 No Content
Example 2: Error Scenarios
# Attempt to create a product with invalid data
POST /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"name": "X",
"price": -10
}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "validation_failed",
"message": "The provided data failed validation",
"details": [
{
"field": "name",
"code": "too_short",
"message": "Name must be at least 3 characters"
},
{
"field": "price",
"code": "invalid_value",
"message": "Price must be greater than 0"
},
{
"field": "category_id",
"code": "required",
"message": "Category ID is required"
}
]
}
}
# Attempt to access a non-existent resource
GET /products/999 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"code": "not_found",
"message": "The requested product could not be found"
}
}
# Attempt to update with invalid authentication
PUT /products/456 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer invalid_token
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer error="invalid_token"
{
"error": {
"code": "invalid_token",
"message": "The access token provided is expired, revoked, or invalid"
}
}
# Attempt to delete without necessary permissions
DELETE /products/456 HTTP/1.1
Host: api.example.com
Authorization: Bearer token_without_delete_permission
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": {
"code": "insufficient_permissions",
"message": "You do not have permission to delete this product"
}
}
# Attempt to use an unsupported HTTP method
PATCH /products/bulk HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Allow: GET, POST
{
"error": {
"code": "method_not_allowed",
"message": "The PATCH method is not supported for this resource",
"allowed_methods": ["GET", "POST"]
}
}
Example 3: Complex Operations
# Async operation: Import products
POST /imports HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"source_url": "https://example.com/products.csv",
"options": {
"update_existing": true,
"notify_on_completion": true
}
}
HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /imports/job-789
{
"job_id": "job-789",
"status": "queued",
"status_url": "/imports/job-789",
"estimated_completion_time": "2025-03-16T17:30:00Z"
}
# Check job status
GET /imports/job-789 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
HTTP/1.1 200 OK
Content-Type: application/json
{
"job_id": "job-789",
"status": "processing",
"progress": 45,
"processed_items": 450,
"total_items": 1000,
"started_at": "2025-03-16T17:00:00Z",
"estimated_completion_time": "2025-03-16T17:25:00Z"
}
# Conditional request with caching
GET /products/456 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
If-None-Match: "ghi789"
HTTP/1.1 304 Not Modified
ETag: "ghi789"
Cache-Control: max-age=3600
# Bulk operation with partial failure
POST /products/bulk HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
{
"products": [
{
"name": "Product A",
"price": 19.99,
"category_id": 5
},
{
"name": "Product B",
"price": -5.00,
"category_id": 5
},
{
"name": "Product C",
"price": 29.99,
"category_id": 5
}
]
}
HTTP/1.1 207 Multi-Status
Content-Type: application/json
{
"status": "partial_success",
"results": [
{
"index": 0,
"status": 201,
"id": 501,
"message": "Created successfully"
},
{
"index": 1,
"status": 422,
"message": "Validation failed",
"errors": [
{
"field": "price",
"message": "Price must be greater than 0"
}
]
},
{
"index": 2,
"status": 201,
"id": 502,
"message": "Created successfully"
}
],
"summary": {
"total": 3,
"successful": 2,
"failed": 1
}
}
Common Implementation Pitfalls
When implementing HTTP methods and status codes in RESTful APIs, developers often encounter these common pitfalls:
Method Selection Issues
- Using POST for everything: Not leveraging the semantics of other HTTP methods
- Using GET with request bodies: GET requests shouldn't include a request body
- Using PUT for partial updates: Not understanding the replacement semantics of PUT
- Creating non-idempotent PUT implementations: Breaking client expectations
- Implementing DELETE to return 404 on subsequent requests: Breaking idempotence
Status Code Misuse
- Using 200 OK for everything: Putting error details in the response body while returning a success code
- Confusing 401 vs. 403: Not distinguishing between authentication and authorization errors
- Returning 404 instead of 403: Hiding restricted resources instead of acknowledging but denying access
- Using incorrect success codes: Not using 201 for creation, 204 for empty responses
- Returning 500 for client errors: Not properly distinguishing client vs. server errors
HTTP Header Mistakes
- Not including Content-Type: Leaving clients to guess the response format
- Missing Location headers: Not providing the URI of newly created resources
- Incorrect Cache-Control: Causing unintended caching or preventing valid caching
- Not handling Accept headers: Ignoring client's requested format preferences
Content Handling Issues
- Not validating Content-Type: Processing request bodies without ensuring the right format
- Inconsistent response structures: Using different formats for success vs. error responses
- Not properly parsing query parameters: Mishandling arrays, special characters, etc.
- Not handling trailing slashes consistently: Treating /resources and /resources/ differently
Security Considerations
- Exposing sensitive information in errors: Revealing implementation details in error messages
- Not validating input thoroughly: Accepting and processing malformed or malicious requests
- Using GET for sensitive operations: Making bookmarkable/cacheable URLs that modify state
- Not securing all HTTP methods: Forgetting to check permissions on less common methods
Best Practices Summary
Based on our exploration of HTTP methods and status codes, here are key best practices to follow:
HTTP Method Usage
- Use GET for retrieving resources, never for modifying state
- Use POST for creating resources and non-idempotent operations
- Use PUT for complete replacement of a resource
- Use PATCH for partial updates
- Use DELETE for removing resources
- Make PUT, GET, HEAD, and DELETE implementations idempotent
- Use OPTIONS for CORS preflight and discovering available methods
- Return appropriate headers with each method (e.g., Location with 201 responses)
Status Code Selection
- Focus on a core set of widely understood status codes
- Return 2xx codes only for successful operations
- Use specific success codes (201 for creation, 204 for no content)
- Distinguish between different client error types (400, 401, 403, 404, 422)
- Use 400 for malformed syntax, 422 for validation errors
- Use 401 for authentication issues, 403 for authorization issues
- Reserve 5xx codes for genuine server errors, not client mistakes
- Include detailed error information in the response body
Content Handling
- Always specify Content-Type in requests and responses
- Respect Accept headers for content negotiation
- Keep response formats consistent across endpoints
- Use standard formats for error responses
- Include pagination metadata when returning collections
- Use correct character encodings (usually UTF-8)
Security and Performance
- Implement proper caching headers (ETag, Cache-Control)
- Don't expose sensitive information in error messages
- Validate all input thoroughly
- Implement rate limiting with appropriate headers
- Use HTTPS for all API endpoints
- Apply consistent authentication and authorization checks
Documentation
- Document all supported methods for each endpoint
- List all possible status codes that can be returned
- Provide examples of request and response bodies
- Document required and optional headers
- Explain error codes and their meanings
- Include information about idempotence and safety
Practice Activities
Activity 1: Method and Status Code Matching
For each of the following scenarios, identify the most appropriate HTTP method and status code(s) to use:
- A client wants to retrieve a list of all products.
- A client wants to create a new user account.
- A client wants to update a user's email address only.
- A client wants to completely replace a product's information.
- A client tries to access a resource that doesn't exist.
- A client submits malformed JSON in a request.
- A client tries to access a protected resource without authentication.
- A client is authenticated but lacks permissions for an operation.
- A client submits valid data but it violates business rules (e.g., insufficient inventory).
- A client makes too many requests in a short time period.
- The server encounters an unexpected database error.
- A client tries to use a method not supported by the endpoint.
- A long-running operation is started but not yet complete.
- A client requests a resource that has been permanently removed.
- A client wants to check if their cached version of a resource is still current.
Activity 2: Error Response Design
Design appropriate error responses for the following situations, including status code and response body:
- A user registration form with multiple validation errors (username too short, invalid email format, passwords don't match)
- An attempt to access data from a user's account when logged in as a different user
- A payment processing failure due to insufficient funds
- A request to an API endpoint that has been deprecated and removed
- A database timeout during a resource-intensive query
Activity 3: HTTP Conversation Analysis
Analyze the following HTTP conversations, identify any issues, and suggest improvements:
# Conversation 1
POST /users/search HTTP/1.1
Content-Type: application/json
{
"name": "John",
"status": "active"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"users": [
{"id": 123, "name": "John Smith"},
{"id": 456, "name": "John Doe"}
]
}
# Conversation 2
DELETE /articles/789 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"message": "Article deleted successfully"
}
# Conversation 3
PUT /products/456 HTTP/1.1
Content-Type: application/json
{
"price": 129.99
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 456,
"name": "Wireless Keyboard",
"description": "Ergonomic design",
"price": 129.99
}
# Conversation 4
GET /orders HTTP/1.1
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"status": 401,
"error": "You must be logged in to view orders"
}
# Conversation 5
POST /products HTTP/1.1
Content-Type: application/json
{
"name": "New Product",
"price": -10.00
}
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Validation failed: Price must be greater than 0"
}
Summary
In this lecture, we've explored the critical role of HTTP methods and status codes in RESTful API design:
- HTTP Methods: We examined the semantic meaning and appropriate usage of GET, POST, PUT, PATCH, DELETE, and other methods.
- Method Characteristics: We learned about safety, idempotence, and cacheability as important properties that guide method selection.
- HTTP Status Codes: We explored the five classes of status codes and when to use specific codes to communicate clearly with API clients.
- Headers and Content Negotiation: We discussed how HTTP headers enable content type, language, and encoding negotiation.
- Implementation Examples: We saw practical examples of HTTP methods and status codes working together in real API scenarios.
- Common Pitfalls: We identified frequent mistakes in HTTP implementation and how to avoid them.
- Best Practices: We summarized key guidelines for effective use of HTTP in RESTful APIs.
Understanding HTTP methods and status codes is foundational for RESTful API design and development. By leveraging these standard mechanisms correctly, we create APIs that are intuitive, predictable, and aligned with web architecture principles.
Remember that HTTP is more than just a transport protocol—it's a rich application protocol with semantics that can greatly simplify API design when used correctly. The careful selection of methods and status codes serves as a form of documentation, helping API consumers understand what operations are possible and what has happened as a result of their requests.