How the HTTP QUERY Method Works
QUERY solves a problem every API faces: sending complex queries to a server without sacrificing safety, idempotency, or cacheability. Defined in RFC 10008 (June 2026), QUERY is an HTTP method that carries a request body — like POST — while preserving the semantic guarantees of GET.
In one sentence: QUERY is GET with a body — safe, idempotent, and cacheable by specification.
Method Properties
Section titled “Method Properties”| Property | GET | POST | QUERY |
|---|---|---|---|
| Safe (no state change) | ✅ | ❌ | ✅ |
| Idempotent (repeatable without side effects) | ✅ | ❌ | ✅ |
| Cacheable | ✅ | ❌¹ | ✅ |
| Accepts request body | ❌² | ✅ | ✅ |
¹ POST responses can only be cached for future GET/HEAD requests, with severe restrictions.
² RFC 9110 states that a body in GET “has no generally defined semantics” and intermediaries may discard it.
The Request Body
Section titled “The Request Body”In QUERY, the body is the query. The server processes the body content as a question — not as a state-changing command.
Content-Type is mandatory
Section titled “Content-Type is mandatory”The RFC requires the server to reject the request if the Content-Type header is missing or inconsistent with the content. Any media type is valid: application/json, application/sql, application/graphql, application/jsonpath — the server decides what it accepts.
Format discovery
Section titled “Format discovery”A server can advertise which query types it accepts via the Accept-Query header:
Accept-Query: "application/json", "application/graphql"Complete QUERY request example
Section titled “Complete QUERY request example”QUERY /api/contacts HTTP/1.1Host: api.example.comContent-Type: application/jsonAccept: application/jsonContent-Length: 76
{ "filter": { "city": "Berlin", "active": true }, "limit": 25, "offset": 0}This is semantically identical to asking “which active contacts exist in Berlin?” — without modifying any data on the server.
Caching
Section titled “Caching”QUERY responses are cacheable, but the cache key is more complex than GET.
The cache key includes the body
Section titled “The cache key includes the body”For GET, the cache key is straightforward: method + URI. For QUERY, the RFC mandates that the cache key MUST incorporate the request content and related metadata (such as Content-Type).
In practice, a proxy computes:
cache_key = hash(method + target_URI + normalized_body + content_type)Normalization for efficiency
Section titled “Normalization for efficiency”Caches MAY normalize the body before generating the key, removing semantically insignificant differences:
- Strip content-encoding (e.g., decompress gzip before comparing)
- Normalize JSON (reorder keys, remove extraneous whitespace)
- Apply media type knowledge (a
+jsonsuffix indicates JSON normalization is safe)
Normalization never alters the actual request — only the representation used for key generation.
Content-Digest for content identification
Section titled “Content-Digest for content identification”The server can return the Content-Digest header in the response, providing a hash of the response body. This allows clients and intermediaries to verify integrity and identify identical responses:
Content-Digest: sha-256=:4REjxQ4yrqUVicfSKYNO/cF9zNj5ANbzgDZt3/h3Qxo=:Key difference vs GET caching
Section titled “Key difference vs GET caching”| Aspect | GET | QUERY |
|---|---|---|
| Cache key | Method + URI | Method + URI + Body + Content-Type |
| Buffering required | No | Yes (proxy must read the full body) |
| Normalization target | URI parameters | Request body |
| Equivalent resource | The URI itself | URI derived by incorporating the body |
Response example with cache headers
Section titled “Response example with cache headers”HTTP/1.1 200 OKContent-Type: application/jsonContent-Digest: sha-256=:Kp+BNRhIbcXKW4IA6WmUBjbOz/ia/enOVP22eCJPMjU=:Cache-Control: max-age=300ETag: "q-contacts-berlin-v42"Date: Fri, 04 Jul 2026 12:00:00 GMTContent-Length: 231
{ "results": [ {"id": 1, "name": "Anna Müller", "city": "Berlin"}, {"id": 2, "name": "Karl Weber", "city": "Berlin"} ], "total": 94, "limit": 25, "offset": 0}An identical QUERY request within the next 300 seconds can be served directly from cache — without hitting the origin server.
Intermediary Safety
Section titled “Intermediary Safety”The RFC resolves a historical problem: proxies and intermediaries must not discard the body of QUERY requests.
The problem with GET + body
Section titled “The problem with GET + body”When developers attempted to send a body with GET, intermediaries (proxies, load balancers, CDNs, WAFs) would frequently:
- Silently strip the body
- Reject the request with an error
- Close the connection on suspicion of request smuggling
This happened because RFC 9110 defines that a body in GET “has no generally defined semantics” — intermediaries had no obligation to preserve it.
QUERY changes the rules
Section titled “QUERY changes the rules”With QUERY, the semantics are explicit: the body is the meaningful content of the request. Intermediaries:
- MUST forward the body intact to the origin server
- MUST include the body when computing cache keys
- MUST NOT replay the request without the original body
- MAY retry automatically (because QUERY is safe + idempotent)
Sensitive data protection
Section titled “Sensitive data protection”The RFC highlights that URIs are routinely logged by intermediaries, but request bodies typically are not. QUERY allows sending filters containing sensitive data (email addresses, internal IDs, PII-bearing fields) without exposing them in access logs across the entire proxy chain.
Complete Flow: Client → Proxy → Origin → Cache
Section titled “Complete Flow: Client → Proxy → Origin → Cache”┌────────┐ ┌───────────┐ ┌────────────┐│ Client │ │ Proxy │ │ Origin │└───┬────┘ └─────┬─────┘ └──────┬─────┘ │ │ │ │ QUERY /contacts │ │ │ Body: {"city":"…"} │ │ ├────────────────────►│ │ │ │ │ │ ┌──────┴──────┐ │ │ │ Compute key │ │ │ │ method+URI │ │ │ │ +body+CT │ │ │ └──────┬──────┘ │ │ │ │ │ Cache miss? │ │ │ QUERY /contacts │ │ │ Body: {"city":"…"} │ │ ├─────────────────────►│ │ │ │ │ │ 200 OK │ │ │ Content-Digest:... │ │ │ Cache-Control:... │ │ │◄─────────────────────┤ │ │ │ │ ┌──────┴──────┐ │ │ │ Store in │ │ │ │ cache (key) │ │ │ └──────┬──────┘ │ │ │ │ │ 200 OK │ │ │ Content-Digest:... │ │ │◄────────────────────┤ │ │ │ │ │ │ │ │ QUERY /contacts │ │ │ Body: {"city":"…"} │ (same query) │ ├────────────────────►│ │ │ │ │ │ Cache hit! │ │ 200 OK │ │ │ (from cache) │ (origin not │ │◄────────────────────┤ contacted) │ │ │ │Conditional Requests
Section titled “Conditional Requests”QUERY supports conditional requests. If the client holds an ETag from a previous query, it can send If-None-Match to check whether results have changed:
QUERY /api/contacts HTTP/1.1Host: api.example.comContent-Type: application/jsonIf-None-Match: "q-contacts-berlin-v42"
{"filter": {"city": "Berlin"}, "limit": 25}If results are unchanged, the server returns:
HTTP/1.1 304 Not ModifiedETag: "q-contacts-berlin-v42"Zero bytes transferred. The query was validated without retransmitting the result set.
Practical Comparison: Before and After
Section titled “Practical Comparison: Before and After”Before (POST /search — incorrect semantics)
Section titled “Before (POST /search — incorrect semantics)”POST /api/contacts/search HTTP/1.1Content-Type: application/json
{"filter": {"city": "Berlin"}, "limit": 25}Problems:
- Proxy cannot cache (POST is potentially unsafe)
- Automatic retry is forbidden (POST may have mutated state)
- Semantics are dishonest — no resource is being created
Before (GET with query string — practical limitations)
Section titled “Before (GET with query string — practical limitations)”GET /api/contacts?filter[city]=Berlin&filter[active]=true&limit=25Problems:
- URI exposed in logs of every intermediary
- ~8,000 octet limit (best case)
- Every parameter combination = distinct resource for caches
After (QUERY — correct semantics)
Section titled “After (QUERY — correct semantics)”QUERY /api/contacts HTTP/1.1Content-Type: application/json
{"filter": {"city": "Berlin", "active": true}, "limit": 25}Advantages:
- ✅ Safe — no state is altered
- ✅ Idempotent — repeatable without consequences
- ✅ Cacheable — proxy can serve from cache
- ✅ Body preserved by intermediaries
- ✅ Sensitive data kept out of URI logs