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
| Method | Safe | Idempotent | Cacheable | Request body | Success response body |
|---|---|---|---|---|---|
GET | Yes | Yes | Yes | No defined semantics | Yes |
HEAD | Yes | Yes | Yes | No defined semantics | No |
OPTIONS | Yes | Yes | No | Rare | Optional |
TRACE | Yes | Yes | No | Not allowed | Yes (echo) |
POST | No | No | Only if explicit | Yes | Yes |
PUT | No | Yes | No | Yes | Optional |
PATCH | No | No | No | Yes | Optional |
DELETE | No | Yes | No | No defined semantics | Optional |
CONNECT | No | No | No | No | Tunnel |
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:
| Format | Content-Type | Example body | Idempotent |
|---|---|---|---|
| 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.
| Situation | Use | Why |
|---|---|---|
| Full edit form submitted | PUT | Client holds complete state; retries are safe |
| Toggle one boolean | PATCH | No need to send or risk overwriting other fields |
| Client chooses the identifier | PUT | PUT /docs/my-slug creates or replaces at a known URL |
| Server generates the identifier | POST | Client cannot name a URL it does not know yet |
| Append to a collection field | PATCH (JSON Patch) | Merge patch cannot express "append" |
| Concurrent editors | Either, with If-Match | An 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.