BytePane

HTTP Methods Explained: GET, POST, PUT, DELETE & More

API Design19 min read

The Myth: HTTP Methods Are Just Labels

A common misconception among developers learning REST is that HTTP methods are advisory — a naming convention with no mechanical consequences. Under this mental model, GET /delete-user/123 is a bit unconventional, but it works. Why bother with DELETE?

The reality: HTTP methods have precise semantic contracts defined in RFC 9110 (published June 2022, the current HTTP semantics standard that supersedes RFC 7231). Those contracts carry real consequences. A GET response can be cached by a CDN, a proxy, or the browser — transparently, without your involvement. A PUT request can be retried automatically by an HTTP client after a timeout without creating duplicate records. A POST cannot be retried safely. These behaviors are baked into browsers, CDNs, HTTP clients, and load balancers — whether your API respects them or not.

This article covers all nine HTTP methods defined in RFC 9110 and RFC 5789, their safety and idempotency properties precisely defined, the correct status codes per method, CORS preflight mechanics, and the design mistakes that violate the HTTP contract in ways your users will eventually feel.

Key Takeaways

  • Safe methods (GET, HEAD, OPTIONS, TRACE) are guaranteed read-only. Caches, crawlers, and prefetch systems call them freely.
  • Idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) can be retried safely after a network timeout. POST cannot.
  • PATCH vs PUT: PUT replaces the entire resource; PATCH applies partial changes. Use PATCH to update a single field.
  • POST should return 201 Created with a Location header on success — not 200 OK.
  • CORS preflight sends OPTIONS first for non-simple requests — always implement OPTIONS handlers that return correct Access-Control headers.

The RFC Foundation: RFC 9110 and RFC 5789

HTTP methods are not defined by a framework or convention — they are defined by IETF standards documents. The current authoritative references are:

  • RFC 9110 (June 2022) — "HTTP Semantics." Defines GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, and TRACE. Supersedes RFC 7231.
  • RFC 5789 (March 2010) — Defines the PATCH method, which was intentionally omitted from the original HTTP/1.1 spec.

RFC 9110 also defines three critical properties that every method either has or lacks — and these properties drive the behavior of every HTTP intermediary in the path between client and server.

Safe, Idempotent, and Cacheable: Precise Definitions

Safe (RFC 9110 §9.2.1)

A method is safe if the client's intended semantics are read-only. The server may still perform side effects (logging, analytics updates) — but those are incidental to the request, not caused by the client. Safe methods are GET, HEAD, OPTIONS, and TRACE.

The practical consequences of safety: web crawlers, link prefetchers, and browser "hover to prefetch" features call safe methods freely. If your "delete account" endpoint is a GET request, a zealous crawler or browser prefetch can trigger it. This has happened in production.

Idempotent (RFC 9110 §9.2.2)

A method is idempotent if the intended effect on the server is the same whether the request is sent once or ten times. Idempotent methods: GET, HEAD, PUT, DELETE, OPTIONS, TRACE. POST is not idempotent. PATCH is not idempotent by default (though it can be designed to be).

Important nuance: idempotent does not mean "returns the same response code every time." DELETE /users/123 returns 200 the first call (resource deleted) and 404 the second (resource already gone). Both are correct — the state of the server (user 123 does not exist) is the same after either call.

The practical consequence: HTTP clients can automatically retry idempotent requests after a network timeout. If a PUT request times out, the client doesn't know if the server processed it or not — but it can safely retry because a second identical PUT produces the same final state. A POST /orders that times out cannot be retried automatically: the server may have created the order, and a retry would create a duplicate.

Cacheable (RFC 9110 §9.2.3)

A cacheable method's responses can be stored and reused by caches. GET and HEAD are cacheable by default. POST and PATCH responses can be cached if the response includes explicit Cache-Control or Expires headers. PUT and DELETE are not cacheable.

MethodRFCSafeIdempotentCacheableHas Request Body
GETRFC 9110 §9.3.1YesYesYesDiscouraged
HEADRFC 9110 §9.3.2YesYesYesNo
POSTRFC 9110 §9.3.3NoNoOnly if explicitYes
PUTRFC 9110 §9.3.4NoYesNoYes
DELETERFC 9110 §9.3.5NoYesNoOptional
PATCHRFC 5789NoBy designOnly if explicitYes
OPTIONSRFC 9110 §9.3.7YesYesNo (separate cache)Optional
HEADRFC 9110 §9.3.2YesYesYesNo
TRACERFC 9110 §9.3.8YesYesNoNo

Each HTTP Method in Depth

GET — Retrieve a Representation

GET requests the current representation of a resource at the target URI. The server must not modify state in response to a GET. GET responses are cacheable and CDN-friendly. Correct status codes: 200 (success with body), 304 (conditional GET, cache still valid), 404 (not found), 410 (permanently gone).

GET /users/123 HTTP/1.1
Host: api.example.com
Accept: application/json

# Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60
ETag: "abc123"

{"id": 123, "name": "Alice", "email": "[email protected]"}

# Conditional GET (revalidation) — server returns 304 if ETag matches
GET /users/123 HTTP/1.1
If-None-Match: "abc123"

POST — Submit Data for Processing

POST submits data to the resource for processing. The resource defines what that means — it can create a new subordinate resource (most common), process a form, append to a log, trigger an action, or anything else. POST is neither safe nor idempotent: two identical POST requests may create two separate resources.

POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json

{"name": "Bob", "email": "[email protected]"}

# Correct response: 201 Created with Location header
HTTP/1.1 201 Created
Location: /users/124
Content-Type: application/json

{"id": 124, "name": "Bob", "email": "[email protected]"}

# WRONG: returning 200 OK on resource creation
# WRONG: no Location header
# WRONG: returning the new resource URL in the body instead of Location

PUT — Replace a Resource

PUT replaces the entire current representation of the target resource with the request payload. If the resource does not exist, PUT may create it (returning 201). PUT is idempotent: sending the same PUT twice leaves the server in the same state as sending it once. The complete resource must be included in the body — sending a partial object with PUT means the omitted fields will be lost or nulled.

PUT /users/123 HTTP/1.1
Content-Type: application/json

# MUST include ALL fields — this replaces the entire user object
{"id": 123, "name": "Alice Smith", "email": "[email protected]", "role": "admin"}

# Response options:
HTTP/1.1 200 OK       # Updated — include updated representation in body
HTTP/1.1 204 No Content  # Updated — no body (common for PUT)
HTTP/1.1 201 Created  # Resource was created (didn't exist before)

PATCH — Partial Update (RFC 5789)

PATCH applies a partial modification to a resource. Unlike PUT, only the fields included in the PATCH body are changed. PATCH is not inherently idempotent — two identical PATCH requests may produce different results if the first modified state the second depends on. However, well-designed PATCH implementations can be made idempotent.

RFC 5789 specifies that servers should advertise PATCH support via the Accept-Patch header in OPTIONS responses, indicating which patch document formats they accept (e.g., application/json-patch+json from RFC 6902).

# Approach 1: Merge patch (application/merge-patch+json, RFC 7396)
PATCH /users/123 HTTP/1.1
Content-Type: application/merge-patch+json

{"email": "[email protected]"}  # Only email changes; all other fields unchanged

# Approach 2: JSON Patch (application/json-patch+json, RFC 6902)
PATCH /users/123 HTTP/1.1
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/email", "value": "[email protected]" },
  { "op": "add", "path": "/tags/-", "value": "premium" }
]

# Response
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 123, "name": "Alice Smith", "email": "[email protected]", ...}

DELETE — Remove a Resource

DELETE removes all current representations of the target resource. DELETE is idempotent — the server state (resource gone) is the same whether you call it once or five times, even though the second through fifth calls return 404. The response body is optional; 204 No Content is the most common success response.

DELETE /users/123 HTTP/1.1

# First call: user exists
HTTP/1.1 204 No Content     # Deleted, no body (most common)
# OR
HTTP/1.1 200 OK             # Deleted, with confirmation body

# Second call: user already gone — still idempotent
HTTP/1.1 404 Not Found      # Correct: resource doesn't exist

# 202 Accepted: async deletion (queued for processing)
HTTP/1.1 202 Accepted
{"message": "Deletion scheduled", "eta": "30s"}

HEAD — Metadata Without Body

HEAD is identical to GET, but the server must not return a message body. All response headers that GET would return (Content-Length, ETag, Last-Modified, Content-Type) must appear in the HEAD response. Use HEAD to check if a resource exists, read its size before downloading, or validate cache freshness — without transferring the body.

# Check if a large file exists and how big it is before downloading
HEAD /files/backup-2026-05-01.tar.gz HTTP/1.1

HTTP/1.1 200 OK
Content-Length: 2147483648      # 2 GB
Content-Type: application/gzip
Last-Modified: Thu, 01 May 2026 02:00:00 GMT
ETag: "deadbeef"
# No body

# Link validation: check if a URL still returns 200
HEAD /blog/old-article/ HTTP/1.1
HTTP/1.1 301 Moved Permanently  # URL has moved

OPTIONS — Discover Capabilities

OPTIONS returns the HTTP methods supported by the target resource, primarily via the Allow response header. Most developers encounter OPTIONS exclusively through CORS preflight — the browser sends OPTIONS before cross-origin requests to ask what the server permits.

# Manual OPTIONS — what methods does this endpoint support?
OPTIONS /users/123 HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Accept-Patch: application/merge-patch+json, application/json-patch+json

# CORS preflight — browser sends this automatically before cross-origin requests
OPTIONS /api/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400     # Cache this preflight response for 24h

CONNECT — Tunnel Establishment

CONNECT requests a proxy to establish a TCP tunnel to the destination server. It is used by browsers when connecting to HTTPS sites through an HTTP proxy. CONNECT api.example.com:443 tells the proxy to open a raw TCP connection to port 443; the browser then performs its TLS handshake through that tunnel. Application developers rarely implement CONNECT directly.

TRACE — Diagnostic Loop-Back

TRACE performs a message loop-back diagnostic. The server echoes the request back as the response body, letting clients see what intermediaries (proxies, gateways) modified in transit. TRACE is disabled in all modern browsers due to the Cross-Site Tracing (XST) attack — it can expose cookies and authentication headers to JavaScript via XMLHttpRequest. Web servers should disable TRACE in production.

Correct Status Codes by HTTP Method

One of the most common REST API inconsistencies is returning incorrect status codes. The status code carries information to caches, clients, and monitoring systems — returning 200 for everything (including errors) breaks this signaling entirely. Per RFC 9110 and the HTTP status codes guide:

MethodSuccess CodesCommon Error CodesNotes
GET200, 304400, 404, 410410 Gone = permanently deleted, don't retry
POST201, 202, 200400, 409, 422201 + Location header for creation; 422 for validation failures
PUT200, 201, 204400, 409, 422201 if resource was created; 200/204 if replaced
PATCH200, 202, 204400, 409, 422Return updated resource in 200; 409 if patch conflicts with current state
DELETE200, 202, 204404, 409204 most common (no body); 404 on second call is correct and expected
OPTIONS200, 204200 is safer for CORS preflight (some Safari versions reject 204)

5 HTTP Method Mistakes That Break APIs

1. Using GET for State-Mutating Operations

# WRONG — GET must never mutate state
GET /api/users/123/delete
GET /api/cart/checkout
GET /api/emails/[email protected]

# RIGHT
DELETE /api/users/123
POST /api/orders                  # checkout creates an order
POST /api/emails

# Why it matters: Google's web crawler, browser prefetch, and link checkers
# will follow GET URLs freely. "Oops, I accidentally deleted a user
# because Googlebot crawled /api/users/123/delete" has happened in production.

2. Using POST for All Updates (Tunnel Pattern)

# WRONG — "method tunneling" ignores HTTP semantics
POST /api/users/123?method=PUT   # using a query param to fake the method
POST /api/users/123/update       # using a URL verb instead of HTTP method

# RIGHT
PUT /api/users/123     # full replacement
PATCH /api/users/123   # partial update

# The consequence: retry logic breaks. If PUT /api/users/123 times out,
# the client can safely retry. If POST /api/users/123?method=PUT times out,
# the client has no way to know if it should retry without risking duplication.

3. PATCH When You Mean PUT (or Vice Versa)

// Scenario: update a user's email address only

// WRONG — using PUT with partial data
PUT /users/123
{"email": "[email protected]"}
// This replaces the entire user! name, role, etc. become null or error.

// RIGHT — use PATCH for partial updates
PATCH /users/123
{"email": "[email protected]"}
// Only email changes; all other fields are preserved.

// WRONG — using PATCH when you're replacing everything
PATCH /users/123
{"id": 123, "name": "Alice", "email": "[email protected]", "role": "admin", ...}
// This is a full replacement — use PUT.

4. Missing CORS OPTIONS Handler

// WRONG: Only implement POST handler, forget OPTIONS
app.post('/api/orders', createOrder);
// Browser sends OPTIONS preflight → 404 or 405 → CORS error → POST never fires

// RIGHT: Handle OPTIONS explicitly (or use a middleware)
// Express.js
app.options('/api/orders', cors(corsOptions));
app.post('/api/orders', cors(corsOptions), createOrder);

// OR use cors() middleware globally before routes
app.use(cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Authorization', 'Content-Type'],
  maxAge: 86400,  // cache preflight 24h — reduces OPTIONS request volume
}));

// FastAPI (Python) — OPTIONS is handled automatically with CORSMiddleware
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.com"], ...)

5. GET with a Request Body

// Controversial but real: Elasticsearch _search uses GET with a body
GET /index/_search
{"query": {"match": {"name": "Alice"}}}
// This works with curl and Elasticsearch's HTTP client, but breaks:
// - Many API gateways that strip GET bodies
// - AWS API Gateway (explicitly rejects GET bodies)
// - Some load balancers and CDNs

// Better alternatives:
POST /index/_search      // POST for complex queries (most REST clients support it)
GET /users?name=Alice    // Query string for simple filters
// If query data is too complex for query strings, POST is the right answer.

HTTP/2 and HTTP/3: What Changes for Methods?

The critical point: HTTP/2 (RFC 9113) and HTTP/3 change the transport, not the semantics. All method definitions, safety properties, idempotency rules, and status codes from RFC 9110 apply identically across HTTP/1.1, HTTP/2, and HTTP/3.

What HTTP/2 and HTTP/3 do change at the transport layer:

  • Multiplexing: Multiple requests interleave over a single connection as separate streams. A blocked POST does not prevent a concurrent GET from completing.
  • Binary framing: Methods are transmitted as binary, not ASCII text. Parsing is faster, but method semantics are unchanged.
  • Server push: HTTP/2 allows the server to preemptively send resources the client will need — but push is effectively deprecated. Chrome removed support for HTTP/2 Server Push in January 2023.
  • QUIC (HTTP/3): HTTP/3 replaces TCP with QUIC (UDP-based). Per Cloudflare benchmarks, HTTP/3 reduces LCP by ~20% at the 75th percentile on high-latency connections. Method semantics: unchanged.

Design your API around RFC 9110 method semantics. Transport version is a deployment and infrastructure concern, not an API design concern.

Frequently Asked Questions

What is the difference between PUT and PATCH?

PUT replaces the entire resource representation with the request body — all fields not included are lost. PATCH applies a partial modification, changing only the fields in the request. PUT is idempotent by definition; PATCH is not by default. Use PATCH when updating a single field; PUT only when replacing the complete resource.

What does idempotent mean in HTTP?

An idempotent method produces the same server state whether called once or ten times. GET, HEAD, PUT, DELETE, OPTIONS, and TRACE are idempotent. POST is not — calling POST /orders twice creates two orders. Idempotency is critical for retry logic: clients can safely retry idempotent requests after a network timeout without creating duplicates.

Can a GET request have a body?

RFC 9110 does not prohibit a GET body but strongly discourages it. Most HTTP clients, proxies, and servers ignore or strip GET bodies. Elasticsearch's _search API uses GET with a body, breaking many API gateways and tools. Alternatives: query string parameters, switch to POST, or use a custom header.

What HTTP method should I use to delete a resource?

DELETE. It is idempotent — DELETE /users/123 called twice is safe: the first returns 200 or 204, the second 404. Never use GET /users/123/delete, as web crawlers and prefetch mechanisms will trigger it. Never use POST /users/123/delete — that conflates creation semantics with deletion.

What triggers a CORS preflight request?

A CORS preflight OPTIONS request fires when: the method is not GET, HEAD, or POST; custom headers are included (Authorization, X-API-Key, etc.); or Content-Type is not one of the three simple values (application/x-www-form-urlencoded, multipart/form-data, text/plain). The browser sends OPTIONS first, then the actual request only if the server's Access-Control-Allow-* headers permit it.

What status code should POST return on success?

201 Created when POST creates a new resource, with a Location header pointing to the new resource URL. 202 Accepted when processing is asynchronous. 200 OK only if POST performs an action that does not create a resource. Never return 200 with an empty body from a resource-creating POST.

What is the HEAD method used for?

HEAD is identical to GET but the server must not return a response body. Use it to check if a resource exists and read its metadata (Content-Length, ETag, Last-Modified) without downloading the body. Common use cases: checking file size before downloading, validating URL existence, and cache freshness checks.

Test Your API Endpoints

Use BytePane's HTTP status code reference to look up what any status code means and when to use it — organized by method and use case. No sign-up required.

HTTP Status Code Reference

Related Articles