O HTTP tem um novo verbo para buscas
Conheça o HTTP QUERY, método seguro e idempotente para buscas com corpo. Veja como funcionam o cache e as tentativas, quais clientes o suportam e onde ele é bloqueado.
O método HTTP QUERY, definido na RFC 10008 em junho de 2026, coloca a consulta no corpo da requisição, como o POST, mas é seguro e idempotente, como o GET. Isso significa que as respostas podem ser armazenadas em cache e que uma requisição que falhou pode ser repetida automaticamente.
A maioria das equipes de API conhece bem o endpoint de busca que começa como um GET simples e acaba virando uma URL com filtros aninhados, intervalos de datas e chaves de ordenação codificados nela. Então alguém o migra para POST, e o cache e as novas tentativas (retries) param de funcionar.
Este artigo aborda o problema que o QUERY resolve, como uma troca QUERY aparece na rede e quais partes da stack já o aceitam e quais ainda o rejeitam.
Principais conclusões
- QUERY é um método HTTP seguro e idempotente que transporta a consulta no corpo da requisição. Ele foi padronizado na RFC 10008 (junho de 2026).
- Um servidor deve rejeitar uma requisição QUERY cujo Content-Type esteja ausente ou não corresponda ao corpo. Caches que armazenam respostas QUERY incluem o corpo da requisição na chave de cache.
- Scripts podem enviar QUERY com
fetch(), mas um formulário HTML commethod="query"recorre ao GET e descarta o corpo. - Um QUERY cross-origin enviado pelo navegador sempre dispara um preflight de CORS, porque QUERY não é um método CORS-safelisted.
- Qualquer servidor, proxy ou WAF que não reconheça o método pode rejeitá-lo antes que a requisição chegue à sua aplicação.
Por que os endpoints de busca ficam presos entre GET e POST?
Nem o GET nem o POST funcionam bem para buscas complexas. O GET coloca todos os filtros na URL. O POST move os filtros para o corpo, mas informa a todos os intermediários que a requisição pode alterar o estado.
A RFC 9110 pede que todo remetente e destinatário lide com URIs de pelo menos 8.000 octetos. Isso é um mínimo, não um máximo, e o HTTP não define um limite superior; portanto, qualquer proxy, gateway ou servidor no caminho pode rejeitar uma URL mais longa. Query strings longas também acabam no histórico do navegador, nos logs do servidor e nos favoritos, o que vaza o conteúdo dos filtros.
O POST evita esses problemas, mas não é seguro nem idempotente. Uma requisição idempotente tem o mesmo efeito pretendido, seja enviada uma ou dez vezes. A RFC 9110 orienta os clientes a não repetir automaticamente uma requisição não idempotente, a menos que tenham alguma forma de saber que ela é idempotente na prática. Como resultado, os caches geralmente não reutilizam respostas POST, e os gateways não repetem um POST que falha no meio do caminho.
O que é o método HTTP QUERY?
De acordo com a RFC 10008, uma requisição QUERY pede ao servidor que execute a consulta descrita no corpo da requisição e devolva o resultado, sem alterar nada no servidor. Por isso, um cliente ou proxy pode reenviar ou reiniciar um QUERY sem se preocupar com a possibilidade de uma primeira tentativa incompleta ter alterado o estado — algo que não pode ser presumido com segurança no caso do POST. Veja como a requisição aparece na rede (consulte a anatomia de uma requisição HTTP para conhecer cada parte):
QUERY /products/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{"category": "laptops", "price": {"max": 1500}, "brands": ["acme", "globex"], "sort": "price_asc"}
A consulta é definida pelo corpo em conjunto com seu media type. Se o cabeçalho Content-Type estiver ausente ou não corresponder ao conteúdo do corpo, a RFC 10008 exige que o servidor rejeite a requisição. As orientações de códigos de status da RFC 10008 indicam um código 4xx, como 400 quando nenhum media type é informado, e 415 quando o recurso não suporta o media type enviado.
Uma resposta poderia ser assim:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60
Accept-Query: application/json
Content-Location: /products/search/results/7f3a
Location: /products/search/queries/7f3a
{"results": [ ... ]}
Cada cabeçalho tem uma função específica:
- Chave de cache: respostas QUERY podem ser armazenadas em cache. Um cache que as armazena inclui o corpo da requisição na chave de cache, porque duas requisições QUERY para a mesma URL com corpos diferentes fazem perguntas diferentes.
Accept-Query: o servidor envia este cabeçalho de resposta para listar os formatos de consulta que aceita, escritos como uma lista Structured Field de media types. Um único valor vale para todas as URIs do servidor com o mesmo path, independentemente da query string.- Content-Location e Location: em uma resposta QUERY, o Content-Location identifica o resultado daquela consulta específica. O Location fornece uma URI para a própria consulta, que o cliente pode buscar depois com um GET comum. O artigo sobre o que há dentro de uma resposta HTTP explica como esses cabeçalhos normalmente se comportam.
O Cache-Control do exemplo é uma configuração de cache comum. A RFC 10008 não o exige.
Onde o QUERY já funciona hoje?
O método HTTP QUERY já funciona em vários runtimes de servidor, frameworks e clientes HTTP, desde que nada entre o cliente e o servidor o rejeite.
- Node.js aceita QUERY em seu módulo http. Seu parser embutido, o llhttp, define
HTTP_QUERY(veja a cópia dollhttp.hno Node) e reconhece o método desde o llhttp 9.2. Segundo o changelog do Node.js, essa versão do parser foi incluída na v21.7.2 e está presente na v22 e posteriores, além da v20.19.2. Nessas versões,http.METHODSinclui'QUERY'e um handler de servidor recebereq.method === 'QUERY'. - O cliente HTTP do Go aceita qualquer token de método válido como string de método em
http.NewRequest, então já consegue enviar QUERY:
package main
import (
"log"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"category":"laptops","price":{"max":1500}}`)
req, err := http.NewRequest("QUERY", "https://api.example.com/products/search", body)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
}
- Laravel: o Laravel 13.19.0 adiciona um método de cliente
Http::query()e os helpers de testequery()/queryJson()para o verbo QUERY. As rotas podem aceitar QUERY por meio deRoute::match(), com os helpers de teste adicionados no PR #60662 do laravel/framework. Um helper dedicadoRoute::query()foi incorporado ao branch master do Laravel, e não ao 13.x, portanto nenhuma versão 13.x o inclui.
No navegador, fetch() pode enviar QUERY a partir de um script na mesma origem. A issue do Fetch que acompanha o QUERY (whatwg/fetch#1938) observa que o Fetch Standard não proíbe o QUERY nem normaliza sua capitalização. Os navegadores enviam a string do método exatamente como você a escreve, então use sempre letras maiúsculas:
const res = await fetch('/products/search', {
method: 'QUERY',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ category: 'laptops', price: { max: 1500 } }),
});
const data = await res.json();
O que ainda não suporta QUERY?
Três fatores bloqueiam o método HTTP QUERY atualmente: formulários HTML não conseguem enviá-lo, alguns servidores e camadas de segurança rejeitam o método de imediato, e chamadas cross-origin exigem um preflight.
Formulários. Scripts podem enviar QUERY com fetch(), mas formulários não. A proposta para suportar method="query" é a issue #12594 do WHATWG HTML. Ela continua aberta, com o rótulo “needs implementer interest”. Até que seja implementada, este markup:
<form method="query" action="/products/search">
<input name="category" value="laptops">
<button>Search</button>
</form>
chega ao servidor como um GET, com o corpo descartado, porque os navegadores tratam um método de formulário desconhecido como GET. Uma reprodução ao vivo criada para a issue demonstra isso.
Servidores, gateways e WAFs. Qualquer intermediário (hop) que não reconheça o método pode rejeitá-lo. A mesma reprodução observa que o LiteSpeed, por exemplo, responde ao QUERY com um 400 antes mesmo de a aplicação ser executada. Reverse proxies, load balancers, CDNs, API gateways e firewalls precisam ser verificados individualmente.
Preflight cross-origin. QUERY não está na lista de métodos CORS-safelisted do Fetch Standard. Portanto, um QUERY cross-origin sempre dispara um preflight, independentemente dos cabeçalhos que carregue:
OPTIONS /products/search HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: QUERY
Access-Control-Request-Headers: content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, QUERY
Access-Control-Allow-Headers: content-type
Se Access-Control-Allow-Methods não listar QUERY, o navegador bloqueia a requisição real.
| Ambiente | Status |
|---|---|
fetch(), mesma origem | Funciona |
fetch(), cross-origin | Funciona após um preflight que permita QUERY |
| Formulários HTML | Recorre ao GET, corpo descartado |
| Node.js http | Funciona (v20.19.2+, v21.7.2+, v22+; o llhttp 9.2 reconhece QUERY) |
| Cliente HTTP do Go | Funciona (string de método personalizada) |
| Laravel 13.19+ | Funciona (cliente, testes, Route::match) |
| LiteSpeed | Rejeitado com 400 antes de a aplicação ser executada (conforme a reprodução da #12594) |
| Proxies, CDNs, gateways, WAFs | Depende do produto e da sua configuração |
O GraphQL deveria usar QUERY?
O QUERY é adequado para operações de query do GraphQL, que apenas leem dados e se encaixam em um método seguro, idempotente e que transporta um corpo. Mutations alteram o estado, portanto continuam pertencendo ao POST. Se você está comparando os dois estilos de API, o guia GraphQL vs REST aborda os prós e contras. O argumento a favor do QUERY é o mesmo em ambos: leituras do tipo busca recuperam o cache e as novas tentativas no nível do HTTP.
O que precisa acontecer para o QUERY funcionar de ponta a ponta
Para que o QUERY funcione em todos os lugares, cada camada entre o usuário e o seu handler precisa reconhecê-lo:
- A especificação HTML precisa aceitar
method="query", e os navegadores precisam implementá-lo. - Cada intermediário (servidor de origem, reverse proxy, load balancer, CDN, API gateway e WAF) precisa interpretar e encaminhar o método.
- Os frameworks precisam rotear o QUERY para os handlers e interpretar seu corpo.
- Os caches precisam incluir o corpo da requisição e o Content-Type na chave de cache antes de armazenar respostas QUERY.
O preflight de CORS é intencional: a RFC 10008 o prevê, e a whatwg/fetch#1938 não pede nenhuma alteração na lista de métodos safelisted. O que continua em aberto nessa discussão é se o cache HTTP do Fetch, baseado em URL, deve suportar cache baseado no corpo para o QUERY.
Conclusão
O QUERY corrige uma incompatibilidade antiga do HTTP: requisições de busca podem transportar um corpo sem abrir mão do cache e de novas tentativas seguras. Scripts, clientes Go e aplicações Laravel já podem usá-lo. Formulários não, e a infraestrutura que não reconhece o método ainda pode rejeitá-lo. Antes de migrar um endpoint, envie um QUERY real por todo o seu caminho de produção, incluindo CDN, gateway e WAF, e mantenha o endpoint POST em funcionamento até que todos os intermediários o deixem passar.
Perguntas frequentes
Por que não simplesmente enviar um corpo de requisição com GET em vez de usar QUERY?
Um corpo em GET não é confiável. A seção 9.3.1 da RFC 9110 afirma que um corpo em um GET não tem significado definido de forma geral e não pode alterar o alvo nem o significado da requisição. Alguns servidores rejeitam essas requisições de imediato, porque um corpo em GET pode ser usado para request smuggling. Além disso, o corpo não faz parte da chave de cache padrão. O QUERY torna o corpo a própria consulta e o inclui na chave de cache.
Qual é a diferença entre o método QUERY e o método SEARCH do WebDAV?
SEARCH é um método WebDAV, definido na RFC 5323 em 2008, para buscar recursos DAV com gramáticas de consulta em XML. QUERY é um método HTTP de uso geral para qualquer formato de consulta. Ambos os métodos são seguros, mas o SEARCH nunca se difundiu além do WebDAV. As primeiras versões preliminares da especificação do QUERY usavam o nome SEARCH. Os autores mudaram para QUERY porque o SEARCH e os outros métodos seguros existentes vêm do WebDAV e dependem de um media type XML genérico, e porque o nome QUERY corresponde à parte query de uma URI.
Posso documentar endpoints QUERY no OpenAPI?
Sim, a partir do OpenAPI 3.2.0, lançado em setembro de 2025. Essa versão adiciona suporte nativo ao método query, de modo que uma operação QUERY pode declarar um schema de requestBody e respostas, assim como get ou post. Outros métodos não padronizados ficam no novo mapa additionalOperations. O OpenAPI 3.0 e o 3.1 não têm um campo para QUERY, e o suporte das ferramentas à versão 3.2 varia de um gerador para outro.
Qual código de status um servidor retorna quando não suporta QUERY?
De acordo com a RFC 9110, um servidor que não reconhece um método de requisição deve responder com 501 Not Implemented. Um servidor que reconhece o QUERY, mas não o permite em um determinado recurso, retorna 405 Method Not Allowed, com um cabeçalho Allow listando os métodos que suporta. Proxies e WAFs podem falhar de outras formas, por exemplo com um simples 400, então qualquer lógica de fallback para POST deve tratar os três casos.