DEV Community

Cover image for POST /search Is a Lie. HTTP Finally Admits It.
Islam Hafez
Islam Hafez

Posted on

POST /search Is a Lie. HTTP Finally Admits It.

For 30 years, every search API on the internet has been telling the protocol one thing and doing another. RFC 10008 just ended that.


Somewhere in your codebase right now, there is a route called POST /search.

It works. It has always worked. And every developer who wrote it knew, at some level, that it was wrong — that using POST for a read-only operation was a small, quiet lie to the protocol. They wrote it anyway because HTTP left them no choice. The only verb that could carry a body was POST, and POST is defined to mean "this operation might change something."

For three decades, every search API, every filter endpoint, every analytics query, every reporting tool on the internet made the same compromise. We picked the verb that worked over the verb that was correct, because the correct verb didn't exist.

On June 15, 2026, after eleven years of drafts, the IETF published RFC 10008. The new method is called QUERY. And the first thing to understand about it is that it isn't really adding something new. It's making the protocol honest about what we were already doing.


Why the Lie Happened in the First Place

HTTP has always had a semantics problem for complex reads.

GET is the right verb for reading data. It's safe (no side effects), idempotent (repeat it a hundred times, same result), and cacheable. Proxies can retry it. CDNs can cache it. Clients can prefetch it. Everything about GET is designed around the assumption that reading data is a safe operation that can be done again without consequence.

The problem is the URL. GET has no body. Your entire request — every filter, every parameter, every field — has to fit in the URI. And nobody actually knows what "fit" means, because the size limit isn't defined by HTTP itself. It depends on the browser, the server, every proxy in between, and every CDN in the path. RFC 9110 recommends servers support at least 8,000 octets. Some support less. Some support more. None of them agree.

For simple queries, that's fine. GET /products?category=laptops&sort=price fits anywhere. But modern APIs don't stay simple:

GET /analytics/reports
  ?filters[]=region:US,EU
  &filters[]=date_range:2026-01-01..2026-06-30
  &filters[]=revenue:>10000
  &group_by[]=month,region,product_line
  &having[]=total_revenue:>50000
  &sort=total_revenue:desc
  &fields[]=id,name,revenue,margin,growth_rate
  &page=1&per_page=100
Enter fullscreen mode Exit fullscreen mode

That's not contrived. That's a real analytics query. In a URL. And this is the mild version — Elasticsearch DSL, GraphQL queries, and SQL-style filter trees make this look minimalist.

So teams switched to POST. And POST solved the body-size problem and created a different one: POST is defined to mean "this might have side effects." It is neither safe nor idempotent by default. Caches ignore POST responses. Proxies won't automatically retry a failed POST. Monitoring tools flag POST calls as potentially state-changing. OpenAPI specs describe them under the "mutations" section. Every piece of infrastructure that makes decisions based on HTTP semantics now has the wrong information about what your search endpoint does.

We weren't just working around a limitation. We were telling the protocol a lie, and that lie had real costs.


What QUERY Actually Is

QUERY is a single, precise addition to the HTTP method registry. Four properties, two of which it inherits from GET and two from POST:

From GET: Safe. Idempotent.
From POST: Carries a request body.
New: Explicitly cacheable with a body.

QUERY /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json

{
  "filters": {
    "category": "laptops",
    "price": { "min": 500, "max": 2000 },
    "in_stock": true
  },
  "sort": { "field": "price", "direction": "asc" },
  "page": 1,
  "per_page": 50
}
Enter fullscreen mode Exit fullscreen mode

The request body carries the query. The method communicates what the request does. The protocol and the intent finally match.

Servers advertise which body formats they accept via a new Accept-Query response header. The RFC's examples include application/json, application/x-www-form-urlencoded, application/sql, application/jsonpath, and application/xslt+xml. QUERY doesn't care what format the body is — it's format-agnostic by design. A REST API, a GraphQL server, and an Elasticsearch cluster can all speak QUERY using their own body conventions.


The Timeline: 11 Years. 16 Years. Both True.

The idea behind QUERY isn't new. The first draft circulated roughly eleven years ago. It moved through review for over a decade, reached a final draft in November 2025, received IESG approval on November 20, 2025, and was published as RFC 10008 on June 15, 2026.

The last genuinely new HTTP method before this was PATCH, published as RFC 5789 in March 2010. That makes QUERY the first new general-purpose HTTP method in sixteen years.

Both numbers — eleven years to draft, sixteen years since the last new method — are features, not bugs. HTTP underpins the entire web. A change to it gets reviewed against every server, every browser, every proxy, every CDN, every security assumption, every existing implementation that exists on the planet before it becomes a standard. That process is deliberately slow. It's why "new HTTP method" is real news, not framework noise.

The RFC is 24 pages. Read it. It's unusually readable for an IETF document — the problem statement alone is worth your time.


The Author List Tells You Something

RFC 10008 carries three names: Julian Reschke of greenbytes, James M. Snell of Cloudflare, and Mike Bishop of Akamai.

Cloudflare and Akamai, between them, sit in front of a significant portion of the web's traffic. These aren't academic researchers speculating about how CDNs might behave. They are the CDN. Their involvement in writing the spec means the caching behavior — the hardest part to get right — was designed by people who operate the caching infrastructure it will run on.

That matters more than it sounds. Caching a GET response is straightforward: the URL is the cache key. Caching a QUERY response requires including the request body in the cache key, and getting that wrong opens cache poisoning attacks. The spec is precise about this because the people who wrote it will also be the ones whose infrastructure gets poisoned if they got it wrong.


Caching: The Part That Actually Changes Everything

The difference between QUERY and POST /search isn't just semantic cleanliness. It's whether your responses can be cached.

POST responses are not cached by default. Every search request hits your server fresh. For simple APIs, that's fine. For high-traffic search endpoints — a product catalog, a content discovery feed, a real-time analytics dashboard — it means every filter combination, every repeated query, every pagination request goes to the origin. No CDN edge caching. No shared cache hits. Your server takes the full load.

QUERY is explicitly cacheable. A CDN can store the response to a QUERY request and serve it to the next client who sends the same request body to the same URL. For read-heavy APIs, that's not a minor ergonomic improvement. It's a different performance tier.

The catch: the cache key must include the full request body, not just the URL. A cache that keys on the URL alone will serve one user's search results to every user who queries the same endpoint — regardless of what their filter body says. That is a cache poisoning vulnerability, and it requires no bug in the application. It's a misconfigured cache.

This is the part your infrastructure team needs to understand before you ship QUERY anywhere near production.


What the Protocol Now Lets Intermediaries Do

The semantic shift from POST to QUERY isn't just about correctness. It's about what intermediaries — proxies, CDNs, load balancers, API gateways — are permitted to do with your requests.

With POST, intermediaries have to assume the operation might have side effects. They won't cache it. They won't automatically retry a failed request. They won't prefetch it. They treat it like a state change, because that's what the protocol says POST might be.

With QUERY:

  • Caches can store and reuse responses.
  • Clients can safely retry a failed QUERY without worrying about double-submission.
  • Proxies can automatically retry on network failures.
  • Monitoring tools can correctly classify it as a read operation.
  • OpenAPI specs can describe it accurately without gymnastics.
  • Rate limiters can apply read-operation quotas instead of write-operation quotas.

Every layer of the stack that currently makes decisions based on HTTP method semantics gets the right information. That's not a cosmetic improvement. Distributed systems built around HTTP use method semantics to make real decisions about retry behavior, caching, and traffic routing. Giving them accurate semantics means those decisions become correct.


Where It Works Today (Be Specific With Yourself)

Environment Status — July 2026
Node.js Native QUERY parsing since early 2024 ✓
OpenAPI 3.2 Full documentation support ✓
curl Supported — curl -X QUERY ...
GraphQL servers Natural fit for query operations (server-side) ✓
Spring (Java) PR open, RequestMethod enum not yet updated ✗
Ruby on Rails Discussion stage only ✗
Chrome / Firefox / Safari Still evaluating; fetch() not yet ✗
WAFs (default rulesets) Not included in method allowlists pre-June 2026 ✗
CDNs (default config) Varies — verify explicitly before relying on it ✗

Node.js is ready. OpenAPI is ready. Your WAF is probably not.

The browser gap is the most significant constraint for public-facing APIs. Browser JavaScript's fetch() doesn't support QUERY yet as of this writing, which means any API that needs to be called directly from a browser is either stuck on POST-as-fallback or waiting. For server-to-server APIs, internal APIs, and GraphQL backends, the picture is much better.


The Security Side Nobody's Talking About

New HTTP methods are an attack surface before they're a feature. Security infrastructure written before June 2026 doesn't know QUERY exists, which creates gaps that are interesting to both attackers and defenders.

WAF inspection gaps. Web application firewalls typically enforce method allowlists: GET, POST, PUT, DELETE, PATCH. A WAF that doesn't know about QUERY might pass it through without running the injection and XSS detection rules it would run against a POST body carrying the same payload. This isn't a theoretical problem — test it yourself:

# Send the same payload via POST first
curl -X POST https://target.com/api/search \
  -H "Content-Type: application/json" \
  -d '{"q": "test OR 1=1"}' -v

# Then try QUERY
curl -X QUERY https://target.com/api/search \
  -H "Content-Type: application/json" \
  -d '{"q": "test OR 1=1"}' -v
Enter fullscreen mode Exit fullscreen mode

If your WAF blocks the POST but passes the QUERY, you've found a live inspection gap in your own infrastructure. Fix that before you enable QUERY in production.

Cache poisoning. Already covered above — but worth repeating in plain terms. If a cache doesn't include the full request body in its cache key, two different QUERY requests to the same URL will share a cache entry. User A searches for {"category": "laptops"}. User B searches for {"category": "phones"}. If the cache keys on URL alone, User B gets User A's laptop results. Worse: an attacker can deliberately populate the cache with a crafted response that other users will receive.

CORS preflights. QUERY is not a CORS-safelisted method. Any browser JavaScript calling a QUERY endpoint cross-origin will trigger a preflight OPTIONS request — same as PUT or DELETE. The spec got this right on purpose. But your CDN, reverse proxy, and CORS middleware all need to handle that preflight consistently. Don't assume — test the actual path.

CSRF audit. CSRF middleware is typically scoped to state-changing methods. QUERY is safe by spec — but a developer might implement a QUERY endpoint that has an unintended side effect (logging, rate-limit tracking, "last searched" timestamp updates). If your CSRF middleware doesn't cover QUERY because it's "safe," those side effects get a free pass from cross-site requests. Audit any QUERY endpoint with side effects explicitly.


GraphQL Finally Has the Right Verb

GraphQL is the clearest example of how far POST /search misrepresented what APIs were actually doing.

GraphQL query operations — not mutations, not subscriptions, just queries — are already safe, already idempotent, and already read-only by design. They've been executed over POST /graphql for years because GET couldn't carry the complex JSON bodies that GraphQL operations require.

QUERY is the verb GraphQL queries were always supposed to use. The operation is read-only. The method is now QUERY. The semantics match. CDN caching of GraphQL query responses becomes correct-by-default rather than requiring custom cache configurations that fight against POST semantics.

Server-side GraphQL frameworks are among the first candidates to ship QUERY support, and the alignment is clean enough that they should.


The Practical Migration Path

Don't do a hard cutover. The ecosystem isn't ready.

The right approach is additive: keep POST /search running alongside the new QUERY endpoint. Advertise QUERY support via the Accept-Query response header on the POST response. Let clients migrate as their tooling catches up. When QUERY traffic exceeds POST traffic for a given endpoint, deprecate POST. Until then, both coexist.

Before you enable QUERY anywhere that matters, four things need to be explicitly verified:

1. Your framework routes it. Don't assume — check the specific version. Spring doesn't yet. Rails doesn't yet. Express with Node 22+ does.

2. Your WAF inspects the body. Run the comparison test above. If the WAF treats the QUERY body differently from a POST body, that's a problem to fix now, not after you've launched.

3. Your cache includes the body in the key. This is the highest-priority item for any public API. A misconfigured cache is worse than no caching — it serves wrong data to real users.

4. Your CORS and CSRF middleware covers it. Don't rely on inheritance from POST behavior. Explicitly add QUERY to the method list your security middleware handles.


The Honest Summary

Property GET POST QUERY
Safe (no side effects) Yes No Yes
Idempotent Yes No Yes
Cacheable by default Yes No Yes
Carries a request body No Yes Yes
CORS preflight required No Depends Yes
Standard since 1991 1996 June 2026

What This Signals About HTTP Itself

PATCH took sixteen years from proposal to wide adoption. QUERY will probably follow a similar arc. The IETF doesn't ship methods on a quarterly release cycle, and that's exactly why each one matters.

The gap since PATCH was published in March 2010 is sixteen years. In those sixteen years, the web went from REST APIs being novel to REST APIs being infrastructure. The entire time, every search endpoint on the internet was using the wrong verb, and the protocol had no answer.

RFC 10008 is a 24-page document. It will take years to fully propagate through frameworks, WAFs, CDNs, and developer habits. But the vocabulary of HTTP — the shared language every API on the internet speaks — just got one new word. A word that makes it possible, for the first time, to say what POST /search was always trying to say.


RFC 10008 is available in full at rfc-editor.org/info/rfc10008. The IESG approval record and public review comments are on the IETF datatracker at datatracker.ietf.org/doc/rfc10008. All framework and browser support statuses are current as of July 2026.

Cheers!

Top comments (0)