The Verbs That Move the Web
A bug report lands in your queue: "Updating a user's email sometimes wipes their phone number." You pull the logs. Sure enough, the frontend is using PUT when it should be using PATCH. Classic HTTP-method confusion — and expensive: hours of debugging, a hotfix, a shipped regression, and a customer-support ticket.
HTTP methods (also called verbs) are the action part of every HTTP request. They tell the server what to do with the resource at the URL. Get them right and your API is intuitive, cacheable, retryable, and safe. Get them wrong and you build subtle data-loss bugs, break browser caching, and fight your own CDN.
There are nine methods defined by RFC 9110: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, and PATCH (PATCH is defined separately in RFC 5789). Most APIs use five or six. This guide covers all of them: what each does, whether it is safe, whether it is idempotent, when to use it, when not to, with real code in curl, JavaScript fetch, Python requests, and Node.js Express.
What Is an HTTP Method?
An HTTP method is the first token of the request line. It indicates the desired action on the resource identified by the request URI. A minimal HTTP request looks like this:
GET /users/42 HTTP/1.1 Host: api.example.com Accept: application/json
The method is GET. The path is /users/42. The protocol is HTTP/1.1. Together, "GET the resource at /users/42" is a complete instruction.
Methods have two critical properties defined by RFC 9110:
- Safety: a method is safe if it does not modify server state. GET, HEAD, OPTIONS, and TRACE are safe. - Idempotency: a method is idempotent if making the same request N times produces the same result as making it once. GET, HEAD, OPTIONS, TRACE, PUT, and DELETE are idempotent. POST and PATCH are not (in general).
All safe methods are idempotent, but not all idempotent methods are safe — PUT and DELETE change state but are repeatable without further change.
These properties drive everything: whether a browser can prefetch a URL, whether a CDN can cache it, whether a retry library can automatically retry on timeout, and whether a proxy can replay a request.
GET — Retrieve a Resource
GET is the workhorse. It retrieves a representation of the resource at the URL. Safe and idempotent.
Use GET for reads: fetching a user, listing orders, searching products. Never for mutations — even if you really want to delete something via a URL someone can paste into a browser, don't. Google's web accelerator famously deleted user data in the mid-2000s by prefetching every "delete" link it could find on a page.
curl https://api.example.com/users/42
const res = await fetch("/users/42"); const user = await res.json();
r = requests.get("https://api.example.com/users/42")
GET requests should not have a body. Some servers and proxies drop them. Put filters in query strings: /users?status=active&role=admin.
Cachability: GET is cacheable by default. Use Cache-Control, ETag, and Last-Modified to control browser, proxy, and CDN caching. A conditional GET with If-None-Match can return 304 Not Modified and save enormous bandwidth. This is why sites like Wikipedia can serve billions of GETs on a modest origin.
POST — Create or Perform an Action
POST submits data to the server, typically creating a new resource. Not safe, not idempotent.
Use POST for creation (POST /users), non-idempotent actions (POST /charges/42/refund, POST /emails/send), and operations that do not map cleanly to another verb.
curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"email":"jane@example.com"}'
Successful creation returns 201 Created with a Location header pointing to the new resource. For async operations return 202 Accepted.
Because POST is not idempotent, retries can create duplicates. The solution is idempotency keys: the client sends a unique key in an Idempotency-Key header, and the server deduplicates within a window. Stripe requires this on every mutating POST:
curl -X POST https://api.stripe.com/v1/charges \ -H "Idempotency-Key: a7f3e2b1-4c5d-4e9f-b2a8-1d3e5f7a9c2b" \ -H "Authorization: Bearer sk_test_..." \ -d amount=2000 -d currency=usd
Retries with the same key return the original result. Without it, a timeout that triggers a client retry can double-charge your customer.
PUT — Replace a Resource
PUT fully replaces the resource at the URL. Idempotent but not safe.
The semantics are important: PUT means "make the resource at this URL exactly this." If you PUT {"email":"new@example.com"} to /users/42, fields you omit (like phone) may be reset to defaults or nulled out. This is the bug from our opening scenario.
curl -X PUT https://api.example.com/users/42 \ -H "Content-Type: application/json" \ -d '{"email":"new@example.com","name":"Jane","phone":"555-1234"}'
Because PUT is idempotent, sending it twice produces the same state — handy for retries. PUT can also create a resource if the client chooses the URL (PUT /users/jane creates the user if missing), though POST-for-create is more common.
Use PUT when the client has the full new state and wants to replace the resource atomically. Use PATCH when the client wants to change only specific fields.
PATCH — Partially Update a Resource
PATCH applies a partial modification. Not guaranteed idempotent. Defined in RFC 5789.
PATCH is the right verb when you want to change one field and leave the rest alone:
curl -X PATCH https://api.example.com/users/42 \ -H "Content-Type: application/merge-patch+json" \ -d '{"email":"new@example.com"}'
There are two common PATCH formats. RFC 7396 JSON Merge Patch (content type application/merge-patch+json) is a simple object where keys overwrite and null deletes. RFC 6902 JSON Patch (application/json-patch+json) is a sequence of operations (add, remove, replace, move, copy, test) and is strictly more powerful.
PATCH can be made idempotent with appropriate body design (merge-patch is), but operations like "increment counter" break idempotency. When in doubt, pair PATCH with If-Match: <etag> for optimistic concurrency.
PUT vs PATCH vs POST cheat sheet:
- Replace the whole resource? PUT. - Change a few fields? PATCH. - Create or perform an action? POST.
DELETE, HEAD, OPTIONS
DELETE removes the resource. Idempotent but not safe.
curl -X DELETE https://api.example.com/users/42
Return 204 No Content on success, or 200 with the deleted resource if useful. Return 404 or 204 on subsequent calls — both are idempotent-consistent. Some APIs soft-delete (set deleted_at) rather than hard-delete to preserve audit logs.
HEAD returns headers only — no body. Safe and idempotent. Used to check whether a resource exists, to read its Content-Length or Last-Modified without downloading, or to test a URL cheaply.
curl -I https://example.com/video.mp4
Download managers HEAD to determine file size before issuing Range GETs.
OPTIONS returns the methods and features a server supports. Safe and idempotent.
curl -X OPTIONS https://api.example.com/users
HTTP/1.1 204 No Content Allow: GET, POST, HEAD, OPTIONS
The biggest real-world use is CORS preflight: browsers send OPTIONS before a cross-origin request with non-simple methods or headers, and the server answers with Access-Control-Allow-* headers. For deep CORS coverage see /blog/cors-explained.
CONNECT and TRACE
These two are rare but worth knowing.
CONNECT establishes a tunnel to the server, typically used for HTTPS traffic through an HTTP proxy. When your browser is behind a corporate proxy, it sends CONNECT api.example.com:443 HTTP/1.1 to the proxy, which then forwards encrypted bytes blindly. You will not implement CONNECT in application code — it is a proxy concern.
TRACE performs a message loop-back test. The server echoes the received request back, letting clients see what proxies modified along the way. TRACE is disabled on almost every production server because it can leak cookies and auth headers via cross-site tracing attacks (XST). If you see it enabled in a pentest, disable it in your web server config.
Neither CONNECT nor TRACE should appear in an application-level API design. Mentioning them completes the picture of RFC 9110.
Safety and Idempotency Reference
The full property matrix:
Method — Safe • Idempotent • Cacheable • Has body GET — yes • yes • yes • no HEAD — yes • yes • yes • no OPTIONS — yes • yes • no • optional TRACE — yes • yes • no • no POST — no • no • sometimes (with explicit headers) • yes PUT — no • yes • no • yes PATCH — no • usually • no • yes DELETE — no • yes • no • optional CONNECT — no • no • no • no
Why this matters:
- Retry policies — auto-retry is safe for idempotent methods on network errors. Never auto-retry POST without an idempotency key. - Caching — CDNs cache GET and HEAD by default; everything else requires explicit opt-in. - Pre-rendering — browsers and crawlers feel free to issue GETs, so GET must be safe. - Load-balancer health checks typically use GET or HEAD for exactly this reason.
Common Mistakes with HTTP Methods
Using GET for mutations. "GET /deleteUser?id=42" looks convenient until a browser prefetcher or a link-checker issues it. Delete by using DELETE, period.
Using POST for everything. An API where every endpoint is POST ("because PUT/DELETE is hard") loses caching, retry safety, and verb-based routing. Legacy SOAP APIs did this; do not inherit the habit.
PUT that behaves like PATCH. If you accept a partial body on PUT and merge it, clients will eventually send a body missing a field and be surprised when it stays. Pick one semantics and document it.
Forgetting idempotency keys on POST. Payments, email sends, and SMS all require idempotency. A retry after a timeout will otherwise cost money or spam customers.
Returning the wrong status code. 200 for a successful DELETE with no body is fine but 204 is more idiomatic. See /blog/http-status-codes-guide for a full guide.
Blocking OPTIONS. Disabling OPTIONS breaks CORS preflight for every browser client. Allow it and return the correct CORS headers.
Allowing TRACE in production. It can leak credentials via XST. Disable it at the web server level.
Testing HTTP Methods
You can exercise every method with curl:
curl -X GET https://api.example.com/users/42 curl -X POST https://api.example.com/users -d '{"email":"a@b.com"}' curl -X PUT https://api.example.com/users/42 -d '{...}' curl -X PATCH https://api.example.com/users/42 -d '{...}' curl -X DELETE https://api.example.com/users/42 curl -X HEAD https://api.example.com/users/42 curl -X OPTIONS https://api.example.com/users
Or fetch() in JavaScript:
await fetch("/users/42", { method: "PATCH", headers: {"Content-Type": "application/merge-patch+json"}, body: JSON.stringify({email: "new@example.com"}) });
Or requests in Python:
import requests r = requests.patch(url, json={"email": "new@example.com"})
For interactive exploration, the StringTools API Client at /api-client supports all nine methods in a browser UI with status-code coloring, header inspection, and saved collections.
Frequently Asked Questions
What is the difference between PUT and PATCH? PUT fully replaces the resource; PATCH partially updates it. PUT /users/42 with {"email":"new"} may null out every other field. PATCH /users/42 with {"email":"new"} only changes the email. PUT is always idempotent; PATCH usually is but not guaranteed. When in doubt for updates, use PATCH with RFC 7396 merge-patch.
Can GET have a request body? Technically yes (RFC 9110 no longer forbids it), but practically no. Many servers, proxies, and client libraries drop or ignore GET bodies. Elasticsearch allowed GET with body historically and ran into interop problems. Put filters in query strings. If you truly need a body, use POST.
Is POST always non-idempotent? By default, yes — POST /charges creates a new charge each time. But you can make POST effectively idempotent by accepting an Idempotency-Key header and deduplicating on the server. This is the standard pattern for payment APIs (Stripe, Adyen, Square).
What HTTP method should I use for search? GET with query parameters if the query fits (GET /search?q=nodejs). GET is cacheable and bookmarkable. If the query is too complex for a URL (JSON filter trees, hundreds of parameters), use POST /search — you lose cacheability but keep expressivity.
Why do browsers send OPTIONS before my POST? That is a CORS preflight request. Browsers send OPTIONS automatically when a cross-origin request uses a non-simple method (anything other than GET, HEAD, simple POST) or non-simple headers. The server must respond with appropriate Access-Control-Allow-* headers. See /blog/cors-explained for the full flow.
Is DELETE actually idempotent if it returns 404 on the second call? Yes. Idempotency means the state is the same after N calls as after 1. If DELETE removes the resource and subsequent calls return 404, the state (resource not present) is unchanged. Some APIs return 204 on every call instead; both are correct.
Which methods do form HTML tags support? Only GET and POST natively. HTML forms cannot issue PUT, PATCH, or DELETE directly. Frameworks simulate this with a hidden _method field and server-side interpretation (Rails, Laravel). Single-page apps do not have this limitation because they use fetch or XHR.
Conclusion
HTTP methods are the grammar of the web. GET reads. POST creates. PUT replaces. PATCH updates. DELETE deletes. HEAD peeks. OPTIONS discovers. That is 95% of what you need, and getting it right makes your API work with every tool, cache, and retry library on the internet.
Test your methods live. The StringTools API Client at /api-client supports all nine methods, idempotency keys, custom headers, and saved collections — no sign-up, runs in your browser. Pair it with /blog/http-status-codes-guide and /blog/what-is-rest-api for the full picture.
The verb is the contract. Choose deliberately.
Related Tools and Reading
Try the StringTools API Client at /api-client to send real requests with any HTTP method.
Related reading: /blog/what-is-rest-api for how methods fit into REST, /blog/http-status-codes-guide for what the response should say, /blog/cors-explained for why OPTIONS matters, /blog/api-security-best-practices for hardening mutations, and /blog/jwt-tokens-explained for the Bearer tokens you send on every request.