HTTP QUERY Is Here: What RFC 10008 Means for API Design
~10 min readSixteen Years Without a New Method
The last time the HTTP method registry grew was 2010, when PATCH (RFC 5789) arrived to solve the partial-update problem. Before that, you have to go back to the original HTTP/1.1 specification in 1997 for the core set — GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, CONNECT. For nearly three decades, API designers have been forced to contort real-world use cases into a method set that was never designed for modern query-heavy, body-carrying workloads.
That changes with RFC 10008, published in June 2026 on the IETF Standards Track. Authored by Julian Reschke (greenbytes), James M. Snell (Cloudflare), and Mike Bishop (Akamai), the QUERY method fills a gap that has been a source of architectural friction since the first RESTful API was deployed.
The Problem That Wouldn't Die
Every API designer has faced this scenario: you need to send a complex query to a server — a GraphQL document, an Elasticsearch query DSL, a SPARQL triple pattern, a CouchDB _find selector. These payloads are too large for a GET query parameter, and they contain special characters that make URL encoding painful. Your options have been:
| Method | Carries Body | Safe | Idempotent | Cacheable |
|---|---|---|---|---|
| GET | No | Yes | Yes | Yes |
| POST | Yes | No | No | Only by explicit header |
| QUERY | Yes | Yes | Yes | Yes (with body in key) |
Safe means the request causes no side effects. Idempotent means N identical requests produce the same server state. GET is both — but cannot carry a body. POST can carry a body but is neither safe nor idempotent, which breaks retry logic, intermediary caching, and preloading. This is not a niche problem.
GraphQL deployments ship every query as a POST because the query document is a JSON string that lives in the request body. This means GraphQL queries cannot be cached by HTTP intermediaries, cannot be safely retried, and cannot be prefetched by browsers or CDNs. The GraphQL community has long known this is suboptimal — the recommended workaround is to compute a hash of the query and send it as a GET parameter, but that requires server-side support and breaks the self-describing nature of the request.
Elasticsearch users deal with the same tension: every search query is a POST to _search. So do SPARQL endpoints, OData services, and CouchDB clients. The QUERY method eliminates this category of workaround entirely.
How QUERY Works
RFC 10008 defines QUERY as a method that:
- Is safe — the server MUST NOT have side effects from a QUERY request
- Is idempotent — multiple identical requests produce the same result
- Carries a request body — the semantics of the query are expressed in the payload
- Is cacheable — responses MAY be stored and reused by caches
- Supports content negotiation — the client indicates what query language it speaks via the
Content-Typeheader
The request body contains the query expression. The server interprets it according to the media type in Content-Type and returns a representation of the results. The response can be any HTTP status code appropriate for the query — 200 for a successful result set, 204 for empty results, 404 if the query targets a resource that doesn't exist.
The Cache Key Problem
One of the trickiest design questions the RFC had to solve was caching. A QUERY to /api/search with {"term": "kubernetes"} should produce a different cached response than the same URL with {"term": "rust"}. In HTTP, the cache key for GET requests is the URL alone. For QUERY, the cache key MUST include the request body.
The RFC specifies that caches calculate the cache key as the URL plus the body content. Vary headers and Cache-Control directives apply as normal. Intermediate caches that do not support body-in-cache-key MUST NOT cache QUERY responses. This is a significant implementation hurdle for existing CDNs and reverse proxies — more on that below.
The Accept-Query Header
Servers that support QUERY advertise it via the Accept-Query response header (or OPTIONS response). This header lists the query media types the server understands:
Accept-Query: application/graphql, application/json
Clients can use OPTIONS to discover whether a server supports QUERY before sending a potentially expensive request. This mirrors how Allow works for method discovery.
Why Not SEARCH?
If you have been around the WebDAV world, you might recall the SEARCH method defined in RFC 5323. It had essentially the same semantics — safe, idempotent, carries a body. So why a new method?
The working group considered SEARCH but ultimately rejected it for two reasons:
- SEARCH carried WebDAV baggage — the method was tied to WebDAV's XML-based query syntax and had implementation assumptions that didn't map cleanly to JSON, GraphQL, or other modern formats
- The name "SEARCH" implies narrow semantics — QUERY is broader; it includes search, but also introspection, schema discovery, validation, and any operation that asks the server a question
The consensus was that a clean start was preferable to retrofitting SEARCH for the modern web.
Real-World Adoption
Adoption is happening faster than any previous HTTP method. Here is where things stand as of July 2026:
Client Libraries
| Runtime | Support | Notes |
|---|---|---|
| Node.js 21.7+ | Native via http module | Ships in core — no polyfill needed |
| curl | --request QUERY | Full support since 8.10 |
| Rust reqwest | Custom method via Method::from_bytes(b"QUERY") | Works with any HTTP method |
| Python httpx | client.request("QUERY", url, content=body) | Supported via generic method API |
| Go net/http | No special const, but http.NewRequest("QUERY", url, body) | Works with any method string |
| Java Apache HttpClient 5.4+ | HttpMethods.QUERY | First-class constant added |
| Ruby Net::HTTP | Net::HTTP.new(uri).request_post(uri, body, "method" => "QUERY") | Via generic method override |
Server Implementations
| Platform | Support | Notes |
|---|---|---|
| Go net/http | Standard handler — match on r.Method == "QUERY" | Trivially adopted |
| Node.js http | request.method === 'QUERY' | Same as any method |
| Express.js | app.query('/path', handler) | Express 5.x experimental |
| Python Starlette/FastAPI | Via generic route handler | No dedicated decorator yet |
| Rust actix-web | guard::Method("QUERY") | Manual guard required |
| Laravel 12 | Route::query('/search', ...) | First framework with dedicated routing |
Laravel deserves special mention — Taylor Otwell shipped Route::query() in Laravel 12 within weeks of RFC publication, making it the first major framework to provide first-class QUERY method routing.
Middleboxes and CDNs
This is where the story gets complicated:
| Proxy | Status |
|---|---|
| nginx | Passes through unmodified — supports unknown methods by default |
| Caddy | Patched for query-cache-key in v2.10 |
| Apache httpd | Patch available in 2.5-dev |
| CloudFront | Blocks QUERY — seen as unrecognised method |
| Akamai | Blocks QUERY — requires explicit method allowlist change |
| Cloudflare | Passes through but ignores body in cache key — effectively uncacheable |
| Varnish | Requires custom vcl to handle body-based cache key |
If you are behind CloudFront or Akamai, you cannot serve QUERY requests through the CDN today. This is the single biggest adoption blocker. The RFC authors are actively working with CDN providers, and Cloudflare has indicated support will ship before the end of 2026.
Code Examples
Node.js — Making a QUERY Request
import http from 'node:http';
const queryBody = JSON.stringify({ query: '{ users { name email } }' });
const options = {
hostname: 'api.example.com',
port: 443,
path: '/graphql',
method: 'QUERY',
headers: {
'Content-Type': 'application/graphql',
'Content-Length': Buffer.byteLength(queryBody),
},
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => console.log(data));
});
req.write(queryBody);
req.end();
curl — QUERY at the Command Line
curl --request QUERY \
--header 'Content-Type: application/json' \
--data '{"query": {"term": "kubernetes", "limit": 10}}' \
https://api.example.com/search
Server-Side Handler (Node.js)
import http from 'node:http';
const server = http.createServer((req, res) => {
if (req.method === 'QUERY' && req.url === '/search') {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', () => {
const query = JSON.parse(body);
// Execute search against index
const results = searchIndex(query.term, query.limit);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(results));
});
return;
}
// Handle other methods...
});
server.listen(3000);
Go — Minimal QUERY Handler
package main
import (
"encoding/json"
"net/http"
)
type Query struct {
Term string `json:"term"`
Limit int `json:"limit"`
}
func queryHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "QUERY" {
http.Error(w, "method not allowed", 405)
return
}
var q Query
json.NewDecoder(r.Body).Decode(&q)
results := search(q.Term, q.Limit)
json.NewEncoder(w).Encode(results)
}
func main() {
http.HandleFunc("/api/search", queryHandler)
http.ListenAndServe(":8080", nil)
}
Where QUERY Shines
GraphQL Endpoints
This is the killer use case. A GraphQL endpoint today sends every query as POST. With QUERY:
- Queries become cacheable — CDNs and reverse proxies can store the response keyed by URL + query document
- Queries become safe — the framework can guarantee no mutations happen through the QUERY method
- Mutations still use POST — the distinction is now enforced by the protocol, not convention
A GraphQL server advertising Accept-Query: application/graphql tells clients "you can send read-only queries here." The framework can route QUERY requests to the query executor and POST requests to the mutation executor at the transport layer, eliminating an entire class of bugs where a query accidentally triggers a write.
Search APIs
Elasticsearch, Algolia, Meilisearch, and every custom search endpoint benefits immediately. Search is inherently safe and idempotent — you are asking the index a question. QUERY makes this explicit in the protocol.
SPARQL and OData
Triple stores and OData services have always carried complex query expressions in the body. SPARQL endpoints in particular have suffered from GET URL length limits (the default SPARQL protocol over GET encodes the entire query in the URL). QUERY eliminates this constraint entirely.
CouchDB _find
CouchDB is a case study in method workarounds. Its _find endpoint uses POST with a JSON selector, and the CouchDB community has long wanted a dedicated method. In fact, the CouchDB team registered ? as an HTTP method back in 2015, waiting for a standard — QUERY is the formalisation they were waiting for.
Caveats and Migration
QUERY is not a drop-in replacement for existing patterns. Here are the considerations before adopting it:
CDN compatibility — If you rely on CloudFront or Akamai, you cannot use QUERY through them today. Plan for an upgrade path where QUERY is terminated at your origin or behind a proxy that supports it.
Framework support — Most web frameworks do not have dedicated QUERY routing yet. You can still handle it via generic method matching (r.Method == "QUERY"), but you lose the ergonomics that POST and GET have. This will improve over the next 12 months.
Existing clients — Old clients will still send POST to your query endpoints. You need to support both QUERY and POST during a transition period. The RFC recommends this explicitly — servers SHOULD continue to accept POST as a fallback for query operations.
Caching infrastructure — If you cache responses, ensure your cache layer supports body-in-cache-key. Varnish requires custom VCL. nginx requires the proxy_cache_key directive to include the body. Redis-based caches need to hash the body manually.
Security appliances — WAFs, API gateways, and intrusion detection systems may not recognise QUERY. Check your security tooling for method allowlist support and add QUERY explicitly.
The Shape of Things to Come
RFC 10008 is the first new HTTP method in sixteen years, but it will not be the last. The HTTP Working Group has resumed work on method registration with an eye toward formalising patterns that have emerged organically — WebSocket upgrade semantics, event-stream subscription patterns, and idempotent batch operations are all under discussion.
For API designers, QUERY offers something that has been missing since HTTP was first standardised: a method that is simultaneously safe, idempotent, body-carrying, and cacheable. The fact that it took thirty years to arrive says less about the difficulty of the problem and more about how creative the web community has been at working around protocol gaps. GraphQL, Elasticsearch, CouchDB, SPARQL, and every search API you have ever built were all using the wrong method because the right one did not exist.
Now it does. Start by adding Accept-Query to your OPTIONS response. Add QUERY routing alongside your existing POST handlers. Verify your CDN allows it. And the next time you design an API that asks the server a question, use the method that was designed for that purpose.