Durante anos, quem desenvolve APIs precisou tomar uma decisão que nem sempre parece correta:
- usar
GETe colocar todos os filtros na URL; - ou usar
POSTpara enviar uma consulta complexa no corpo da requisição.
O primeiro segue corretamente a semântica de uma operação de leitura, mas começa a ficar desconfortável quando a consulta cresce.
O segundo resolve o problema do tamanho e da estrutura dos parâmetros, mas comunica uma intenção diferente para clientes, proxies, caches e outras partes da infraestrutura HTTP.
Em junho de 2026, a IETF publicou a RFC 10008 — The HTTP QUERY Method, propondo uma solução oficial para esse espaço entre GET e POST.
Sim, agora temos um método HTTP chamado:
QUERY
E não, ele não é apenas um POST /search com um nome mais elegante.
O problema que o QUERY tenta resolver
Considere uma API de pesquisa simples:
GET /products?category=laptops&brand=example&minPrice=500&maxPrice=1500
Até aqui, tudo bem.
Agora imagine que a pesquisa precisa suportar:
- vários grupos de filtros;
- condições
ANDeOR; - intervalos de datas;
- ordenação por múltiplos campos;
- paginação;
- agregações;
- filtros geográficos;
- campos dinamicamente selecionados;
- regras aninhadas.
A URL pode rapidamente começar a parecer uma tentativa de escrever uma linguagem de programação usando apenas &, %20 e muita esperança.
GET /products?filter=%7B%22and%22%3A%5B%7B%22category%22...
Além de difíceis de ler e manter, URLs muito grandes encontram limites práticos em browsers, proxies, servidores, gateways, firewalls e ferramentas intermediárias.
A própria RFC também destaca que URLs são mais propensas a aparecer em:
- logs de acesso;
- históricos;
- bookmarks;
- ferramentas de analytics;
- sistemas intermediários.
Uma solução bastante comum é trocar o GET por POST:
POST /products/search
Content-Type: application/json
{
"category": "laptops",
"price": {
"minimum": 500,
"maximum": 1500
},
"brands": ["example", "another-brand"],
"sort": [
{
"field": "price",
"direction": "asc"
}
]
}
Tecnicamente, funciona.
O problema é semântico.
O método POST não informa, por si só, que essa operação é segura e pode ser repetida sem modificar o estado do sistema.
Para um cliente ou componente intermediário que não conhece aquela API, o POST pode representar qualquer coisa:
- criar um pedido;
- cobrar um cartão;
- enviar uma mensagem;
- iniciar um processo;
- modificar informações;
- ou simplesmente pesquisar produtos.
É nesse ponto que entra o QUERY.
Como funciona o método QUERY
A mesma consulta poderia ser representada assim:
QUERY /products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
{
"category": "laptops",
"price": {
"minimum": 500,
"maximum": 1500
},
"brands": ["example", "another-brand"],
"sort": [
{
"field": "price",
"direction": "asc"
}
]
}
A consulta continua no corpo da requisição, como aconteceria com POST.
Entretanto, o método agora comunica explicitamente que a operação:
- é uma consulta;
- é segura;
- é idempotente;
- pode ser repetida automaticamente;
- pode ter sua resposta armazenada em cache.
Podemos pensar no QUERY como uma operação com a capacidade de transportar conteúdo de um POST, mas com propriedades semânticas semelhantes às de um GET.
QUERY não significa “GET com body”
Uma possível reação seria:
Por que não enviar simplesmente um corpo em uma requisição GET?
Porque o HTTP não define uma semântica geral para conteúdo enviado em uma requisição GET.
Algumas implementações até permitem isso, mas clientes, servidores, proxies, caches e bibliotecas podem tratar esse corpo de maneiras inconsistentes ou até ignorá-lo.
O QUERY evita essa ambiguidade.
Seu corpo não é um detalhe acidental. O conteúdo e o respectivo Content-Type fazem parte da definição da consulta.
QUERY é seguro
No contexto HTTP, um método seguro é aquele no qual o cliente não solicita nem espera uma mudança no estado do recurso consultado.
Isso significa que uma chamada como:
QUERY /orders
não deveria cancelar, atualizar ou criar pedidos como parte da operação solicitada pelo cliente.
Isso não impede o servidor de realizar efeitos internos incidentais, como:
- registrar logs;
- coletar métricas;
- preencher caches;
- atualizar estatísticas operacionais;
- criar um recurso temporário que represente o resultado.
O ponto importante é que o objetivo da requisição é consultar, não modificar o recurso.
Nesse aspecto, QUERY pertence à mesma categoria semântica de GET, HEAD e OPTIONS.
QUERY é idempotente
Uma operação idempotente pode ser repetida sem produzir efeitos pretendidos adicionais além daqueles causados pela primeira execução.
Isso é particularmente importante quando existe uma falha de rede.
Imagine que o cliente enviou uma requisição, mas perdeu a conexão antes de receber a resposta. Com uma operação idempotente, a infraestrutura pode tentar novamente com mais segurança.
QUERY /telemetry
Content-Type: application/json
{
"spacecraftId": "satellite-001",
"metrics": [
"battery.voltage",
"payload.temperature"
]
}
Repetir essa consulta não deveria alterar o estado do satélite nem iniciar uma nova operação operacional.
Ela apenas solicita novamente o resultado.
Essa característica permite que clientes, bibliotecas HTTP e componentes intermediários implementem retries automáticos sem o mesmo receio que teriam com um POST.
O Content-Type é obrigatório
Na RFC 10008, o corpo é parte essencial da consulta.
Por isso, o servidor deve rejeitar uma requisição QUERY quando o header Content-Type estiver ausente ou não for consistente com o conteúdo enviado.
Exemplo válido:
QUERY /customers
Content-Type: application/json
{
"country": "GB",
"status": "active"
}
O formato não precisa obrigatoriamente ser JSON.
Um servidor poderia aceitar outros tipos de consulta:
Content-Type: application/sql
Content-Type: application/jsonpath
Content-Type: application/vnd.example.query+json
A RFC não define uma linguagem universal de pesquisa. Ela define o método HTTP e permite que cada recurso determine os formatos e as regras da consulta que suporta.
O header Accept-Query
A especificação também introduz o header de resposta Accept-Query.
Ele permite que o servidor anuncie os formatos de consulta aceitos por determinado recurso:
Accept-Query: application/json, "application/jsonpath"
Um cliente pode, assim, descobrir que o endpoint suporta QUERY e quais formatos podem ser enviados.
Uma resposta poderia ser:
HTTP/1.1 200 OK
Allow: GET, HEAD, QUERY
Accept-Query: application/json
É importante observar que Accept-Query utiliza a sintaxe de HTTP Structured Fields. Apesar de visualmente lembrar outros headers separados por vírgula, sua interpretação deve seguir as regras definidas para Structured Fields.
Tratamento de erros
A RFC sugere status codes apropriados para diferentes problemas.
400 Bad Request
Pode ser utilizado quando:
- o
Content-Typeestá ausente; - o corpo está malformado;
- o conteúdo não corresponde ao tipo declarado.
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"title": "Invalid query document",
"detail": "The request body is not valid JSON."
}
415 Unsupported Media Type
Pode ser utilizado quando o recurso não suporta o formato enviado:
HTTP/1.1 415 Unsupported Media Type
Accept-Query: application/json
422 Unprocessable Content
É apropriado quando o formato e a sintaxe são válidos, mas a consulta não pode ser processada.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"title": "Invalid query",
"detail": "The field 'customerRank' does not exist."
}
406 Not Acceptable
Pode ser retornado quando o servidor não consegue produzir uma resposta no formato solicitado pelo cliente por meio do header Accept.
QUERY pode usar cache
Respostas a QUERY podem ser armazenadas em cache.
Entretanto, há uma diferença importante em relação a GET.
Em uma requisição GET, a URI é uma das principais partes utilizadas para construir a chave do cache.
No caso de QUERY, o cache também precisa considerar:
- o corpo da requisição;
- o
Content-Type; - metadados relacionados;
- informações de negociação de conteúdo.
Considere estas duas requisições:
QUERY /products
Content-Type: application/json
{
"category": "laptops"
}
QUERY /products
Content-Type: application/json
{
"category": "monitors"
}
Apesar de utilizarem a mesma URI, são consultas diferentes e não podem compartilhar incorretamente a mesma resposta.
A chave de cache precisa incorporar o conteúdo enviado.
Isso também torna o cache de QUERY mais complexo. Um intermediário precisa ler o corpo completo antes de conseguir determinar a chave apropriada.
Normalização da chave de cache
A RFC permite que caches removam diferenças semanticamente irrelevantes antes de gerar a chave.
Por exemplo, estes dois documentos JSON podem representar a mesma consulta:
{"status":"active","country":"GB"}
{
"country": "GB",
"status": "active"
}
Um cache que compreenda corretamente o formato poderia normalizá-los para aumentar a eficiência.
No entanto, isso precisa ser feito com muito cuidado.
Uma normalização diferente daquela utilizada pelo servidor pode fazer duas consultas distintas serem consideradas iguais, resultando no retorno de uma resposta incorreta.
Em sistemas multi-tenant ou que lidam com informações sensíveis, um erro desse tipo pode se transformar em uma vulnerabilidade séria.
Location e Content-Location
Um dos pontos mais interessantes da RFC é a possibilidade de o servidor atribuir URIs à consulta ou ao seu resultado.
Content-Location para o resultado
O servidor pode informar uma URI que representa o resultado específico da consulta:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /query-results/abc123
Posteriormente, o cliente poderia recuperar esse resultado com:
GET /query-results/abc123
Isso pode ser útil para:
- relatórios caros;
- grandes conjuntos de dados;
- resultados temporários;
- respostas compartilháveis;
- processamento analítico.
Location para a consulta equivalente
O servidor também pode fornecer uma URI que represente a própria consulta:
HTTP/1.1 200 OK
Content-Type: application/json
Location: /queries/active-uk-customers
Uma chamada posterior poderia ser:
GET /queries/active-uk-customers
Nesse caso, o servidor afirma que a URI pode repetir a consulta sem que o cliente precise reenviar o corpo original.
A distinção é sutil, mas importante:
Content-Locationpode identificar o resultado produzido;Locationpode identificar um recurso equivalente à consulta executada.
Redirecionamentos
O comportamento de redirecionamentos também foi definido.
Diante de respostas 301, 302, 307 ou 308, o cliente pode repetir uma requisição QUERY no novo destino.
O comportamento histórico que às vezes transforma um POST em GET após um 301 ou 302 não deve ser aplicado ao QUERY.
Já uma resposta 303 See Other indica que o resultado pode ser obtido com um GET:
HTTP/1.1 303 See Other
Location: /reports/abc123
O cliente seguiria com:
GET /reports/abc123
Isso oferece uma solução interessante para consultas que produzem resultados persistentes ou pré-calculados.
Requisições condicionais
O método também pode utilizar headers condicionais, como:
If-None-Match: "query-result-v42"
Caso o resultado não tenha mudado, o servidor pode responder:
HTTP/1.1 304 Not Modified
Isso pode reduzir o custo de consultas analíticas caras ou resultados que mudam com pouca frequência.
E quanto à segurança?
Mover os parâmetros da URL para o corpo pode reduzir a exposição acidental das informações.
URLs frequentemente aparecem em:
- access logs;
- históricos;
- analytics;
- ferramentas de monitorização;
- sistemas de tracing;
- bookmarks;
- headers de referência.
Entretanto, isso não torna o conteúdo secreto.
O corpo da requisição ainda pode ser registrado por:
- API gateways;
- proxies;
- WAFs;
- ferramentas de observabilidade;
- sistemas de debugging;
- aplicações backend.
HTTPS, autenticação, autorização, redacção de dados e políticas adequadas de logging continuam sendo indispensáveis.
Também é necessário tomar cuidado quando o servidor cria uma URI para representar uma consulta ou seu resultado. Informações sensíveis do corpo original não devem simplesmente ser copiadas para essa URI.
QUERY e CORS
Nos browsers, QUERY não faz parte da lista de métodos considerados seguros pelo mecanismo de CORS.
Uma chamada cross-origin precisará de preflight:
OPTIONS /products HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: QUERY
Access-Control-Request-Headers: content-type
O servidor precisará autorizar explicitamente o método:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, QUERY
Access-Control-Allow-Headers: Content-Type
Para aplicações frontend, isso significa mais uma camada a ser considerada durante a implementação.
QUERY versus GET versus POST
Uma comparação simplificada ficaria assim:
| Propriedade | GET | QUERY | POST |
|---|---|---|---|
| Seguro | Sim | Sim | Potencialmente não |
| Idempotente | Sim | Sim | Potencialmente não |
| Consulta no corpo | Sem semântica geral definida | Sim | Sim |
| Resposta cacheável | Sim | Sim | Com limitações |
| Retry automático seguro | Sim | Sim | Depende da operação |
| Consulta identificada pela URI | Sim | Opcional | Normalmente não |
O QUERY não elimina GET nem POST.
Para consultas pequenas, simples e naturalmente representáveis na URL, GET continua sendo uma excelente escolha:
GET /users/42
Para comandos e operações que modificam estado, POST, PUT, PATCH e DELETE continuam cumprindo seus respectivos papéis.
O QUERY é mais interessante quando temos uma operação de leitura cuja entrada é complexa demais para ser representada de forma conveniente na URI.
Um exemplo no mundo real
Imagine uma plataforma de observabilidade que pesquisa logs de vários serviços:
QUERY /logs
Content-Type: application/vnd.example.log-query+json
Accept: application/json
{
"services": [
"payment-api",
"order-api",
"notification-worker" ],
"period": {
"from": "2026-06-24T08:00:00Z",
"to": "2026-06-24T12:00:00Z"
},
"conditions": {
"operator": "or",
"rules": [
{
"field": "level",
"operator": "equals",
"value": "error"
},
{
"field": "durationMs",
"operator": "greaterThan",
"value": 2000
}
]
},
"groupBy": [
"service",
"errorCode"
],
"limit": 100
}
Representar tudo isso na URL seria possível, mas dificilmente seria agradável.
Usar POST /logs/search funcionaria, mas perderia a capacidade de comunicar genericamente que a operação é segura e idempotente.
O QUERY expressa precisamente essa intenção.
Posso começar a utilizá-lo agora?
Tecnicamente, o método está definido e registrado.
Na prática, uma RFC publicada não significa suporte imediato em todo o ecossistema.
Antes de adoptar QUERY em produção, seria necessário testar pelo menos:
- browsers e clientes HTTP;
- bibliotecas frontend;
- frameworks backend;
- servidores web;
- reverse proxies;
- load balancers;
- CDNs;
- WAFs;
- API gateways;
- ferramentas de tracing;
- sistemas de cache;
- geradores de SDK;
- ferramentas de documentação;
- plataformas de observabilidade;
- políticas CORS.
Algumas ferramentas podem rejeitar métodos desconhecidos. Outras podem aceitá-los, mas não reconhecer suas propriedades de segurança, idempotência ou cache.
Também existe o risco de um componente intermediário permitir a requisição, mas não incluir corretamente o corpo na chave de cache.
Esse seria um problema muito mais grave do que simplesmente retornar um 405 Method Not Allowed.
Portanto, o primeiro uso do QUERY provavelmente acontecerá em ambientes controlados, nos quais clientes, servidores e infraestrutura são administrados pela mesma equipa.
E o OpenAPI?
Outro ponto prático será o suporte das ferramentas de definição de APIs.
Mesmo que um servidor aceite QUERY, o ecossistema ao redor precisa conseguir descrevê-lo corretamente:
- documentos OpenAPI;
- interfaces de Swagger;
- geração de clientes;
- validação de schemas;
- mocks;
- ferramentas de testes;
- gateways baseados em especificações.
Até que essas ferramentas tenham suporte consistente, muitas equipas continuarão usando POST /search, mesmo que QUERY seja semanticamente mais adequado.
Padrões não vencem apenas por serem tecnicamente bons. Eles precisam ser absorvidos pelo ecossistema.
O QUERY substituirá o POST para pesquisas?
Provavelmente não de imediato.
O padrão POST /search já está profundamente estabelecido. É entendido por frameworks, gateways, bibliotecas e ferramentas de documentação.
O QUERY oferece uma semântica melhor, mas a migração depende de benefícios concretos:
- retries automáticos mais seguros;
- cache intermediário;
- melhor descrição da intenção;
- descoberta de formatos com
Accept-Query; - padronização entre diferentes APIs.
Para muitas APIs internas, apenas trocar POST por QUERY sem aproveitar essas propriedades provavelmente terá pouco retorno.
Por outro lado, em plataformas públicas, APIs analíticas e sistemas distribuídos com infraestrutura sofisticada, essa clareza semântica pode ser bastante valiosa.
Minha visão
A RFC 10008 resolve um problema real.
Desenvolvedores já usam requisições com corpo para consultas complexas há anos. O que faltava não era uma maneira de fazer isso, mas uma maneira padronizada de comunicar a intenção ao restante do ecossistema HTTP.
O QUERY não aparece para tornar possível algo que era impossível.
Ele aparece para tornar explícito algo que já fazíamos de maneira ambígua.
Isso é importante porque HTTP não é apenas um transporte entre frontend e backend. Sua semântica influencia:
- retries;
- caches;
- proxies;
- segurança;
- observabilidade;
- recuperação de falhas;
- interoperabilidade.
Ao escolher POST /search, nós sabemos que aquela chamada é apenas uma consulta. O restante da infraestrutura talvez não saiba.
Com QUERY, essa informação passa a fazer parte do protocolo.
Ainda é cedo para dizer se o método será amplamente adoptado ou se ficará restrito a APIs e plataformas específicas. Seu sucesso dependerá menos da elegância da RFC e mais do suporte de browsers, frameworks, gateways, caches e ferramentas de documentação.
Mas a proposta faz sentido.
Depois de décadas escolhendo entre URLs gigantes e um POST que “na verdade não altera nada”, o HTTP finalmente ganhou uma opção criada especificamente para consultas complexas.
Agora só falta o ecossistema inteiro concordar em utilizá-la.
Sem pressão.
Referências
- RFC 10008 — The HTTP QUERY Method
- RFC 9110 — HTTP Semantics
- RFC 9111 — HTTP Caching