Every POST /search endpoint you have ever written is a read pretending to be a write.
You did not do it because you wanted to. You did it because the alternatives were worse. Your search has a dozen filters, some of them nested, a few of them arrays, and there is no sane way to stuff all of that into a URL. So you reached for POST, put the filter in the body, and moved on. It works.
But you also lied to every proxy, cache, and retry layer between the client and your server. You told them this request might change state. It does not. It is a safe, idempotent read, and you dressed it up as a mutation because the protocol gave you no better option.
As of late 2025, the protocol gives you a better option. It is called QUERY.
A new HTTP method, which almost never happens
HTTP methods do not change often. Most developers carry a mental list that has not moved in their entire career: GET, POST, PUT, PATCH, DELETE, plus HEAD and OPTIONS if you are being thorough. PATCH was the last meaningful addition, standardized as RFC 5789 in 2010.
QUERY is the next one. The IESG approved "The HTTP QUERY Method" as a Proposed Standard on 2025-11-20, published as RFC 10008. It started life as the IETF draft with the very on-the-nose name draft-ietf-httpbis-safe-method-w-body: a safe method, with a body. That name is the whole idea.
If you are wondering why you are reading about a late-2025 RFC in the middle of 2026: standards land long before tooling does. The draft churned from 2021 to 2025, the RFC is only months old, and servers, proxies, and client libraries are just now starting to react. The spec is the starting gun, not the finish line, which is exactly why the conversation is happening now rather than the day it was published.
One more point of confusion worth clearing up: this is a single new method, QUERY, and it is not the same as SEARCH. WebDAV defined a SEARCH verb back in 2008 (RFC 5323), built around a generic XML body and a pile of WebDAV semantics most people never wanted. The early drafts of this very spec were actually titled "HTTP SEARCH Method," then the working group renamed it to QUERY to escape that WebDAV baggage and to better signal its relationship to the URI's query component. So QUERY is the modern, format-agnostic successor to SEARCH, not a second new verb alongside it.
QUERY is exactly what it sounds like once you frame it right: GET's guarantees with POST's request body. It is safe and idempotent, but it is allowed to carry content, and the content plus its Content-Type defines the query.
The search-endpoint dilemma
To see why this matters, look at the three options you have today for a non-trivial search endpoint. All three are compromises.
Option 1: GET with a giant query string
GET /contacts?select=surname,givenname,email&filter[country]=UA&filter[tags][]=vip&filter[tags][]=lead&sort=-createdAt&limit=10 HTTP/1.1
This is semantically perfect. GET is safe, idempotent, and cacheable, which is exactly what a search is. It works beautifully right up until the query gets complicated.
Then the problems start. URLs have practical length limits, usually somewhere between 2KB and 8KB depending on the browser, proxy, and server in the chain, and a rich filter blows past them faster than you would expect. There is no standard way to represent nested objects or arrays in a query string, so every framework invents its own filter[tags][] dialect. Everything is percent-encoded into noise. And those URLs land in access logs and browser history, filters and all.
Option 2: GET with a request body
You might think: fine, keep the GET semantics, just move the filter into the body. The spec has historically said a GET body has "no defined semantics," and in practice that is a minefield. Some proxies and servers reject the request. Some silently drop the body before your handler ever sees it. Some pass it through. You cannot rely on any of it. This is the one option that is simply not safe to ship.
Option 3: POST /search
So you do what everyone does.
POST /contacts/search HTTP/1.1
Content-Type: application/json
{ "country": "UA", "tags": ["vip", "lead"], "limit": 10 }
This works everywhere. The body holds the full filter with real JSON structure. No URL limits, no encoding mess.
The cost is semantic. POST is defined as neither safe nor idempotent. That has real consequences beyond pedantry:
- Retries are off the table. A proxy, gateway, or HTTP client that will happily retry a failed GET will refuse to retry a POST, because retrying a POST might create a second resource. Your read, which is perfectly safe to repeat, is treated as dangerous.
- Caching basically does not happen. POST responses are cacheable only under narrow, rarely-implemented conditions. In practice your search results are uncacheable at every layer.
- Intermediaries lose the plot. Every proxy, WAF, and observability tool in the path sees a POST and assumes a write. None of them can reason about your read as a read.
You have a safe idempotent operation that the entire HTTP stack treats as a mutation. That is the lie.
How QUERY fixes it
QUERY gives you the POST body without the POST semantics. Here is the canonical shape, straight out of the RFC:
QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Accept: application/json
select=surname,givenname,email&limit=10
HTTP/1.1 200 OK
Content-Type: application/json
[
{ "surname": "Smith", "givenname": "John", "email": "smith@example.org" }
]
The body can be anything with a media type. Form-encoded, JSON, a GraphQL document, your own filter DSL. The Content-Type tells the server how to parse it, and a 200 OK returns the results in the response body. Because the method is declared safe and idempotent, every caching and retry mechanism that works for GET is allowed to work here too.
The RFC is also strict about error signaling, which makes QUERY endpoints pleasant to integrate against:
- Missing
Content-Type:400 Bad Request - Unsupported media type:
415 Unsupported Media Type - Body inconsistent with its declared
Content-Type:400 Bad Request - Syntactically valid query that fails semantically:
422 Unprocessable Content - Cannot produce the format the client asked for in
Accept:406 Not Acceptable
The part people miss: Content-Location
QUERY is more than "POST that promises to behave." The detail that makes it interesting is the Content-Location response header.
A QUERY response can include Content-Location pointing at a URI that represents the results of that operation. The client can then issue a plain GET against that URI to retrieve the same results again. So a single QUERY can hand you back a cacheable, bookmarkable, shareable GET URL for a result set that was too complex to express as a GET in the first place. The expensive structured filter goes over QUERY once; the cheap stable link works as an ordinary GET afterward.
And QUERY responses are genuinely cacheable, with one rule that explains the whole design: the cache key must incorporate the request body, not just the method and URL. Two QUERY requests to the same path with different bodies are different queries with different results. This is also precisely why QUERY had to be a new method rather than a blessing of "GET with a body." The entire HTTP caching model keys on method plus URL. Teaching caches to also key on the body is a real semantic change, and it needed a verb of its own to hang that behavior on safely.
The honest trade-off
Here is the part where I stop selling it.
You should not migrate anything to QUERY tomorrow. The standard is months old, and adoption is the bottleneck. Origin servers, reverse proxies, CDNs, API gateways, browser fetch stacks, and HTTP client libraries largely do not understand QUERY yet. A browser fetch() will let you send an arbitrary method string, but you have no guarantee the proxies and load balancers between you and the origin will pass it through instead of choking on a verb they do not recognize.
This is not hypothetical pessimism, it is just how HTTP adoption works. PATCH was standardized in 2010 and still took years before you could assume a random framework and gateway handled it without special-casing. QUERY is at the very start of that same curve.
So the pragmatic position for production in 2026 is unchanged: POST /search is still the right default. It works on every piece of infrastructure you will ever deploy behind.
What changes is how you think about that endpoint. You now know it is a workaround, not the correct expression of what you are doing. QUERY is the thing to learn now, prototype on internal services where you control the whole path, and reach for the moment your stack and your proxies actually support it. The win it offers is not a feature you are currently missing. It is semantic honesty: a way to finally tell the HTTP stack the truth about an operation that was always a safe, idempotent read.
The takeaway
QUERY is GET's guarantees plus POST's body, and it exists because every backend developer independently arrived at the same hack and the standard finally caught up to it. Your search endpoints were never really writes. Now there is a verb that agrees with you, even if the rest of the internet needs a few years to catch up.
What do you reach for on a heavy search endpoint today: POST /search, a sprawling query string, GraphQL, something else? And once your gateways and clients support it, would you actually switch to QUERY, or is POST /search too entrenched to bother? Real-world setups beat theory here.
Top comments (0)