The goal of this article is to provide an overview of HTTP and its principles. If you are new to HTTP, you might find this article to be a helpful primer on the topic. If you are already familiar with HTTP, you can skip to the next section. This article doesn't discuss much about best practices or implementation details.
Distributed systems
The internet is a vast, interconnected network of computers, servers, and devices spanning the globe. It's a massive distributed system where millions of components must communicate seamlessly across different networks, protocols, and organizational boundaries. Unlike centralized systems where all communication flows through a single point of control, the internet operates as a decentralized mesh where any node can potentially communicate with any other node, often through multiple intermediate systems and across various trust domains. How do we manage to communicate in such a system?1
HTTP
The Hypertext Transfer Protocol (HTTP) is a stateless communication protocol that allows computers to communicate over a network. It is expressly designed to be a usable as an interface to distributed object systems2. It is a longstanding and proven technology that operates as the foundation of the web and is used by almost all modern web applications.
There are a number of RFCs that provide specifications for HTTP. I won't rehash all the specifics here, but I would note that they are worth reading. I know it is tempting to avoid reading documentation. In my experience, the "quick and easy" approach often ends up causing more trouble than if you had just invested a little bit of time learning for yourself. Just read the RFCs.
If after reading this section you are still having trouble getting an intuition for HTTP, I would recommend taking this course. The instructor walks you through implementing the HTTP/1.1 protocol from scratch in go. I enjoyed it thoroughly and learned some new things even with several years of experience building HTTP servers. Although all programming languages have a library for HTTP which will handle many details of the protocol for you, it is worthwhile understanding what is happening under the hood.
Semantics
The semantics of HTTP are messages sent between two computers (a client and a server). A client "connects" to a server and sends a "request" message. The server may accept the request and provide a "response" message. The target of the request is called a "resource". A "representation" is a reflection of a state of a resource.2 Messages are stateless, which means we can understand the semantics of a request in isolation. This turns out to be an extremely powerful property. With it, we can reuse proxies and load balance requests across multiple servers.
There are a few key components to a HTTP message:
- Methods indicate the desired action (e.g.,
GET,POST,PUT,DELETE). - Status Codes indicate the result of the server's processing of the request (e.g.,
200 OK,404 Not Found). - Headers provide additional information about the request or response (e.g.,
Content-Type,Authorization). - The Body contains the data sent with the request or returned by the server.
Example request message:
GET /users/123 HTTP/1.1
Host: api.example.com
Example response message:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "123",
"name": "John Doe",
"email": "john.doe@example.com"
}
REST principles
REST (Representational State Transfer)1 is an architectural style that builds on top of HTTP semantics to create predictable, discoverable APIs. At its core, REST treats everything as a resource that can be manipulated through a uniform interface. This uniform interface is what makes REST powerful. Instead of having custom actions for each resource, you use the same HTTP methods across all resources in a consistent way. When you design a RESTful API, you're mapping your domain objects to HTTP resources and letting the standard HTTP semantics handle the interaction patterns.
We call an API that adheres to these principles a RESTful API. These APIs model resources (nouns) rather than actions (verbs).
Instead of endpoints like /createUser or /deletePost, they design around resources like /users and /posts, using HTTP methods to
indicate the action. This creates a consistent, predictable interface where developers can quickly understand how to interact with any resource in your
system. For example, if you know how to work with /users, you can immediately understand how to work with /products or
/orders because they follow the same patterns.
The uniform interface principle extends beyond just HTTP methods to include consistent response formats, error handling, and pagination across all endpoints. This consistency reduces cognitive load for developers and makes the API feel like a cohesive system rather than a collection of unrelated endpoints.
HTTP methods
HTTP methods (also known as verbs) indicate the desired action to be performed on a given resource.2 Each method has specific semantics that inform the server how to process the request.
Method properties
There are a few properties of HTTP methods that are worth understanding.
Side effects and safe methods
If you have read any of the RFCs or have read about APIs, chances are you have come across the word "side effects".
- Side effects: Any observable changes on the server or in the system caused by handling a request, beyond just returning the requested data.
- Safe requests: Requests that do not have any intended effect on the server's state.
In practice, all requests will have some side effect as we will log each request our server receives. From the RFC on HTTP Semantics:
Request methods are considered "safe" if their defined semantics are essentially read-only; i.e., the client does not request, and does not expect, any state change on the origin server as a result of applying a safe method to a target resource. Likewise, reasonable use of a safe method is not expected to cause any harm, loss of property, or unusual burden on the origin server.2
Note that definition of safe requests revolves around the client's expectations. They expect a safe request to neither alter state nor cause harm. Incidental side effects (like logging or analytics) do not violate these expectations and may therefore occur.
The GET, HEAD, OPTIONS, and TRACE methods are defined to be safe.2 The purpose of specifying safe methods is to
allow for automated retrieval, caching and lets users understand which methods should have some constraints programmed around
them.
Idempotent requests
A request method is called "idempotent" if making the same request multiple times has the same intended effect on the server as
making it just once.2 Put simply, repeating the same request should not cause any additional or unintended changes.
PUT, DELETE, and safe request methods are idempotent by definition.2 The POST method is not idempotent by default. For
example, a POST request to an endpoint /users might create an unintended duplicate user record if the client were to send the
POST request twice in a row. We talk more about idempotency later .WIP
Methods and caching
A cache can store a response if the method explicitly allows it.2 Usually, we will only consider caching a response from a GET
and HEAD request. We talk more about caching later .WIP
Methods overview
Here is a quick overview of the methods and what they do.
| Method | Idempotent | Safe | Action |
|---|---|---|---|
GET | Yes | Yes | Read |
POST | No | No | Create |
PATCH | No | No | Update |
PUT | Yes | No | Replace |
DELETE | Yes | No | Delete |
GET
A GET request is used to retrieve the current selected representation of a resource from a server. This is the primary way we retrieve
information from an API. A GET request has no body, meaning all information necessary to specify the request must be included in the request
URI, headers, or query parameters.2
Use Cases:
- Retrieving a resource
- Checking the status of a resource
- Retrieving a subset of a resource
- Retrieving many resources
Sample Response Codes:
200 OKfor successfulGETrequests400 Bad Requestfor invalidGETrequests404 Not FoundforGETrequests where the requested resource is not found
Caching: GET responses are cacheable by default. Most caching strategies focus on GET requests since they're the primary method for
information retrieval. The Cache-Control header is used to control caching behavior.
Range Requests: The Range header allows clients to request only specific parts of a representation, useful for large resources or
resumable downloads.
Security Considerations: When using GET with user-provided data (like form queries), be aware that sensitive information can be exposed in URIs.
Consider using a different method instead when transmitting sensitive data that shouldn't appear in URLs.
HEAD
The HEAD method is similar to GET, but it only retrieves the headers of the response. This is useful for checking the status of a resource
without downloading the entire representation.2
Caching: HEAD responses are cacheable by default. The Cache-Control header
is used to control caching behavior.
POST
The POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics.
This is the primary method for creating new resources or performing non-idempotent actions that don't fit neatly into other HTTP methods.2
Common Use Cases:
- Creating new resources (e.g., creating a new user account)
- Submitting form data to a data-handling process
- Performing actions that don't map to standard CRUD operations
- Appending data to existing resources
Response Codes: The server should choose an appropriate status code based on the result:
201 Createdwhen a new resource is successfully created (include aLocationheader)200 OKfor successful processing without resource creation (see idempotency)WIP202 Acceptedfor asynchronous operations that will complete later400 Bad Requestfor invalid input data409 Conflictwhen the request conflicts with current state
Caching: POST responses are only cacheable when they include explicit freshness information and a Content-Location header matching the POST's
target URI. A cached POST response can be reused to satisfy later GET or HEAD requests.
Idempotency: POST is not idempotent by default. Use an Idempotency-Key header to make POST requests safely retryable.
PUT
The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in
the request message content.2 It can also be used for performing idempotent actions that don't fit neatly into other HTTP methods.
Key Characteristics:
- Idempotent: Multiple identical
PUTrequests have the same effect as a single request - Complete Replacement:
PUTreplaces the entire resource state, not just parts of it - Create or Update:
PUTcan create a new resource if it doesn't exist, or replace an existing one
Response Codes:
201 Createdwhen a new resource is successfully created200 OKwhen an existing resource is successfully updated409 Conflictwhen the representation conflicts with current resource constraints415 Unsupported Media Typewhen the content type is not acceptable
Validation: The server should verify that the PUT representation is consistent with configured constraints for the target resource.
If inconsistencies exist, the server should either transform the representation to make it consistent or respond with an appropriate error.
Caching: PUT responses are not cacheable. Successful PUT requests invalidate any cached responses for the target URI.
PATCH
The PATCH method applies partial modifications to a resource. Unlike PUT which replaces the entire resource, PATCH allows for targeted updates
to specific fields or properties.3
Use Cases:
- Updating specific fields of a resource without affecting others
- Applying incremental changes to complex objects
- Supporting partial updates in a controlled manner
- Modifying resources that may not exist (depending on patch document type)
Key Characteristics:
- Not Safe:
PATCHcan modify resource state - Not Idempotent: By default,
PATCHis not idempotent, though it can be designed to be - Conditional: Should use conditional requests (
If-MatchwithETags) to prevent conflicts
Response Codes:
200 OKfor successful partial updates400 Bad Requestfor malformed patch documents409 Conflictwhen the patch cannot be applied due to resource state412 Precondition Failedwhen conditional request fails (e.g., ETag mismatch)415 Unsupported Media Typewhen the patch document format is not supported422 Unprocessable Entityfor semantic validation errors
Caching: PATCH responses are only cacheable when they include explicit freshness information and a Content-Location header matching the Request-URI.
A cached PATCH response can only be used to respond to subsequent GET and HEAD requests.
Accept-Patch Header: Servers should include an Accept-Patch header in OPTIONS responses to advertise supported patch document formats.
DELETE
The DELETE method requests that the origin server remove the association between the target resource and its current functionality.2
Key Characteristics:
- Idempotent: Multiple
DELETErequests to the same resource have the same effect - Resource Removal: Deletes the resource mapping, not necessarily the underlying data
- Implementation Dependent: The actual effect depends on the server's implementation
Response Codes:
200 OKwhen the deletion succeeds and includes a representation describing the status202 Acceptedwhen the deletion will likely succeed but hasn't been enacted yet204 No Contentwhen the deletion succeeds and no further information is needed
Caching: DELETE responses are not cacheable. Successful DELETE requests invalidate any cached responses for the target URI.
CONNECT
The CONNECT method requests that the recipient establish a tunnel to the destination origin server identified by the request target.
This is primarily used for establishing secure connections through proxies.2
Use Cases:
- Creating HTTPS tunnels through HTTP proxies
- Establishing end-to-end encrypted connections
- Supporting secure communication in corporate environments
Request Format: The request target consists of only the host and port number (e.g., server.example.com:443)
Security Considerations:
- Restrict
CONNECTto known, safe ports (typically 443 for HTTPS) - Implement proper authentication for proxy access
- Be cautious of
CONNECTrequests to non-standard ports
Response Codes:
2xxresponses indicate successful tunnel establishment4xxresponses indicate the tunnel could not be established
Caching: CONNECT responses are not cacheable.
OPTIONS
The OPTIONS method requests information about the communication options available for the target resource or the server in general.2
Use Cases:
- Discovering available HTTP methods for a resource
- Checking server capabilities and supported features
- Implementing CORS preflight requests
- Testing server availability and configuration
Response Headers: The response should include relevant headers such as:
Allow: Lists the HTTP methods supported by the target resourceAccess-Control-Allow-Methods: For CORS supportAccess-Control-Allow-Headers: For CORS support
Request Target:
- Specific resource: Returns options for that resource
- Asterisk (
*): Returns general server capabilities
Caching: OPTIONS responses are not cacheable.
TRACE
The TRACE method requests a remote, application-level loop-back of the request message. This is primarily used for debugging and
diagnostic purposes.2
Use Cases:
- Debugging request/response chains through proxies
- Testing proxy configurations
- Diagnosing network issues
- Security testing and validation
Security Considerations:
- Never include sensitive data in
TRACErequests - Be cautious of information disclosure through
TRACEresponses - Consider disabling
TRACEin production environments
Response Format: The response should reflect the received request message, typically in message/http format
Request Content: TRACE requests should not include a request body
Caching: TRACE responses are not cacheable.
Response codes
Response codes are how the server communicates the result of the request to the client. I won't go into all the details of the response codes here, but they have an interesting history.4 There is an RFC that covers them in detail.2 Here are some common response codes that you will frequently use.5
| Code | Description | Typical Methods | Notes |
|---|---|---|---|
| 200 | OK | GET POST DELETE PATCH PUT | Request succeeded for a GET, POST, DELETE, or PATCH call that completed synchronously, or a PUT call that synchronously updated an existing resource. |
| 201 | Created | POST PUT | Request succeeded for a POST or PUT call that synchronously created a new resource. It is also best practice to provide a Location header pointing to the newly created resource. |
| 202 | Accepted | POST PUT DELETE PATCH | Request accepted for a POST, PUT, DELETE, or PATCH call that will be processed asynchronously. |
| 204 | No Content | DELETE | The request was successful and the response body is empty. Careful with this one.6 |
| 206 | Partial Content | GET | Request succeeded on GET, but only a partial response returned (see below on ranges). |
| 400 | Bad Request | GET | The request was invalid. |
| 401 | Unauthorized | GET | Request failed because user is not authenticated. |
| 403 | Forbidden | GET | Request failed because user does not have authorization to access a specific resource. |
| 404 | Not Found | GET | The resource was not found. |
| 410 | Gone | GET | The resource is no longer available. See idempotency WIP for more details. |
| 422 | Unprocessable Entity | GET | Your request was understood, but contained invalid parameters. |
| 429 | Too Many Requests | GET | You have been rate-limited, retry later. |
| 500 | Internal Server Error | GET | Something went wrong on the server, check status site and/or report the issue. |
URI scheme
A Uniform Resource Identifier (URI) is the address that uniquely identifies a resource on the web. The path component indicates the identity and location of the resource within your API's resource hierarchy.5
Key principles:
- Consistency: Use a consistent URI scheme across your entire API
- Lowercase: URIs are normalized to lowercase2, so always use lowercase to avoid confusion and redirects
- Security: URIs are public and visible in logs, browser history, and network traces. Never include sensitive information like API keys, passwords, or personal data
- Readability: URIs should be human-readable and self-documenting
URI Structure Examples:
http://www.example.com/users/123/posts/123:80
https://www.example.com/users?q=john&age=30
Protocol
The protocol defines the communication method and security level. http sends data in plain text, while https encrypts data using TLS/SSL
for secure transmission.
Host
The host identifies the server that will process your request. It can be a domain name (like api.github.com) or an IP address
(like 192.168.1.1). The host determines which server receives your API call.
Path
The path specifies the exact location of the resource on the server. It follows a hierarchical structure where each segment represents
a level of organization. For example, /users/123/posts means "posts belonging to user with ID 123."
Target resource
The target resource is singular entity you're interacting with. In /users/123, the target resource is a specific user that can be
identified by the resource identifier 123.
Collection
A collection represents a group of similar resources. Collections are typically plural nouns that group related items together. Examples
include /users, /orders, /products. Use consistent naming conventions across your API.
Resource identifier
The resource identifier uniquely identifies a specific resource within a collection. It's usually a numeric ID, UUID, or slug. For example,
/users/123 identifies user #123, while /users/john-doe might identify a user by username.
Port
The port specifies which network port the server listens on for requests. Port 80 is the default for HTTP, and 443 is the default for HTTPS.
You typically don't need to specify these in URLs unless using non-standard ports.
Query parameters
Query parameters provide additional information to filter, sort, or modify the request. They come after the ? and are separated by &. Common
uses include search terms (?q=john), pagination (?page=2&limit=10), and filtering (?status=active&category=tech).
Headers
Headers communicate metadata about the request or response. There are many headers, but we will only cover the most common ones. You can find more
complete information in the RFCs.2 You might occasionally see headers that start with X-. This was a historical convention denoting experimental
HTTP headers or headers that do not conform to the standard. This practice has now been depreciated and consequently you should not use X- headers.7
User Agent
The User-Agent header is a string sent by a client to identify the software making the request (browser, SDK, bot, CLI, etc.).
Example:
GET /users/123 HTTP/1.1
Host: api.example.com
User-Agent: curl/8.1.0
This is useful for debugging and analytics and occasionally for providing a more tailored experience.8
Location
A Location 2 header is used by the server to indicate the URI of a newly created or redirected resource.
- In
201 Createdresponses, it tells the client where the newly created resource can be found. - In
3xx Redirectresponses, it specifies the URL the client should request instead.
Example:
POST /users HTTP/1.1
Host: api.example.com
Location: /users/123
Content-Type
The Content-Type header is used to indicate the media type of the resource in the response body.
Example:
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key
The Idempotency-Key request header lets clients make non-idempotent methods like POST fault-tolerant by safely
retrying the same operation without causing duplicates. See the IETF Internet-Draft for details.9 We talk about
Idempotency later WIP.
Example:
POST /users HTTP/1.1
Host: api.example.com
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324
Range
The Range request header serves two primary purposes: requesting partial content from large resources and implementing pagination
for list endpoints. This dual functionality makes it a powerful tool for efficient data retrieval.5 10
Partial content requests
For large files and resources, the Range header allows clients to request only specific portions:2
Units: Most APIs use bytes as the unit for file downloads.
Partial responses: When the server honors a range request, it responds with 206 Partial Content and
includes a Content-Range header indicating the range returned.
Example (file download):
GET /files/abc123 HTTP/1.1
Host: api.example.com
Range: bytes=0-999
Response:
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-999/12345
List pagination and sorting
For list endpoints, the Range header may be used to paginate and sort.2
Default behavior: List requests return a Content-Range header indicating the range of values returned. Large lists may require
additional requests to retrieve all data.
Pagination: If a list response has been truncated, you'll receive a 206 Partial Content status with a Next-Range header. To
retrieve the next range, repeat the request with the Range header set to the value of the previous request's Next-Range header.
Page size control: Use the max parameter to control the number of values returned in a range.
Example (pagination):
GET /users HTTP/1.1
Host: api.example.com
Range: id ..; max=10;
Response (truncated):
HTTP/1.1 206 Partial Content
Content-Range: id 1-10/1000
Next-Range: id 11..; max=10;
Next page request:
GET /users HTTP/1.1
Host: api.example.com
Range: id 11..; max=10;
Cache-Control
The Cache-Control header controls how, if at all, responses are cached by clients, proxies, and CDNs.
Common values:
no-store- Don't cache at all.no-cache- Cache, but always revalidate before using.public- Cacheable by any cache.private- Cacheable only by the client's browser or device.max-age=<seconds>- Cache lifetime before revalidation.
For dynamic or sensitive API responses, Cache-Control: no-store is safest unless caching is intentional.
Example:
GET /users/123 HTTP/1.1
Host: api.example.com
Cache-Control: no-cache
Authorization
The Authorization header carries credentials for authenticating the client with the API.
The most common scheme is Bearer tokens. However, there are other schemes such as Basic auth, HMAC signatures, and custom schemes. Always use HTTPS to protect credentials in transit.
Example (Bearer token):
GET /users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_4eC76HqLyjWTfdrjtT1zds90c
Accept
The Accept request header tells the server which response formats the client can handle.
APIs usually default to application/json but may allow other media types. It is probably a good idea to
just use application/json.
Example:
GET /users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
You can also include versioning in the Accept header, but it is also okay to use a custom header for this.5
Retry-After
The Retry-After response header tells the client how long to wait before retrying.
Sent with 429 Too Many Requests or 503 Service Unavailable.
Value can be:
HTTP-date: Exact time to retry (we will discuss why this is a bad idea later).WIPSeconds: Relative wait time.
Example:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Rate Limit Headers
There are a few standardized headers for communicating API rate limits to clients.
RateLimit-Limit: Max requests allowed in the current window.
RateLimit-Remaining: Requests left before hitting the limit.
RateLimit-Reset: Seconds until the limit resets.
Example:
HTTP/1.1 200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 42
RateLimit-Reset: 120
ETag
The ETag response header contains a unique identifier for a resource version. These can either be "weak" or "strong".
Strong ETags:
Change on any byte difference.
ETag: "abc123"
Weak ETags:
Allow minor changes without invalidating.
ETag: W/"abc123"
It is a good idea to include it in all responses.5 The server can determine what exactly the version identifier is and clients should not try to parse it.2 However, don't assume that clients will not try to parse it. It might make sense for the identifier to be distinct from typical resource identifiers so that clients won't make any assumptions about its format and utilize it outside of its intended use.
If-None-Match
The If-None-Match request header is sent by the client to the server along with an ETag representing the version of the resource
that the client currently has cached. The server can then check to see if that version of the resource matches the current version.
If the resource has not changed, the server can return a 304 Not Modified response. This is important because it allows us
to avoid sending information that the client already has.5
Example (conditional GET):
GET /users/123 HTTP/1.1
Host: api.example.com
If-None-Match: "abc123"
Response (unchanged):
HTTP/1.1 304 Not Modified
If-Match
The If-Match request header is sent by the client to the server along with an ETag representing the version of the resource
that the client currently has cached. The server can then determine if the resource has changed since the client last requested it.
If the resource has changed, the server can return a 412 Precondition Failed response. This is important because it allows us
to avoid race conditions where the client updates a resource that has been updated by another client.8
Example (conditional PATCH):
PATCH /users/123 HTTP/1.1
Host: api.example.com
If-Match: "abc123"
Response (changed):
HTTP/1.1 412 Precondition Failed
Response (unchanged):
HTTP/1.1 200 OK
Request-Id
It is a good idea to record every request sent to your API and provide a unique identifier for each request. This can be useful for debugging
and tracing.5 The Request-Id header is a standardized header for this purpose and should be included in all responses.
Example:
HTTP/1.1 200 OK
Request-Id: req_a1b2c3d4
Body
The body of an HTTP message contains the data being transmitted between client and server. In HTTP requests, the body carries the data that the client wants to send to the server (such as form data, JSON payloads, or file uploads). In HTTP responses, the body contains the data that the server returns to the client (such as HTML pages, JSON responses, or file downloads).
We call the data returned by the server a "representation" of the resource. A resource has underlying state that cannot be seen directly by the client. Hence, we call the data returned by the server a "representation" of that resource state. Clients might want to see that resource in different formats, which we call media types.
The body is optional - many HTTP messages don't have one. GET requests typically don't include a body, while POST, PUT, and PATCH requests
commonly do. The presence and format of the body is indicated by headers like Content-Type and Content-Length.
Footnotes
-
HTTP Semantics - RFC 9110 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23
-
HTTP API Design Guide - Mark McGranaghan and Brandur Leach ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
HTTP API design: 204 "No content" mildly harmful - Brandur Leach ↩
-
Deprecating the "X-" Prefix and Similar Constructs in Application Protocols - RFC 6648 ↩