DEV Community

Cover image for HTTP QUERY: The Missing Middle Ground Between GET and POST
Henry Osei
Henry Osei

Posted on

HTTP QUERY: The Missing Middle Ground Between GET and POST

For years, API developers have lived with an awkward choice. When a client wants to retrieve data, the natural method is GET. But when the query becomes too large, too structured, or too sensitive to fit in a URL, most teams switch to POST. It works, and it has always felt slightly wrong.

RFC 10008, published in June 2026, finally closes that gap with the HTTP QUERY method: a safe, idempotent method that allows a request body. I've been waiting for something like this since the first time I watched a query string blow past 2,000 characters, so I read the RFC the week it landed. Here's what it actually says.

The problem with GET

A typical search endpoint looks like this:

GET /feed?q=foo&limit=10&sort=-published HTTP/1.1
Host: example.com
Enter fullscreen mode Exit fullscreen mode

Fine for simple queries. But real systems rarely stay simple. Think about:

{
  "filters": {
    "country": "GH",
    "status": ["active", "pending"],
    "created_after": "2026-01-01",
    "risk_score": {
      "gte": 70
    }
  },
  "sort": ["-created_at"],
  "limit": 100
}
Enter fullscreen mode Exit fullscreen mode

Encoding that into a URL gets ugly fast, and ugliness is the smallest problem. RFC 10008 lists the real ones: URI length limits are unknowable when requests pass through multiple systems, URI encoding is inefficient, URLs are more likely to be logged or bookmarked, and every combination of query parameters effectively becomes a distinct resource. So developers do the pragmatic thing and reach for POST.

The problem with POST

The common workaround:

POST /feed/search HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "q": "foo",
  "limit": 10,
  "sort": "-published"
}
Enter fullscreen mode Exit fullscreen mode

I've shipped plenty of these and they work fine, until you notice what POST tells everything between the client and your handler: something may change. HTTP clients, proxies, caches, API gateways, retry logic, and security tools all read method semantics to understand intent. Your POST /search may be completely read-only, but nothing in the request says so, which means no intermediary will cache it and no client will retry it automatically. RFC 10008 calls this out directly: when POST is used for a safe query operation, there is no way to know the request is safe and idempotent without server-specific knowledge.

Enter QUERY

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

{
  "q": "foo",
  "limit": 10,
  "sort": "-published"
}
Enter fullscreen mode Exit fullscreen mode

This request says: process this query body and return a result, but don't change the state of the target resource. RFC 10008 defines QUERY as a method for initiating a server-side query, where the request content and its media type define the query and the server processes it within the scope of the target resource. Because the method is safe and idempotent, clients and intermediaries can retry it without worrying about partial state changes.

The mental model:

GET    → retrieve a resource identified by a URI
POST   → submit data that may create or change state
QUERY  → perform a read/query operation using structured request content
Enter fullscreen mode Exit fullscreen mode

Why this matters for API design

Modern APIs are rarely plain CRUD. Search endpoints, analytics and reporting APIs, GraphQL-style reads, RAG retrieval services, fraud and risk scoring queries, BI dashboards: a lot of what we build now is read-only operations whose input is too complex for clean URL parameters.

Until now you had two imperfect choices, abuse the URL with GET or lose read-only semantics with POST. QUERY lets the body carry structured input while keeping the HTTP properties you actually want: safety, idempotency, retryability, and cacheability.

Content-Type matters

A QUERY request is not "GET with a body." The body only has meaning together with its media type, and RFC 10008 says servers must fail the request if Content-Type is missing or inconsistent with the content. It also spells out which errors to use: 400 for bad or missing media type information, 415 for unsupported media types, 422 when the content parses but cannot be processed, and 406 when the requested response format is not available.

So this is good:

QUERY /transactions HTTP/1.1
Content-Type: application/json
Accept: application/json
Enter fullscreen mode Exit fullscreen mode

And this is not enough:

QUERY /transactions HTTP/1.1

{ "status": "pending" }
Enter fullscreen mode Exit fullscreen mode

The server should refuse to guess, because content sniffing has a long history of turning into security and interoperability bugs.

QUERY and caching

One of the biggest wins is that QUERY responses are cacheable. RFC 10008 requires the cache key to include the request content and related metadata, which makes sense once you see why:

QUERY /customers HTTP/1.1
Content-Type: application/json

{ "country": "GH" }
Enter fullscreen mode Exit fullscreen mode

is not the same query as:

QUERY /customers HTTP/1.1
Content-Type: application/json

{ "country": "NG" }
Enter fullscreen mode Exit fullscreen mode

Same URI, different body, so it needs a different cache key. This makes QUERY caching harder than GET caching. The cache has to read and understand the request content well enough to key on it, and I suspect this is where early implementations will have the most bugs. The RFC offers one way to soften it: use Location to expose an equivalent GET resource for later requests.

Location and Content-Location

Which brings up a pattern I like a lot. A server responding to QUERY can return:

Content-Location: /reports/results/abc123
Location: /reports/queries/q456
Enter fullscreen mode Exit fullscreen mode

The two headers mean different things. Content-Location identifies a resource representing the result that was just returned. Location identifies an equivalent resource that clients can hit later with plain GET to rerun the same query without resending the body.

For expensive queries this is genuinely useful. An analytics API accepts a QUERY, runs a complex filter once, then exposes the stored query as a normal URI:

First request:

QUERY /analytics/events
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

Later requests:

GET /analytics/stored-queries/q456
Enter fullscreen mode Exit fullscreen mode

Now the expensive part happens once and everything downstream is ordinary, cacheable GET.

Discovering QUERY support

Clients can check whether a resource supports QUERY with OPTIONS; the server includes QUERY in its Allow header. RFC 10008 also defines an Accept-Query response field that lets a resource advertise which media types it accepts for query content.

That last part matters because QUERY is deliberately not tied to one query language. One API accepts application/json, another application/sql, a third some domain-specific format. The method carries the semantics; the media type defines the query language.

Security considerations

Moving query data out of URLs helps in one specific way: URLs get logged everywhere, by servers, proxies, browsers, monitoring systems, and analytics tools. RFC 10008 notes that request content is less likely to be logged or processed by intermediaries, so sensitive query information may be better placed in the body.

Don't oversell this to yourself, though. Request bodies can still be logged. Temporary result URIs can leak information if designed carelessly. Caches can serve wrong responses if they normalize query content incorrectly. And if you're calling from a browser, QUERY is not a CORS-safelisted method, so cross-origin requests will trigger a preflight. It's a semantic improvement with some incidental privacy benefits, not a security control.

Should you migrate your APIs immediately?

Probably not. The method exists in the spec as of last month, but you can't deploy a spec. Adoption depends on browsers, HTTP clients, gateways, reverse proxies, load balancers, SDK generators, OpenAPI tooling, and backend frameworks all catching up, and some of those move slowly. In most production systems, POST /search will stay the practical choice for a while, and there's no shame in that.

Where I'd start experimenting: new internal APIs, data platforms, AI retrieval services, and gateway-controlled environments where you own every hop between client and server. If your infrastructure can't mangle the method along the way, you get the benefits today.

Where QUERY fits

QUERY doesn't replace GET or POST; it fills the space between them. Use GET when the URI cleanly identifies the resource and the query is simple. Use POST when the request has side effects. Use QUERY when the client wants a safe, idempotent read with structured input.

Final thought

For years we've used POST to mean "please search this," "please filter this," "please run this read-only report," and every proxy, cache, and gateway in between had to assume the worst. QUERY gives HTTP a native way to say what those requests always meant. It will take time for the ecosystem to catch up, and POST /search isn't going anywhere soon. But when the method matches the intent, everything between the client and the server gets easier to reason about, and that's worth the wait.

Top comments (0)