The HTTP QUERY Method, Explained
RFC 10008 gives HTTP a method for safe queries with a body. It fills the awkward gap between long GET URLs and POST requests that normally reserved for mutations.
Teams have used POST for read-only search for years because GET puts the query in the URI. That works for simple filters, but it gets awkward when the input is a structured body: nested filters, selected fields, sort rules, date ranges, or a query language.
POST /search with JSON is the usual workaround. The problem is that HTTP infrastructure does not see the application intent. A retry layer, proxy, content delivery network, gateway, firewall, or engineer reading traces still sees POST, a method commonly used for operations that change state.
RFC 10008 introduces QUERY for that missing shape: a safe, idempotent request whose query input lives in the request body.
The Short Version
QUERY is a new HTTP method for read-only operations that need a request body. The client sends query content in the body, and the server processes that content without treating the request as a state-changing command.
The RFC defines QUERY as safe and idempotent. Safe means the client is not asking the server to change the target resource. Idempotent means repeating the same request has the same intended effect as sending it once.
Its basically, just GET with request body
But why?
A Practical Example
Suppose a search page lets users filter products by category, price, tags, inventory status, and result fields. A GET version starts readable, then becomes brittle:
GET /products?category=books&min_price=10&max_price=50&tag=distributed-systems&tag=http&in_stock=true&fields=id,name,price,rating&sort=-rating HTTP/1.1
Host: api.example.com
Accept: application/jsonThat still fits in a URL, but the problem is visible. More filters mean more encoding, longer logs, and harder debugging.
There is also no single safe URL length promised by HTTP that every browser, proxy, gateway, web server, and framework must accept. Each hop can have its own default or configured ceiling.
Taking from my experience as example: A filter such as hubIds may look fine with three selected hubs, then become fragile when a user selects 40 hubs, especially if those hub IDs are UUIDs or encoded values:
GET /products?hubIds=hub_01&hubIds=hub_02&hubIds=hub_03&hubIds=hub_04&hubIds=hub_05&hubIds=hub_06&hubIds=hub_07 HTTP/1.1
Host: api.example.com
Accept: application/jsonThe exact breaking point is environment-specific, which is the problem. Once a read operation depends on long repeated query parameters, the URL is no longer a clean transport for the query input.
With QUERY, the target stays simple and the query gets a real body:
QUERY /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{
"where": {
"category": "books",
"price": { "min": 10, "max": 50 },
"tags": ["distributed-systems", "http"],
"inStock": true
},
"select": ["id", "name", "price", "rating"],
"sort": [{ "field": "rating", "direction": "desc" }],
"limit": 20
}The response can look like any normal JSON response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60
{
"items": [
{
"id": "book_123",
"name": "HTTP Systems In Practice",
"price": 32,
"rating": 4.8
}
]
}The point is not that JSON is special. The body could be JSONPath, SQL, form data, GraphQL-like text, or an application-specific query format. The point is that the method now says “this is a query,” not “this is a submission.”
Why GET With A Body Is Not The Fix
A natural question is why the industry did not simply standardize GET with a body. HTTP does not make that shape impossible at the byte level, but the web has decades of behavior built around GET meaning “the useful inputs are in the URI.” Browsers, forms, bookmarks, caches, corporate proxies, web application firewalls, and server frameworks all grew up with that assumption.
That means GET with a body fails in boring and painful ways. One intermediary can drop the body. Another can reject the request. A framework can route the request but never expose the body to application code. A browser feature can work in one place and fail behind a corporate firewall.
QUERY is cleaner because it does not ask existing GET machinery to change meaning. It gives body-based reads their own method.
Why POST Was The Wrong Compromise
POST /search is popular because it works almost everywhere. It accepts a body, most frameworks support it, and every HTTP client can send it.
The cost is semantic confusion. A generic retry layer is careful with POST because a repeated request may create a duplicate order, send another email, or charge a card twice. A cache is careful with POST because the method does not clearly identify a normal retrieval operation. An engineer reading traces has to know that this particular POST endpoint is read-only.
QUERY moves that knowledge into the protocol shape.
That is the real benefit. QUERY lets application code and HTTP infrastructure agree on the same meaning.
What This Means For Frontend Filters
A common frontend pattern looks like this:
import { useQuery } from '@tanstack/react-query'
type ProductFilters = {
category: string
minPrice?: number
maxPrice?: number
tags: string[]
}
export function useProductSearch(filters: ProductFilters) {
return useQuery({
// TanStack Query needs every input that changes the result in the key.
queryKey: ['products', filters],
queryFn: async () => {
const response = await fetch('/api/products:query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(filters),
})
if (!response.ok) {
throw new Error('Product search failed')
}
return response.json()
},
})
}This is not a bad design. It is a practical workaround. TanStack Query gives the frontend a correct application-level cache as long as the queryKey includes the filter body. The server can also add its own application cache by hashing the body and storing the result behind that hash.
The awkward part is that both caches are private agreements. A CDN, proxy, gateway, or generic retry library still sees POST /api/products:query. Unless it has custom rules for that endpoint, it cannot safely treat the request like a read.
With QUERY, the same client code can make the meaning explicit:
import { useQuery } from '@tanstack/react-query'
type ProductFilters = {
category: string
minPrice?: number
maxPrice?: number
tags: string[]
}
export function useProductSearch(filters: ProductFilters) {
return useQuery({
queryKey: ['products', filters],
queryFn: async () => {
const response = await fetch('/api/products', {
method: 'QUERY',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(filters),
})
if (!response.ok) {
throw new Error('Product search failed')
}
return response.json()
},
})
}TanStack Query still matters here. It handles component state, deduplication, background refetching, stale times, retries, and cache invalidation inside the app. QUERY handles the HTTP semantics outside the app. They solve different layers of the same problem.
So yes, QUERY answers the long-running POST-for-search problem. The cautious version is: it answers the semantics, not all the deployment work. Frontend code still needs stable query keys. Servers still need validation and cache-control headers. Infrastructure still needs to allow the new method.
When To Use QUERY
Use QUERY when these are all true:
- The operation is read-only from the client’s point of view.
- The input is too large, nested, or structured for a clean URL.
- The result benefits from HTTP behavior such as retries, caching, content negotiation, or conditional requests.
- The client and server path can be tested end to end with the new method.
Good candidates include search APIs, report builders, analytics endpoints, read-only JSON-RPC calls, catalog filters, and internal data APIs with a typed query language.
A simple decision rule works well:
Meaning comes first. Payload shape comes second.
When Not To Use QUERY
Use POST, PUT, PATCH, or DELETE when the request creates, updates, deletes, enqueues, charges, reserves, sends, or otherwise asks the server to change state. Those are commands, not queries.
Keep GET when the request is simple, bookmarkable, shareable, or meant to open directly from an address bar. A QUERY request body is not part of a normal clickable URL, so user-facing filtered pages are often better as GET URLs or saved-query resources.
Do not treat QUERY as a privacy feature. It can keep query details out of the URL, but request bodies can still appear in application logs, traces, debug tools, and error reports.
Avoid assuming every production path already supports it. Gateways, web application firewalls, frameworks, and monitoring agents may reject or mishandle unfamiliar methods until configured.
Gotchas
The biggest gotcha is support, not syntax. Many clients can send arbitrary method strings, but every hop between the client and the handler has to allow them. Some API clients already support custom methods, and Kreya added built-in QUERY support in version 1.20, but that does not mean a production gateway, firewall, or observability agent accepts it yet.
The second gotcha is cache normalization. Two JSON bodies can mean the same thing with different whitespace or key order. A cache may normalize those bodies to improve hit rate, but if it normalizes differently from the origin server, it can return the wrong response.
The third gotcha is error handling. A missing Content-Type should be a client error. An unsupported query format should be 415 Unsupported Media Type. A syntactically valid query that asks for something impossible, such as a missing table, can be 422 Unprocessable Content.
The fourth gotcha is redirects. Redirecting a QUERY is not the same as redirecting POST. If the server wants the client to switch to GET, use a response pattern that clearly points at a GET resource.
Takeaway
Use QUERY when an API is asking a read-only question that has outgrown the URL, especially if the team already treats a POST body as cacheable query input in application code.
References
- Reschke, Julian, Snell, James M., and Bishop, Mike. "The HTTP QUERY Method." IETF RFC 10008, June 2026.
- Smith, Blain. "RFC 10008: The HTTP QUERY Method." blainsmith.com, June 2026.
- Manuel. "The New HTTP QUERY Method Explained." Kreya, June 2026.
- Fielding, Roy, Nottingham, Mark, and Reschke, Julian. "HTTP Semantics." RFC 9110, June 2022.
- Fielding, Roy, Nottingham, Mark, and Reschke, Julian. "HTTP Caching." RFC 9111, June 2022.
Related essays
Expand and Contract: The Pattern Behind Zero-Downtime API Changes
Renaming one field in a live API sounds trivial until you realize every deployed client is reading the old name. Here is the pattern that makes the change boring instead of dangerous.