Skip to content

HTTP forces you to lie

You need to fetch data. The query has filters, date ranges, nested fields. It’s a read — nothing changes on the server.

Query string overflowing from the browser — 414 URI Too Long

Which HTTP method do you use?

GET is semantically correct. It’s safe. It’s idempotent. Proxies can cache it. Browsers can prefetch it.

But GET doesn’t take a body.

So you shove everything into the query string:

GET /api/orders?status=pending&created_after=2026-01-01&created_before=2026-06-30&customer_id=8841&include=items,shipping,payment&sort=-created_at&fields=id,status,total,customer.name,items.sku,items.qty&page=2&per_page=50 HTTP/1.1

Works. Until it doesn’t.

Now imagine a real dashboard filter — 15 facets, 3 levels of nesting, logical operators:

GET /api/analytics/events?filter[0][field]=event_name&filter[0][op]=in&filter[0][value]=page_view,click,scroll&filter[1][field]=timestamp&filter[1][op]=gte&filter[1][value]=2026-01-01T00:00:00Z&filter[2][field]=timestamp&filter[2][op]=lte&filter[2][value]=2026-06-30T23:59:59Z&filter[3][field]=properties.country&filter[3][op]=in&filter[3][value]=BR,US,DE,JP,FR,GB,CA,AU,IN,MX&filter[4][field]=properties.device&filter[4][op]=neq&filter[4][value]=bot&group_by=event_name,properties.country&metrics=count,unique_users,avg_duration&having[0][metric]=count&having[0][op]=gte&having[0][value]=100&sort=-count&limit=500&offset=0&timezone=America/Sao_Paulo HTTP/1.1

That’s 742 characters. In a real-world scenario with UUIDs and actual field names, you’ll blow past 2000 easily.

Problem 1: URLs have limits — and nobody agrees on the number

Section titled “Problem 1: URLs have limits — and nobody agrees on the number”

The HTTP spec (RFC 9110) recommends servers accept at least 8,000 octets in the request-line.

In practice:

Component Actual limit
Chrome ~2 MB (but things break elsewhere first)
Apache (default) 8,190 bytes
Nginx (default) 4,096–8,192 bytes
IIS 16,384 bytes
CDNs (Cloudflare, AWS ALB) 8,192–16,384 bytes
Corporate proxies Unpredictable — often 2,048

The problem isn’t the browser. It’s everything between the browser and your server.

“In the wild, your link has to survive a gauntlet of browsers, proxies, CDNs, and ancient server configurations. If any one of those links in the chain decides your URL is too long, the whole thing snaps.”

A corporate proxy decides your URL is too long? 414 Request-URI Too Long. No retry. No fallback. Blank screen for the user.

Problem 2: POST lies about what’s happening

Section titled “Problem 2: POST lies about what’s happening”

When the URL gets too long, everyone does the same thing:

POST /api/orders/search HTTP/1.1
Content-Type: application/json
{
"status": "pending",
"created_after": "2026-01-01",
"created_before": "2026-06-30",
"customer_id": 8841,
"include": ["items", "shipping", "payment"],
"sort": "-created_at",
"page": 2
}

Solves the URL limit. But now you’re lying.

POST means “this request may cause side effects.” Intermediaries, CDNs, and browsers cannot cache it. If the connection drops mid-request, they cannot automatically retry. Service meshes treat it as a write. Observability tools classify it as a mutation.

The entire HTTP infrastructure — built over 30 years — interprets POST as “careful, this changes state.”

You’re not changing state. You’re reading. But the entire protocol stack thinks you’re writing.

Problem 3: Intermediaries are hostile to GET with body

Section titled “Problem 3: Intermediaries are hostile to GET with body”

“OK, but what if I send GET with a body?”

Technically, HTTP/1.1 doesn’t forbid it. But:

  • RFC 9110 (§9.3.1): “content within a GET request has no defined semantics”
  • Proxies silently strip the body — your complex filter just disappears
  • WAFs block it — “GET request with body? Likely malicious”
  • Client libraries ignore it — many drop the body from GET without warning
  • Caches don’t consider the body — different requests return the same cached response

“There is software that expects GET to not have a body, so if it encounters such an aberration, it may decide it is a bad request.” — u/azhder, r/programming (112 upvotes)

The result? Silent bugs. The request arrives without a body, returns wrong data, and you spend hours debugging why your filter “works locally but not in production.”

The most visible case. A GraphQL query is a potentially massive string:

query GetDashboardData($orgId: ID!, $dateRange: DateRangeInput!, $filters: [FilterInput!]!) {
organization(id: $orgId) {
analytics(dateRange: $dateRange, filters: $filters) {
events { name count uniqueUsers avgDuration }
topPages(limit: 20) { path views bounceRate }
funnels { name steps { label conversionRate dropoff } }
}
users(filter: { active: true, role: ADMIN }) {
edges { node { id email lastSeen permissions } }
}
}
}

With variables, serialized and URL-encoded in a GET, this easily exceeds 4KB. That’s why the GraphQL over HTTP spec recommends POST for queries. The industry standard is to accept that HTTP is broken and use the wrong method.

POST /products/_search HTTP/1.1
Content-Type: application/json
{
"query": {
"bool": {
"must": [
{ "match": { "title": "wireless headphones" } },
{ "range": { "price": { "gte": 50, "lte": 200 } } },
{ "terms": { "brand": ["Sony", "Bose", "Apple", "JBL"] } }
],
"filter": [
{ "term": { "in_stock": true } },
{ "geo_distance": { "distance": "50km", "location": "-23.55,-46.63" } }
]
}
},
"aggs": {
"price_ranges": { "range": { "field": "price", "ranges": [...] } },
"brands": { "terms": { "field": "brand", "size": 20 } }
},
"sort": [{ "_score": "desc" }, { "created_at": "desc" }],
"size": 20,
"from": 40
}

Elasticsearch used GET with body for years. Until it couldn’t — proxies and API gateways broke. Now they accept both GET and POST. The official docs say: “Both HTTP GET and HTTP POST can be used to execute search with body.”

That’s the definition of institutionalized workaround.

Every SaaS with an analytics panel, every platform with advanced search, every e-commerce with product filters — they all face the same choice:

  1. Massive URL → breaks at proxies and CDNs
  2. POST /search → loses caching, retry, and correct semantics
  3. GET with body → silent bugs in production

There’s no good option. Until now.

“Every time you filter a database or build a dashboard, you are committing an HTTP crime.” — Byte Pint, YouTube

This isn’t drama. It’s architectural reality. We’re using a protocol from 1996 that never anticipated structured queries in the body of a safe request.

The community knew this. The IETF knew this. The first draft of a method to solve this — originally called SEARCH — dates back to 2015. It took more than 10 years of discussion, revisions, and debates about semantics.

“The whole process (from its inception as SEARCH) took more than 10 years.” — u/Nimelrian, r/programming (337 upvotes)

In June 2026, RFC 10008 was published. The method is called QUERY.

Safe. Idempotent. Cacheable. With a body.


Next: How it works →

QUERY solves each of these problems. See the semantics, caching behavior, and what changes in practice.