Meet QUERY: The Missing HTTP Method
QUERY is a new HTTP method: the body of a POST with the safety and caching of a GET. It is the verb we have been faking with POST for every search endpoint. What it is and how it looks in code.
TL;DR —
QUERYis a new HTTP method: the body of aPOSTwith the safety and caching of aGET. It's the verb we've been faking withPOSTfor every search endpoint. Here's what it is and how it looks in code.
The problem, in one screenshot
You have a search endpoint. It reads data and changes nothing. Semantically that's a GET. But the query is too rich for a URL:
GET /contacts?filter=%7B%22country%22%3A%22NL%22%2C%22tags%22%3A%5B%22vip%22%5D%7D&sort=-createdAt&limit=50 HTTP/1.1
Ugly, fragile, and it blows past URL length limits fast. So you try a body on the GET:
GET /contacts HTTP/1.1
Content-Type: application/json
{ "country": "NL", "tags": ["vip"], "sort": "-createdAt", "limit": 50 }
…and the spec shrugs: a GET body has "no defined semantics." Some proxies strip it, some servers reject it. Don't rely on it.
So everyone reaches for POST:
POST /contacts/search HTTP/1.1
Content-Type: application/json
{ "country": "NL", "tags": ["vip"], "sort": "-createdAt", "limit": 50 }
It works — but you just told every cache, proxy, and gateway a lie: that this read-only search modifies state. Because that's what POST means.
What GET and POST actually promise
- Safe → no state change. Nothing to clean up after.
- Idempotent → call it once or ten times, same result. Retry-friendly.
GET → ✅ safe ✅ idempotent ❌ body ("undefined")
POST → ❌ safe ❌ idempotent ✅ body
We've been stuck picking "safe semantics, no body" or "a body, unsafe semantics." There was no verb for "I have a body AND I'm only reading."
That verb is now QUERY.
Meet QUERY
Defined by the IETF HTTP Working Group in The HTTP QUERY Method (draft-ietf-httpbis-safe-method-w-body, draft 14 / Nov 2025, Standards Track):
QUERYrequests that the server process the enclosed content in a safe and idempotent manner.
| Property | GET | POST | QUERY |
|---|---|---|---|
| Request body | ❌ undefined | ✅ | ✅ |
| Safe | ✅ | ❌ | ✅ |
| Idempotent | ✅ | ❌ | ✅ |
| Cacheable | ✅ | ❌ | ✅ |
The body of a POST, with the guarantees of a GET.
On the wire
QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: application/json
{ "country": "NL", "tags": ["vip"], "sort": "-createdAt", "limit": 50 }
HTTP/1.1 200 OK
Content-Type: application/json
[
{ "surname": "Smith", "givenname": "John", "email": "john@example.org" }
]
Rules that matter:
- ✅
Content-Typeis mandatory — servers must reject aQUERYwith a missing/inconsistent type. - ✅ Body format is your choice — JSON, form-encoded, GraphQL, SQL, SPARQL, a custom DSL.
- ✅ Safe + idempotent by contract — if it has side effects, you're using the wrong verb.
In code
curl
curl -X QUERY https://api.example.org/contacts \
-H "Content-Type: application/json" \
-d '{ "country": "NL", "tags": ["vip"], "limit": 50 }'
Client — fetch (Node / browser)
const res = await fetch("https://api.example.org/contacts", {
method: "QUERY",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ country: "NL", tags: ["vip"], limit: 50 }),
})
const contacts = await res.json()
Server — Express
import express from "express"
const app = express()
app.use(express.json())
// Route the new verb like any other method
app.use((req, res, next) => {
if (req.method === "QUERY" && req.path === "/contacts") {
return searchContacts(req, res)
}
next()
})
async function searchContacts(req: express.Request, res: express.Response) {
const { country, tags, limit } = req.body
const rows = await db.contacts.find({ country, tags }, { limit })
res.json(rows) // safe + idempotent: no writes in here
}
The real payoff: caching
With POST-based search, caching is off the table — a shared cache sees POST and assumes a mutation, so every identical search hits your origin.
QUERY is declared safe, so caches may store and reuse the response. The twist: the cache key now includes the request body.
POST /contacts/search {body} → cache key = method + URL → ❌ not cached
QUERY /contacts {body} → cache key = method + URL + BODY → ✅ cacheable
Same-path, different-body = different cached entries. Servers can also hand clients a canonical cacheable URL via Content-Location. Result: expensive searches cached at the edge, no hand-rolled "hash-the-query-into-a-URL" hack.
Where it fits in 2026
The API world has fragmented — gRPC for service-to-service, GraphQL/tRPC for typed frontends, streaming + events for real-time, MCP for AI agents calling your endpoints.
QUERY is the reminder that REST isn't dead — HTTP is still evolving. Instead of routing around HTTP's limits with another framework, it fixes the limit at the protocol layer, so caches, proxies, and gateways downstream get the benefit for free.
Good protocol design in a nutshell: not a new feature, just naming a guarantee we'd been faking.
Should you use it yet?
Soon — not quite today.
- ⚠️ Still an Internet-Draft, not a finalized RFC.
- ⚠️ Client / server / proxy support is catching up.
- ✅ Worth knowing now, and worth keeping search endpoints genuinely side-effect-free so migration is trivial.
Next time you put a read-only query in a POST body and feel faintly guilty — that instinct was right. There's finally a proper verb for it.
Sources: The HTTP QUERY Method — IETF draft-ietf-httpbis-safe-method-w-body · MDN — GET · MDN — POST
This article is also published on Medium.