HTTP methods

Every HTTP request starts with a method — the verb that tells the server what you want done. This is a complete reference to the nine standard methods, the three properties that classify them (safe, idempotent, cacheable), and the mistakes that cause most real-world API bugs. You can send any of these from the online API tester as you read.

The three properties that matter

Before the individual methods, learn the three adjectives the HTTP specification uses to classify them. They are not trivia — they decide whether a client library may retry your request automatically, whether a CDN may serve a cached copy, and whether a search engine crawler is allowed to follow a link.

Safe

A method is safe if it is read-only: sending it is not intended to change server state. GET, HEAD, OPTIONS and TRACE are safe. This is a promise about intent, not a guarantee — a safe request can still write a log line or increment a counter. What matters is that the client is not asking for a change, so automated agents feel free to send safe requests without asking permission. Browser prefetchers, link previewers and crawlers all rely on this. Wire a GET /orders/42/cancel endpoint and something will eventually cancel orders for you.

Idempotent

A method is idempotent if sending the identical request N times leaves the server in the same state as sending it once. All safe methods are idempotent, plus PUT and DELETE. Note carefully: idempotence is about server state, not about the response. DELETE /users/7 may return 204 the first time and 404 the second, and it is still perfectly idempotent — the user is gone either way.

This is the property that lets a client retry safely. When a request times out you genuinely do not know whether the server processed it. If the method is idempotent you can just send it again. If it is not — a POST /payments, say — a blind retry may charge the card twice, which is why payment APIs ask for an Idempotency-Key header to bolt idempotence onto POST manually.

Cacheable

A response is cacheable if a cache is allowed to store it and reuse it for a later request. GET and HEAD responses are cacheable by default when the status code is one of the heuristically cacheable ones. POST responses are cacheable only when they carry explicit freshness information such as Cache-Control: max-age, and in practice almost no cache implements it. Everything else — PUT, PATCH, DELETE, OPTIONS, TRACE, CONNECT — is not cacheable.

Comparison table

MethodSafeIdempotentCacheableRequest bodySuccess response body
GETYesYesYesNo defined semanticsYes
HEADYesYesYesNo defined semanticsNo
OPTIONSYesYesNoRareOptional
TRACEYesYesNoNot allowedYes (echo)
POSTNoNoOnly if explicitYesYes
PUTNoYesNoYesOptional
PATCHNoNoNoYesOptional
DELETENoYesNoNo defined semanticsOptional
CONNECTNoNoNoNoTunnel

Source: RFC 9110 §9, which replaced RFC 7231, plus RFC 5789 for PATCH.

The methods, one by one

GET — retrieve a representation

GET asks the server for the current representation of a resource. It is the most common method on the web and the one everything else is optimised around: caches store it, CDNs replicate it, browsers prefetch it and crawlers follow it. Inputs go in the path and query string, as in GET /v1/orders?status=open&limit=50.

A GET can technically carry a body, but RFC 9110 gives that body no meaning, and real infrastructure treats it badly — some proxies strip it, some servers return 400, and fetch() in the browser refuses outright. If your query is genuinely too large for a URL, the usual escape hatch is a POST to a /search sub-resource. Conditional GETs using If-None-Match or If-Modified-Since are how you avoid re-downloading unchanged data; see the HTTP headers guide for how those pair with ETag.

POST — create, or do something that has no other verb

POST submits data for the server to process according to the resource's own semantics. In REST APIs it usually means "create a subordinate resource": POST /v1/orders with a JSON body creates an order and should return 201 Created with a Location header pointing at the new resource.

It is also the deliberate catch-all. Actions that are not really CRUD — POST /v1/emails/42/send, POST /v1/jobs/7/retry — belong on POST precisely because it promises nothing about safety or idempotence. Because it is not idempotent, a client that times out cannot simply retry, so any endpoint that costs money or sends messages should accept a client-supplied idempotency key and return the original result on a repeat.

PUT — replace the whole resource

PUT tells the server: make the resource at this URL look exactly like the representation I am sending. It is a full replacement. If the stored order has status, total and notes, and you PUT a body containing only status and total, a spec-compliant server clears notes. That is the single most common source of accidental data loss in API clients.

Because the client supplies the complete target state, PUT is idempotent — sending it five times lands on the same state as sending it once. It can also create: if the client controls the identifier, as with PUT /v1/documents/my-slug, the server may create the resource and return 201, or return 200 / 204 when it updated an existing one.

PATCH — apply a partial modification

PATCH, defined separately in RFC 5789, sends a description of changes rather than a new representation. The body is a patch document, and its format is declared by Content-Type. Two are standardised:

FormatContent-TypeExample bodyIdempotent
JSON Merge Patch (RFC 7386) application/merge-patch+json {"status":"paid","notes":null} Yes
JSON Patch (RFC 6902) application/json-patch+json [{"op":"add","path":"/tags/-","value":"vip"}] No

In merge patch, a field set to null means "delete this field", which is why merge patch cannot set a field to a literal null. JSON Patch is more expressive — it has add, remove, replace, move, copy and test operations — and that expressiveness is exactly why it is not idempotent: appending to an array twice appends twice. Many APIs ignore both standards and accept a plain application/json object of changed fields; that works fine, it is just not interoperable, so read the docs before assuming.

DELETE — remove the resource

DELETE requests that the mapping between the URL and its resource be removed. It is idempotent: after the first successful call the resource is gone, and calling again cannot make it more gone. Typical responses are 204 No Content when the deletion is complete and there is nothing to say, 200 when you return the deleted representation, and 202 Accepted when deletion is queued for later.

A body on DELETE has no defined semantics, same as GET. Some clients and intermediaries drop it silently, so do not design an API that requires one.

HEAD — the headers of a GET, without the body

HEAD is identical to GET except the server must not return a body. The headers must be the same ones a GET would produce, including Content-Length — which describes the body you would have received. That makes HEAD useful for checking whether a large file exists, reading its size, or validating an ETag before committing to the download. It is also a cheap liveness probe. Send one from the REST API tester and you will see a full set of response headers with an empty body pane.

OPTIONS — discover what is allowed

OPTIONS asks about the communication options for a resource. A server should answer with an Allow header listing supported methods, for example Allow: GET, PUT, DELETE, OPTIONS. The special request target * (OPTIONS * HTTP/1.1) asks about the server as a whole rather than any one resource.

Its everyday role is the CORS preflight. Before a browser sends a cross-origin request that is not simple — anything with a custom header, or a method other than GET, HEAD or POST — it first sends an OPTIONS request carrying Access-Control-Request-Method and Access-Control-Request-Headers, and only proceeds if the response grants permission. If your browser console reports a CORS failure on a PUT, the preflight is where to look.

TRACE — echo the request back

TRACE performs a loopback test: the final server echoes the request it received back to you as the message body, with Content-Type: message/http. In principle this reveals what proxies along the path did to your request. In practice it is disabled almost everywhere, because combining it with a browser vulnerability enabled Cross-Site Tracing (XST), an attack that could read cookies marked HttpOnly. Expect 405 or 501, and treat a working TRACE on a production server as a finding to report.

CONNECT — establish a tunnel

CONNECT asks a proxy to open a TCP tunnel to the destination, after which bytes flow through unchanged. It is how an HTTPS request travels through a forward proxy: the client sends CONNECT example.com:443 HTTP/1.1, the proxy answers 200, and the TLS handshake then happens end-to-end inside the tunnel. It is a proxy-level mechanism, not something you use against an application API, and testing tools generally cannot send it meaningfully.

PUT vs PATCH: how to choose

The decision comes down to who owns the complete state. If your client already holds the full resource — it fetched it, the user edited a form, and now it is sending everything back — PUT is the honest choice, and it gives you idempotent retries for free. If your client only knows about one field, use PATCH rather than fetching the resource, mutating a copy and putting it back, because that read-modify-write cycle silently clobbers concurrent changes made between your read and your write.

SituationUseWhy
Full edit form submittedPUTClient holds complete state; retries are safe
Toggle one booleanPATCHNo need to send or risk overwriting other fields
Client chooses the identifierPUTPUT /docs/my-slug creates or replaces at a known URL
Server generates the identifierPOSTClient cannot name a URL it does not know yet
Append to a collection fieldPATCH (JSON Patch)Merge patch cannot express "append"
Concurrent editorsEither, with If-MatchAn ETag precondition turns a lost update into 412

Whichever you pick, guard writes with optimistic concurrency where it matters: take the ETag from your GET, send it back as If-Match, and the server rejects the write with 412 Precondition Failed if someone else changed the resource in the meantime.

Common mistakes

Using GET for operations that change state

The classic is GET /admin/users/9/delete. It looks convenient because you can put it in a link, and that is precisely the problem: link prefetchers, antivirus scanners, chat unfurlers and crawlers all follow GETs without being asked. It also makes the action trivially CSRF-able. State changes belong on POST, PUT, PATCH or DELETE.

Treating PATCH as a smaller PUT

Sending a partial document with the PUT method — or a full document with PATCH — works against lenient servers and breaks against strict ones. Worse, it teaches your team that omitted fields are ignored, until the day a compliant server wipes them.

Confusing idempotent with "returns the same response"

A second DELETE returning 404 does not break idempotence, and neither does a PUT returning 200 after an initial 201. The rule is about the resulting server state. Conversely, an endpoint that returns the same body every time can still be non-idempotent if it appends a row on each call.

Returning 200 with an error inside the body

200 OK with {"success": false} defeats every generic layer between you and the client — caches, retry middleware, monitoring, alerting. Use the status code as the machine-readable outcome: 400 for a malformed request, 401 when authentication is missing or bad, 403 when the caller is known but not permitted, 422 for semantically invalid content, and 409 for a conflict with current state.

Answering an unsupported method with 404

If /v1/orders/42 exists but does not accept DELETE, the correct answer is 405 Method Not Allowed, and RFC 9110 requires an Allow header listing what is supported. Returning 404 instead sends the client hunting for a typo in a URL that was fine. Use 501 Not Implemented only when the server does not recognise the method at all.

Tunnelling everything through POST

An API where every call is POST /api with an action field in the body throws away caching, idempotent retries, method-level authorisation and every piece of HTTP tooling that reads the method. Sometimes that trade is deliberate — GraphQL makes it consciously, which is why the GraphQL tester exists as a separate tool — but it should be a decision, not an accident.

Assuming the method survives a redirect

Historically, clients converted POST to GET when following a 301 or 302, and 303 mandates that conversion. If you need the method and body preserved, the server must send 307 or 308. A POST that mysteriously arrives as a bodyless GET is almost always a 301 from an http to https or a trailing-slash rewrite.

Try each method yourself

The fastest way to make this stick is to send the requests. Open the API tester and work through a few: GET https://api.github.com/users/octocat to see a normal read, the same URL with HEAD to see identical headers and no body, then POST https://httpbin.org/post with a JSON body to watch the request echoed back. Try PUT and PATCH against https://httpbin.org/put and https://httpbin.org/patch and compare what each reports receiving.

When something comes back wrong, the response headers usually explain it — the HTTP headers reference covers what to look at. For structuring a full set of checks around an endpoint rather than poking at it, read the REST API testing guide. And when a status code you have never seen shows up, the HTTP status code reference has all of them.

Frequently asked questions

What is the difference between PUT and PATCH?

PUT replaces the entire resource with what you send, so omitted fields are removed or reset. PATCH applies a partial modification, so you send only what changes. Use PUT when the client holds complete resource state, PATCH when changing one or two attributes.

Which HTTP methods are idempotent?

GET, HEAD, OPTIONS, TRACE, PUT and DELETE. POST, PATCH and CONNECT are not, in the general case.

Is PATCH idempotent?

Not by definition. RFC 5789 defines PATCH as neither safe nor idempotent because a patch document can describe a relative change. A merge patch setting fixed values happens to be idempotent; a JSON Patch appending to an array is not.

Can a GET request have a body?

It can carry one, but the body has no defined semantics and much of the ecosystem strips or rejects it. Use the query string, or POST to a search sub-resource when the input is too large.

Which status code means the method is not supported?

405 Method Not Allowed when the resource exists but rejects that method — with a required Allow header — and 501 Not Implemented when the server does not recognise the method at all.

Are custom methods allowed?

Yes. The method token is extensible and case-sensitive, and WebDAV registered several (PROPFIND, MKCOL, LOCK). But intermediaries frequently block unknown methods, so custom verbs rarely survive the open internet.