For years, we’ve been abusing HTTP.
Not in a catastrophic way, but in a subtle, “everyone does it so it’s fine” kind of way. The biggest offender? Using POST for read-only operations just because GET wasn’t flexible enough.
RFC 10008 introduces a new HTTP method — QUERY — that finally fixes this mismatch. It gives us a clean, semantically correct way to perform read-only operations with a request body.
If you’ve ever built a search endpoint, analytics API, or filtering system, this is for you.
The Problem We’ve Been Ignoring
HTTP gives us two obvious tools:
GET→ safe, cacheable, no request body (in practice)POST→ allows body, but implies side effects
Now imagine building a real-world API:
- Complex filters
- Nested conditions
- JSON payloads
- Possibly even SQL-like queries
You can squeeze that into a query string… but it gets ugly fast:
GET /orders?filter=status:paid,amount>1000,created_at>2025-01-01&sort=-created_at
Or worse:
GET /search?q=%7B%22filters%22%3A%5B...%5D%7D
So what do most teams do?
They switch to:
POST /orders/search
Even though:
- It doesn’t create anything
- It doesn’t change state
- It’s fully safe to retry
This is where semantics break down.
Enter QUERY
QUERY is essentially:
“Like
POST, but safe and idempotent likeGET.”
That’s it. That’s the missing piece.
You can send a request body, but:
- No side effects are expected
- It can be retried safely
- It can be cached (in principle)
- It accurately represents a read operation
A Simple Comparison
Think of it like this:
GET→ “Fetch this resource”QUERY→ “Evaluate this query and return the result”POST→ “Execute this action”
That distinction becomes very real in API design.
Example 1: From Hacky GET to Clean QUERY
Old approach (GET)
GET /products?category=running&price_min=50&price_max=200&sort=rating
Fine… until it’s not.
Better approach (QUERY)
QUERY /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"category": "running",
"price": { "min": 50, "max": 200 },
"sort": "rating"
}Same intent, but:
- Easier to evolve
- Easier to validate
- No URL length concerns
- No encoding nightmares
Example 2: Complex Filtering (Where QUERY Shines)
This is where QUERY really earns its existence.
QUERY /orders HTTP/1.1
Content-Type: application/json
{
"filters": [
{ "field": "status", "op": "eq", "value": "paid" },
{ "field": "total", "op": "gt", "value": 1000 }
],
"date_range": {
"from": "2025-01-01",
"to": "2025-03-31"
},
"sort": ["-created_at"],
"limit": 50
}Try putting that into a query string without making your future self miserable.
Example 3: Query Languages (SQL / JSONPath)
If your API supports expressive queries, QUERY is a perfect match.
SQL-style
QUERY /analytics HTTP/1.1
Content-Type: application/sql
SELECT country, COUNT(*) as users
FROM sessions
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY countryJSONPath-style
QUERY /documents HTTP/1.1
Content-Type: application/jsonpath
$..items[?(@.price > 100 && @.inStock == true)]These are clearly read-only operations. Using POST here was always a semantic compromise.
A Subtle but Powerful Feature
RFC 10008 also allows something interesting:
The server can return a Location header that represents the query.
HTTP/1.1 200 OK
Location: /queries/abc123
Content-Location: /results/xyz789This enables patterns like:
- Save a query once
- Reuse it via
GET - Cache results more effectively
- Share query URLs
This is particularly useful for:
- dashboards
- saved reports
- analytics queries
When You Should Use QUERY
Use QUERY when:
- The operation is strictly read-only
- The request body is necessary (complex filters, large payloads)
- You care about correct HTTP semantics
- You want safe retries and better cache compatibility
When You Should NOT Use It
Do not use QUERY when:
- You are creating or modifying resources
- The operation has side effects
- A simple
GETwith query params is sufficient
QUERY is not a replacement for GET. It’s a complement.
The Real Impact
This RFC doesn’t introduce a new capability — we could already do all of this with POST.
What it does is more important:
It gives us a standard, semantically correct way to model a very common pattern.
And that matters for:
- API clarity
- client expectations
- caching layers
- tooling (OpenAPI already supports it)
- long-term maintainability
In other words, this is one of those small HTTP changes that quietly fixes a decade-old design smell.