Geonode logo
Geonode Team

Geonode Team

Atualizado: 7 de outubro de 2026

Publicado: 2 de setembro de 2026

Como enviar um pedido POST com o curl (+exemplos)

`curl -d "key=value" https://example.com` envia um POST. Essa é toda a resposta básica e é também a origem do erro mais comum com que as pessoas se deparam a seguir. `-d` define um tipo de conteúdo «form-encoded». Se enviar JSON com esse tipo de conteúdo e ignorar o cabeçalho, muitas APIs irão rejeitar o pedido com um código de erro 415. Este guia aborda dados de formulário, JSON — incluindo o atalho que a maioria das pessoas desconhece —, o envio de ficheiros e como determinar qual das várias opções de dados é que realmente pretende.

A declaração, que vou manter breve, uma vez que quase não se aplica aqui: somos a Geonode e vendemos proxies. Um pedido POST não requer nada da nossa parte. Tudo o que se segue funciona a partir da sua própria ligação, dirigindo-se aos seus próprios pontos finais. Os proxies só entram em cena muito mais tarde, e há uma breve nota no final sobre a única coisa que muda realmente quando se envia um pedido POST através de um proxy — o que não é o que a maioria das pessoas espera.

Noções básicas

curl -d "name=Ada&role=engineer" https://api.example.com/users

O manual do curl descreve a opção -d, --data

: «Envia os dados especificados para um servidor. Para HTTP(S), isto é feito com o método POST, da mesma forma que um navegador faz quando um utilizador preenche um formulário HTML e clica no botão de envio. Esta opção faz com que o curl passe os dados para o servidor utilizando o tipo de conteúdo application/x-www-form-urlencoded

.”

Acontecem duas coisas automaticamente e ambas são importantes.

**-d

implica POST.** Não é necessário utilizar -X POST

, e adicioná-lo não altera nada, exceto no tratamento de redirecionamentos, onde -X

é aplicado a cada salto.

**-d

define Content-Type: application/x-www-form-urlencoded

.** Isto está correto para envios de formulários e errado para quase tudo o resto.

Pode repetir -d

e o curl une as partes: o manual refere que «utilizar -d name=daniel -d skill=lousy

geraria um bloco POST semelhante a name=daniel&skill=lousy

.»

Envio de JSON

A utilização prática mais comum e onde surgem os erros.

A forma explícita:

curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada","role":"engineer"}'

O atalho que a maioria das pessoas ainda não conhece. O curl tem uma opção dedicada «--json», documentada da seguinte forma: «Envia os dados JSON especificados numa solicitação POST para o servidor HTTP. --json funciona como um atalho para passar estas três opções: --data-binary [arg], --header "Content-Type: application/json", --header "Accept: application/json".»

curl --json '{"name":"Ada","role":"engineer"}' https://api.example.com/users

Três opções numa só, e define «Accept», bem como «Content-Type», que é normalmente o que se pretende. Também lê a partir de um ficheiro ou do stdin com «@»:

curl --json @payload.json https://api.example.com/users
cat payload.json | curl --json @- https://api.example.com/users

Uma advertência honesta do manual: «Não há verificação de que os dados passados sejam realmente JSON ou de que a sintaxe esteja correta.» Define cabeçalhos; não valida. Um corpo malformado continua a ser enviado com um tipo de conteúdo JSON, e a reclamação do servidor será sobre o seu JSON e não sobre o curl.

Os cabeçalhos que define «podem ser substituídos com --header, como habitualmente», pelo que pode manter o atalho e ajustar apenas uma parte do mesmo.

As aspas simples são importantes. Coloque os corpos JSON entre aspas simples para que o shell não expanda $ nem interprete as aspas duplas no interior. Se o seu JSON também contiver aspas simples, coloque-o num ficheiro.

As cinco opções de dados e quando utilizar cada uma

Esta é a parte que esclarece a maior parte das dúvidas, uma vez que o curl dispõe de várias variantes de «--data» que diferem em aspetos específicos.

OpçãoTipo de conteúdo definidoÉ especial para «@»?Novas linhasUtilizar para
-d / --dataform-urlencodedSim, lê um ficheiroRemovidosEnvios de formulários
--data-rawform-urlencodedNãoRemovidosDados que começam por @
--data-binaryform-urlencodedSimPreservadosFicheiros, bytes exatos
--data-urlencodeform-urlencodedSimCodificadosValores com caracteres especiais
--jsonapplication/jsonSimPreservadosCorpos JSON

--data-raw existe por uma razão: o manual indica que envia dados «de forma semelhante a --data, mas sem a interpretação especial do caractere @». Se os seus dados literais começarem por @ — um endereço de e-mail, um nome de utilizador, uma menção — -d tentará lê-los como um nome de ficheiro e falhará, o que pode causar confusão. O exemplo que eles próprios dão é curl --data-raw "@at@at@".

--data-binary é o endereço a utilizar para ficheiros. O manual: «Envie os dados exatamente como especificado, sem qualquer tipo de processamento adicional... as novas linhas e os retornos de carro são preservados e nunca são feitas conversões.» Note-se que, por predefinição, continua a enviar application/x-www-form-urlencoded; por isso, se estiver a enviar dados binários arbitrários, o manual indica que deve substituir essa predefinição: -H "Content-Type: application/octet-stream".

É por isso que -d @file.json pode falhar de forma subtil: as novas linhas são removidas. Para JSON, isso normalmente não importa; para qualquer coisa em que os espaços em branco sejam significativos, importa sim. --data-binary @file.json ou --json @file.json é a forma mais segura.

--data-urlencode lida com valores que contenham &, =, espaços ou qualquer outra coisa que possa comprometer a codificação do formulário. O manual documenta várias sintaxes, e a que se pretende é quase sempre name=content, que codifica o conteúdo como URL e deixa o nome inalterado:

curl --data-urlencode "comment=hello & goodbye = fine" https://example.com/post

Sem isso, o «&» seria interpretado como um separador de campos e o seu comentário seria truncado sem aviso prévio. Existe também o «name@filename», que carrega o conteúdo a partir de um ficheiro, codifica-o para URL e acrescenta «=» ao nome.

Envio de ficheiros e formulários multiparte

Para o envio efetivo de ficheiros, a opção é -F, que funciona de forma diferente de -d.

O manual: «-F, --form <name=content>... simula um formulário preenchido no qual o utilizador clicou no botão de envio. Isto faz com que o curl envie dados POST utilizando o Content-Type multipart/form-data, de acordo com a RFC 2388.»

curl -F "file=@report.pdf" -F "title=Q3 Report" https://api.example.com/upload

Vale a pena compreender a distinção entre @ e <, pois não é intuitiva. O manual: «Para forçar a parte “content” a ser um ficheiro, anteponha ao nome do ficheiro o símbolo @. Para obter a parte “content” a partir de um ficheiro, anteponha ao nome do ficheiro o símbolo <. A diferença entre @ e < reside, portanto, no facto de @ fazer com que um ficheiro seja anexado na publicação como um upload de ficheiro, enquanto < cria um campo de texto e obtém o conteúdo desse campo a partir de um ficheiro.»

Assim, @ carrega um ficheiro como tal; < envia o conteúdo de um ficheiro como valor de um campo de texto.

Para definir um tipo de conteúdo numa parte:

curl -F "file=@data.csv;type=text/csv" https://api.example.com/upload

E se precisar de um valor literal que comece por @ ou <, utilize --form-string, que não interpreta nenhum desses caracteres.

Não defina Content-Type: multipart/form-data manualmente. O curl gera-o incluindo um parâmetro de delimitação, e substituí-lo produz uma solicitação que o servidor não consegue analisar — uma causa muito comum de erros 400 e 415 inexplicáveis.

Para um PUT simples de um ficheiro, -T é mais simples: «Carregar o ficheiro local especificado para o URL remoto... Se esta opção for utilizada com um URL HTTP(S), é utilizado o método PUT.»

Autenticação e cabeçalhos

curl --json '{"a":1}' \
  -H "Authorization: Bearer eyJhbG..." \
  https://api.example.com/items

-H é repetível e substitui as predefinições do curl, incluindo as definidas por --json .

Para autenticação básica, -u user:password — ou apenas -u user , o que faz com que o curl solicite a palavra-passe, para que esta não fique registada no histórico do shell. O manual refere que «em sistemas onde funciona, o curl oculta o argumento da opção especificada das listas de processos», acrescentando que «isto não é suficiente para proteger as credenciais».

Para uma API baseada em sessão, capture e reutilize os cookies:

curl -c jar.txt -d "user=ada&pass=secret" https://example.com/login
curl -b jar.txt --json '{"a":1}' https://example.com/api/items

Depurar um POST que não funciona

Uma sequência curta que resolve quase tudo.

Veja exatamente o que enviou:

curl -v --json '{"a":1}' https://api.example.com/items 2>&1 | grep -E '^[<>]'

As linhas que começam por > correspondem ao seu pedido, enquanto < corresponde à resposta. Confirme se o método, o Content-Type e o corpo da mensagem são os que pretendia. Uma percentagem surpreendente de relatos do tipo «a API não funciona» resolve-se aqui.

Leia o código de estado e o corpo do erro:

curl -sS -o body.txt -D headers.txt --json '{"a":1}' https://api.example.com/items
head -1 headers.txt; head -c 300 body.txt

Interprete as falhas mais comuns:

EstadoSignifica normalmente
400Corpo malformado ou campo obrigatório em falta
401Credenciais em falta ou inválidas
403Autenticado, mas sem permissão
405O endpoint não aceita POST — verifique o URL e o método
413Corpo demasiado grande
415«Content-Type
» incorreto — o clássico erro «-d
» com JSON
422Tipo de conteúdo correto, mas os dados não passaram na validação

A distinção entre 415 e 422 é aquela que vale a pena interiorizar: 415 significa que o «wrapper» está errado, 422 significa que o conteúdo está. Abordámos este tema em detalhe em o que é um código de estado 415.

Destaque as falhas nos scripts:

curl --fail-with-body --silent --show-error \
     --connect-timeout 5 --max-time 30 \
     --json @payload.json https://api.example.com/items

--fail-with-body devolve um código de saída diferente de zero em caso de erros HTTP, ao mesmo tempo que continua a apresentar o corpo da resposta, o que é o desejável quando a API devolve mensagens de erro JSON úteis. O simples --fail descarta o corpo, o que faz com que a explicação seja perdida.

Converter um pedido do navegador em curl

A forma mais rápida de reproduzir um pedido POST que funciona no navegador mas não no seu código — e uma técnica que a maioria das pessoas descobre muito mais tarde do que deveria.

Copie-o das ferramentas de programador. No Chrome, Firefox e Safari, abra o separador «Rede», localize a solicitação, clique com o botão direito do rato e selecione «Copiar como cURL». Obterá um comando completo com todos os cabeçalhos, cookies e corpo que o navegador enviou. Cole-o num terminal e deverá comportar-se exatamente da mesma forma.

Isso responde imediatamente à questão subjacente à maioria das sessões de depuração: o problema é a solicitação ou é o meu código? Se o comando copiado funcionar e o seu não, a diferença está no que está a enviar, e agora tem ambas as versões lado a lado para comparar.

Depois, simplifique-o. Um comando copiado contém normalmente trinta cabeçalhos, a maioria dos quais irrelevantes. Remova-os aos poucos e volte a executar até que o comando falhe. O que resta é o conjunto mínimo que o servidor realmente requer, e é isso que deve constar na sua aplicação:

curl 'https://api.example.com/items' \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer eyJhbG...' \
  --data-raw '{"name":"Ada"}'

Note que os navegadores exportam --data-raw em vez de -d, precisamente porque um corpo que comece por @ seria, de outra forma, interpretado erroneamente como um nome de ficheiro.

Esteja atento a duas coisas que não sobreviverão à cópia. Os cookies são incluídos como um cabeçalho literal e irão expirar. E tudo o que a página calculou em JavaScript — um token CSRF, uma assinatura, um valor derivado de um carimbo temporal — é incorporado no comando copiado como uma cadeia de caracteres fixa, pelo que funciona uma vez e depois deixa de funcionar. Se um pedido reproduzido for bem-sucedido e depois falhar na segunda execução, essa é quase sempre a razão, e a solução consiste em obter o token em vez de o codificar de forma rígida.

Por outro lado, várias ferramentas convertem um comando curl em código para a maioria das linguagens, o que é uma forma razoável de passar de um comando funcional para um cliente funcional sem ter de voltar a digitar os cabeçalhos manualmente.

Envio de POST através de um proxy

Resumidamente, o ponto importante é mais uma advertência do que uma técnica.

curl -x http://user:pass@proxy.example.com:9000 \
     --json '{"a":1}' https://api.example.com/items

O funcionamento não sofre alterações. O que muda é o cálculo das tentativas de reenvio, e esta é a parte que vale a pena ter em conta antes de adicionar --retry .

O POST geralmente não é idempotente. Enviá-lo duas vezes pode criar dois registos. A opção «--retry » do curl só é ativada em condições transitórias por predefinição, mas «--retry-all-errors » alarga consideravelmente esse âmbito — e, através de um proxy, um código de erro 5xx significa frequentemente que o destino recusou o pedido, em vez de ter tido um momento de falha. Tentar novamente é, na melhor das hipóteses, inútil e, na pior, duplica uma gravação.

Um tempo de espera não é prova de falha. Se um pedido atingir o tempo de espera depois de o servidor o ter recebido, a operação pode ter sido concluída enquanto se via um erro. Através de um proxy, existe um salto adicional onde isto pode acontecer. Se a operação for importante, utilize uma chave de idempotência — a maioria das APIs sérias suporta uma — em vez de confiar na lógica de repetição para garantir a segurança.

E verifique qual o salto que recusou o seu pedido. %{http_connect} apresenta a resposta do proxy ao CONNECT separadamente do estado do destino:

curl -sS -o /dev/null -x "$PROXY" \
  -w 'connect=%{http_connect} status=%{response_code}\n' \
  --json '{"a":1}' https://api.example.com/items

connect=407 significa que o proxy solicitou credenciais. connect=200 status=403 significa que o proxy funcionou e o destino recusou. Problemas diferentes, soluções diferentes.

Perguntas frequentes

Como envio um pedido POST com o curl?

curl -d "key=value" URL. A opção -d implica um pedido POST, pelo que -X POST é desnecessário. Além disso, define Content-Type: application/x-www-form-urlencoded, o que é correto para o envio de formulários, mas incorreto para JSON.

Como envio um pedido POST com JSON usando o curl?

curl --json '{"key":"value"}' URL é o atalho — define --data-binary e, além disso, define os cabeçalhos Content-Type e Accept como application/json. A forma mais longa é -X POST -H "Content-Type: application/json" -d '...'. Note que --json não valida o seu JSON.

Por que recebo o erro 415 Unsupported Media Type do curl?

Quase sempre porque utilizou -d com um corpo JSON sem definir o tipo de conteúdo. -d envia dados codificados como formulário, e uma API que espera JSON rejeita-os. Utilize --json ou adicione -H "Content-Type: application/json".

Qual é a diferença entre -d e --data-raw?

-d interpreta um @ inicial como «ler a partir deste ficheiro». --data-raw não o faz, pelo que é o que precisa quando os seus dados literais começam com @ — um endereço de e-mail ou um identificador, por exemplo. Fora isso, comportam-se de forma idêntica.

Como faço para carregar um ficheiro com o POST do curl?

curl -F "file=@document.pdf" URL, que envia multipart/form-data. Utilize @ para anexar o ficheiro como um ficheiro e < para enviar o seu conteúdo como um valor de campo de texto. Não defina manualmente o cabeçalho Content-Type — o curl gera-o com o delimitador necessário.

Como posso enviar dados POST a partir de um ficheiro?

Utilize curl --json @payload.json URL para JSON, ou --data-binary @file para bytes exatos, incluindo novas linhas. Evite -d @file quando os espaços em branco forem importantes, porque -d remove novas linhas e retornos de carro.

Como posso enviar um valor que contenha um símbolo «&» (ampersand) através de um pedido POST?

Utilize --data-urlencode "field=value with & inside". Com o -d simples, o símbolo «&» é interpretado como um separador de campos e o seu valor é truncado silenciosamente nesse ponto.

Devo tentar novamente um pedido POST que falhou?

Com cuidado. O POST geralmente não é idempotente, pelo que uma nova tentativa pode criar uma duplicação — e um tempo de espera esgotado não prova que o servidor não tenha processado o pedido. Utilize uma chave de idempotência sempre que a API a suportar e tenha cuidado com --retry-all-errors, especialmente através de um proxy, onde um código de erro 5xx significa frequentemente uma recusa, em vez de uma falha transitória.

Conclusão

Todo o assunto resume-se a uma pergunta: que tipo de conteúdo é que o endpoint pretende e será que o seu comando o envia?

-d envia dados codificados como «form», o que é adequado para envios de formulários, mas inadequado para JSON — e essa única incompatibilidade está na origem da maioria dos erros 415 com que as pessoas se deparam. --json é a opção a utilizar em vez disso, e é pouco conhecida: um sinalizador que define o tratamento do corpo e ambos os cabeçalhos, com suporte @ para ficheiros e stdin.

Para além disso, existem variantes por razões específicas que vale a pena recordar. --data-raw quando os seus dados começam por @. --data-binary quando as quebras de linha são importantes. --data-urlencode quando um valor contém caracteres que iriam quebrar a codificação do formulário. -F para uploads de ficheiros propriamente ditos, com @ para anexar um ficheiro e < para ler um campo de texto a partir de um ficheiro.

E quando algo falhar, execute-o com -v e leia as linhas > antes de alterar qualquer coisa. A solicitação que enviou não é, frequentemente, a solicitação que pensava ter enviado, e é nessa discrepância que reside a maior parte da confusão nesta área.