HTTP Methods and Status Codes
Pick the right method and the right status code, every time. Learn the safe and idempotent properties that clients and proxies rely on, then use the decision helper to choose a status code for any situation.
🎯 What you'll learn
- The HTTP methods and their meaning
- Safe and idempotent: why they matter for retries and caching
- Request/response examples per method
- Status code categories and the codes every API needs
📚 Before you start
1The HTTP Methods
HTTP defines a fixed vocabulary of methods (also called "verbs"). Each one carries well-known semantics that clients, servers, proxies, and caches all agree on — this shared meaning is exactly what "self-descriptive messages" (from the REST Fundamentals topic) relies on.
| Method | Purpose | Has a request body? | Typical success status |
|---|---|---|---|
GET | Retrieve a representation of a resource | No | 200 OK |
HEAD | Same as GET, but headers only, no body | No | 200 OK |
POST | Create a resource, or trigger a non-idempotent action | Yes | 201 Created |
PUT | Replace a resource entirely with the given representation | Yes | 200 OK / 204 No Content |
PATCH | Partially modify a resource | Yes | 200 OK |
DELETE | Remove a resource | Usually no | 204 No Content |
OPTIONS | Discover what methods/headers are allowed on a resource | No | 204 No Content |
TRACE | Diagnostic loopback of the request (rarely used, often disabled) | No | 200 OK |
CONNECT | Establish a tunnel (used for HTTPS proxying) | No | 200 OK |
2Safe and Idempotent — the Two Properties That Matter Most
Two properties, formally defined by the HTTP specification (RFC 9110), determine how a method may be treated by caches, proxies, retry logic, and browsers:
- Safe — the method does not (and is not expected to) change server state. A safe request can be issued speculatively (link prefetching, crawler indexing) without side effects.
- Idempotent — issuing the same request N times has the same effect on server state as issuing it once. This is about effect, not about the response being identical every time.
The full Idempotency topic covers how servers actually implement idempotency guarantees for non-naturally-idempotent methods like POST. This topic only covers what the spec itself defines.
3Request/Response Examples per Method
POST /orders HTTP/1.1
Content-Type: application/json
{ "customerId": 42, "items": [{ "sku": "SKU-1", "qty": 1 }] }HTTP/1.1 201 Created
Location: /orders/901
Content-Type: application/json
{ "id": 901, "status": "pending" }PUT /orders/901 HTTP/1.1
Content-Type: application/json
{ "customerId": 42, "items": [{ "sku": "SKU-1", "qty": 3 }], "status": "pending" }HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 901, "customerId": 42, "items": [{ "sku": "SKU-1", "qty": 3 }], "status": "pending" }PATCH /orders/901 HTTP/1.1
Content-Type: application/json
{ "status": "cancelled" }HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 901, "customerId": 42, "items": [{ "sku": "SKU-1", "qty": 3 }], "status": "cancelled" }DELETE /orders/901 HTTP/1.1HTTP/1.1 204 No Content4Status Code Categories
| Range | Category | Meaning |
|---|---|---|
| 1xx | Informational | Request received, processing continues (rarely touched directly by API code) |
| 2xx | Success | The request was received, understood, and accepted |
| 3xx | Redirection | Further action needed to complete the request |
| 4xx | Client Error | The request has a problem the client must fix |
| 5xx | Server Error | The server failed to fulfill an otherwise valid request |
5Status Codes Every API Developer Should Know
| Code | Name | When to use it |
|---|---|---|
| 200 | OK | Generic success with a response body (GET, PUT, PATCH) |
| 201 | Created | A new resource was created (POST); include Location header |
| 202 | Accepted | Request accepted for async processing, not yet complete |
| 204 | No Content | Success with no response body (DELETE, some PUT/OPTIONS) |
| 304 | Not Modified | Conditional GET — cached representation is still valid |
| 400 | Bad Request | Malformed request (bad JSON, invalid parameter type) |
| 401 | Unauthorized | Missing or invalid authentication credentials |
| 403 | Forbidden | Authenticated, but not allowed to perform this action |
| 404 | Not Found | No resource at this URI |
| 405 | Method Not Allowed | Resource exists, but doesn't support this method |
| 409 | Conflict | Request conflicts with current server state (e.g. duplicate) |
| 422 | Unprocessable Entity | Well-formed request, but semantically invalid (failed validation) |
| 429 | Too Many Requests | Rate limit exceeded (see the Rate Limiting topic) |
| 500 | Internal Server Error | Unhandled server-side failure |
| 503 | Service Unavailable | Server temporarily can't handle the request (overload, maintenance) |
Under the HoodWhat the Spec Actually Says About Safe/Idempotent▾
RFC 9110 (which obsoletes the older RFC 7231) defines safety and idempotency per method, and it's a fixed, precise table — not a guideline left to interpretation:
| Method | Safe | Idempotent |
|---|---|---|
| GET | ✅ | ✅ |
| HEAD | ✅ | ✅ |
| OPTIONS | ✅ | ✅ |
| TRACE | ✅ | ✅ |
| PUT | ❌ | ✅ |
| DELETE | ❌ | ✅ |
| POST | ❌ | ❌ |
| PATCH | ❌ | ❌ |
| CONNECT | ❌ | ❌ |
Why GET/HEAD/OPTIONS/TRACE are safe: by definition, these methods are read-only requests to the server — the spec says a client "does not request, and does not expect, any state change on the origin server" as a result. This is precisely what lets browsers prefetch links, search engine crawlers index a site, and CDNs cache GET responses without any risk of accidentally deleting or mutating data. It's a contract — if your API performs a side effect (like a "click to unsubscribe" GET link, a classic real-world violation), you break every tool that assumes GET is free of consequences.
Why PUT and DELETE are idempotent but not safe: they change server state (not safe), but repeating the exact same request converges to the exact same end state. PUT /orders/901 with the same body twice leaves the order in the same final shape both times. DELETE /orders/901 twice leaves it deleted both times — the second call may return 404 instead of 204, but the server state (order 901 does not exist) is identical either way. The spec is explicit that the response can differ across idempotent calls; only the resulting state must match.
Why POST is neither safe nor idempotent by spec: POST's defining trait is "process the enclosed representation according to the resource's own semantics" — which could mean "create a new order" (each call creates a different new resource, so repeating it is absolutely not idempotent), or it could mean something else entirely defined by the API. Because the spec cannot know what a given POST endpoint does, it makes no idempotency guarantee. This is exactly the gap the Idempotency topic's Idempotency-Key header pattern exists to close for POST endpoints where safe retries matter (e.g. payments).
Why PATCH is neither safe nor idempotent by spec — even though it often behaves idempotently in practice: PATCH sends a set of changes to apply, not a full replacement. If the patch document says {"status": "cancelled"}, applying it twice is harmless (idempotent in practice). But if the patch document says something relative, like a JSON Patch {"op": "incr", "path": "/stock", "value": -1} (decrement stock by 1), applying it twice halves the stock twice — very much not idempotent. Because PATCH's payload format is open-ended, the spec can't guarantee idempotency the way it can for PUT's "replace wholesale" semantics.
Practical consequence for caches and proxies: intermediaries are allowed to automatically retry safe and idempotent methods on connection failure (a browser silently retrying a timed-out GET is safe to do); they must not automatically retry POST, because a lost response after a successful POST (rather than a lost request) would cause a duplicate resource to be created if blindly retried.
⚠️ Common Pitfalls
- Using GET with side effects — e.g.
/unsubscribe?email=xperforming the unsubscribe on GET. Breaks prefetching, crawler safety, and caching assumptions. - Returning
200 OKfor everything, including errors — putting the real status in the JSON body ({"success": false}) defeats HTTP-aware tooling (caches, monitoring, retries) that rely on the actual status line. - Using
POSTfor updates that should bePUT/PATCH— makes the API harder to reason about and loses the idempotency guarantee PUT would have given clients for free. - Returning
204 No Contentwith a non-empty body — a204response must have no body by spec; some HTTP clients will error or silently drop it. - Conflating
401and403—401means "who are you? credentials missing/invalid,"403means "I know who you are, and you're not allowed." Returning403for missing auth leaks less information in some designs, but mixing them up inconsistently confuses API consumers either way. - Assuming PATCH is always idempotent — as shown in section 6, only PUT is idempotent by spec; PATCH depends entirely on the semantics of the patch document you define.
📋 Quick Reference
Safe + idempotent: GET, HEAD, OPTIONS, TRACE
Idempotent, not safe: PUT, DELETE
Neither: POST, PATCH, CONNECT
201 Created -> POST that created a resource (+ Location header)
204 No Content -> success, no body (DELETE, sometimes PUT)
400 Bad Request -> malformed syntax
401 Unauthorized -> missing/invalid credentials
403 Forbidden -> authenticated but not allowed
404 Not Found -> no such resource
409 Conflict -> request conflicts with current state
422 Unprocessable Entity -> well-formed but semantically invalid
429 Too Many Requests -> rate limited
5xx -> server's fault, not the client's🧠 Key takeaways
- GET/HEAD/OPTIONS are safe; PUT/DELETE idempotent; POST/PATCH neither by default.
- Safe → cacheable and prefetchable; idempotent → safe to retry automatically.
- 2xx success, 3xx redirect, 4xx client error, 5xx server error. Never
200with an error body. - Know 200, 201, 204, 400, 401, 403, 404, 409, 422, 429, 500, 503 cold.
🎤 Interview Q&A
Q1When do you use PUT vs PATCH vs POST?core▾
POST: create a resource where the server assigns the ID, or trigger an action (not idempotent). PUT: replace a resource entirely at a known URI (idempotent; can create if client chooses the ID). PATCH: partial update (JSON Merge Patch or JSON Patch), not necessarily idempotent.
- Is PATCH idempotent? Merge patches setting fields are; operations like 'append' aren't.
Q2Which status codes should a create endpoint return?core▾
201 Created with a Location header (and usually the resource body); 202 Accepted if processing is asynchronous; 400/422 for validation errors; 409 Conflict for duplicates; 401/403 for auth issues.
- 400 vs 422? 400 for malformed requests; 422 for well-formed requests failing validation (conventions vary).
Q3What should DELETE return?core▾
204 No Content on success (or 200 with a body), and either 204 or 404 on repeated deletes; both keep the operation idempotent in effect. Use 409 if deletion is blocked by dependent data.
- Soft delete? Still return 204; the resource no longer appears to clients.
Q4When do you return 409 vs 412 vs 429?senior▾
409 Conflict: request conflicts with current state (duplicate, invalid state transition). 412 Precondition Failed: If-Match ETag didn't match (optimistic concurrency). 429 Too Many Requests: rate limited, with Retry-After.
- Which for 'order already shipped' on cancel? 409.
Q5Why is returning 200 with an error body a bad practice?core▾
Clients, proxies, monitoring and retries rely on status codes; hiding errors in 200 responses breaks caching, alerting and client error handling.
- Exception? GraphQL commonly returns 200 with an
errorsarray by convention.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.