La mention légale, que nous avons gardée brève puisqu’elle s’applique à peine ici : nous sommes Geonode et nous vendons des proxys. Une requête POST ne nécessite aucune intervention de notre part. Tout ce qui suit fonctionne à partir de votre propre connexion vers vos propres points de terminaison. Les proxys n’entrent en jeu que bien plus tard, et vous trouverez vers la fin une brève remarque concernant la seule chose qui change réellement lorsque vous effectuez une requête POST via un proxy — ce qui n’est pas ce à quoi la plupart des gens s’attendent.
Les bases
curl -d "name=Ada&role=engineer" https://api.example.com/users
Le manuel de curl décrit l'-d, --data
: « Envoie les données spécifiées à un serveur. Pour HTTP(S), cela s'effectue via la méthode POST, de la même manière qu'un navigateur le fait lorsqu'un utilisateur a rempli un formulaire HTML et cliqué sur le bouton « Envoyer ». Cette option fait en sorte que curl transmette les données au serveur en utilisant le type de contenu application/x-www-form-urlencoded
. »
Deux choses se produisent automatiquement et sont toutes deux importantes.
**-d
implique la méthode POST.** Vous n’avez pas besoin de -X POST
, et son ajout ne change rien, sauf dans la gestion des redirections, où -X
est appliqué à chaque étape.
**-d
définit Content-Type: application/x-www-form-urlencoded
.** C’est correct pour les soumissions de formulaires, mais incorrect pour presque tout le reste.
Vous pouvez répéter -d
et curl assemblera les éléments : le manuel précise que « l’utilisation de -d name=daniel -d skill=lousy
générerait un bloc POST ressemblant à name=daniel&skill=lousy
. »
Envoi de données JSON
L'utilisation concrète la plus courante, et celle où se produisent les erreurs.
La méthode explicite :
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","role":"engineer"}'
Le raccourci que la plupart des gens ne connaissent pas. curl dispose d’une option dédiée « --json », documentée comme suit : « Envoie les données JSON spécifiées dans une requête POST au serveur HTTP. --json sert de raccourci pour transmettre ces trois options : --data-binary [arg], --header "Content-Type: application/json", --header "Accept: application/json". »
curl --json '{"name":"Ada","role":"engineer"}' https://api.example.com/users
Trois options en une, qui définit à la fois Accept et Content-Type, ce qui correspond généralement à ce que vous souhaitez. Elle permet également de lire à partir d’un fichier ou de l’entrée standard (stdin) avec @ :
curl --json @payload.json https://api.example.com/users
cat payload.json | curl --json @- https://api.example.com/users
Une mise en garde honnête tirée du manuel : « Il n’y a aucune vérification que les données transmises soient réellement au format JSON ou que la syntaxe soit correcte. » Elle définit les en-têtes ; elle ne valide pas. Un corps mal formé sera tout de même envoyé avec un type de contenu JSON, et la plainte du serveur portera sur votre JSON plutôt que sur curl.
Les en-têtes qu’il définit « peuvent être remplacés par --header comme d’habitude », vous pouvez donc conserver le raccourci et n’en modifier qu’une partie.
Les guillemets simples sont importants. Encadrez les corps JSON entre guillemets simples afin que le shell ne développe pas $ ni n’interprète les guillemets doubles à l’intérieur. Si votre JSON contient également des guillemets simples, placez-le dans un fichier.
Les cinq options de données et quand les utiliser
C'est cette partie qui dissipe la plupart des confusions, car curl propose plusieurs variantes de l'--data qui diffèrent sur des points précis.
| Option | Type de contenu défini | Spécial « @ » ? | Sauts de ligne | Utilisation |
|---|---|---|---|---|
-d / --data | form-urlencoded | Oui, lit un fichier | Supprimés | Soumissions de formulaires |
--data-raw | form-urlencoded | Non | Supprimés | Données commençant par @ |
--data-binary | form-urlencoded | Oui | Conservés | Fichiers, octets exacts |
--data-urlencode | form-urlencoded | Oui | Encodées | Valeurs contenant des caractères spéciaux |
--json | application/json | Oui | Conservées | Corps JSON |
--data-raw existe pour une seule raison : le manuel indique qu’il envoie des données « de la même manière que --data, mais sans l’interprétation particulière du caractère @ ». Si vos données littérales commencent par @ — une adresse e-mail, un identifiant, une mention —, -d tentera de les interpréter comme un nom de fichier et échouera, ce qui prête à confusion. Leur propre exemple est curl --data-raw "@at@at@".
--data-binary est l’URL à utiliser pour les fichiers. Le manuel précise : « Envoyez les données exactement telles qu’elles sont spécifiées, sans aucun traitement supplémentaire… Les sauts de ligne et les retours chariot sont conservés et aucune conversion n’est effectuée. » Notez qu’il envoie toujours application/x-www-form-urlencoded par défaut ; si vous envoyez des données binaires arbitraires, le manuel vous conseille donc de le remplacer : -H "Content-Type: application/octet-stream".
C’est pourquoi -d @file.json peut subitement ne plus fonctionner : les sauts de ligne sont supprimés. Pour le JSON, cela n’a généralement pas d’importance ; en revanche, pour tout ce où les espaces ont une signification, cela en a. --data-binary @file.json ou --json @file.json constituent la forme la plus sûre.
--data-urlencode gère les valeurs contenant &, =, des espaces ou tout autre élément susceptible de perturber l’encodage des formulaires. Le manuel documente plusieurs syntaxes, et celle que vous recherchez est presque toujours name=content, qui encode le contenu en URL tout en laissant le nom tel quel :
curl --data-urlencode "comment=hello & goodbye = fine" https://example.com/post
Sans cela, « & » serait interprété comme un séparateur de champ et votre commentaire serait tronqué sans avertissement. Il existe également « name@filename », qui charge le contenu à partir d’un fichier, l’encode en URL et ajoute « = » au nom.
Envoi de fichiers et formulaires multipart
Pour l'envoi effectif de fichiers, l'option à utiliser est « -F », qui fonctionne différemment de « -d ».
Extrait du manuel : «-F, --form <name=content> ... simulent un formulaire rempli dans lequel l'utilisateur a cliqué sur le bouton de soumission. Cela permet à curl d'envoyer des données POST en utilisant l'en-tête Content-Type multipart/form-data conformément à la norme RFC 2388. »
curl -F "file=@report.pdf" -F "title=Q3 Report" https://api.example.com/upload
Il est utile de comprendre la distinction entre « @ » et « < », car elle n’est pas intuitive. Le manuel précise : « Pour forcer la partie “content” à être un fichier, faites précéder le nom de fichier du signe @. Pour récupérer la partie “content” à partir d’un fichier, faites précéder le nom de fichier du symbole <. La différence entre @ et < réside donc dans le fait que @ permet de joindre un fichier au message sous forme de téléchargement, tandis que < crée un champ de texte et récupère le contenu de ce champ à partir d’un fichier. »
Ainsi, @ télécharge un fichier en tant que tel ; < envoie le contenu d’un fichier sous forme de valeur de champ de texte.
Pour définir un type de contenu sur une partie :
curl -F "file=@data.csv;type=text/csv" https://api.example.com/upload
Et si vous avez besoin d’une valeur littérale commençant par @ ou <, utilisez --form-string, qui n’interprète aucun de ces caractères.
Ne définissez pas vous-même Content-Type: multipart/form-data. curl le génère en incluant un paramètre de délimitation, et le remplacer entraîne une requête que le serveur ne peut pas analyser — une cause très courante de codes d’erreur 400 et 415 inexpliqués.
Pour une requête PUT simple d’un fichier, -T est plus simple : « Télécharge le fichier local spécifié vers l’URL distante… Si cette option est utilisée avec une URL HTTP(S), la méthode PUT est employée. »
Authentification et en-têtes
curl --json '{"a":1}' \
-H "Authorization: Bearer eyJhbG..." \
https://api.example.com/items
-H
est une commande réutilisable qui remplace les paramètres par défaut de curl, y compris ceux définis par --json
.
Pour l'authentification de base, utilisez -u user:password
— ou simplement -u user
, ce qui oblige curl à demander le mot de passe, évitant ainsi qu'il ne figure dans l'historique de votre shell. Le manuel précise que « sur les systèmes où cela fonctionne, curl masque l’argument d’option donné dans la liste des processus », tout en ajoutant que « cela ne suffit pas à protéger les identifiants ».
Pour une API basée sur une session, capturez et réutilisez les 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
Débogage d'une requête POST qui ne fonctionne pas
Une courte séquence qui résout presque tous les problèmes.
Vérifiez exactement ce que vous avez envoyé :
curl -v --json '{"a":1}' https://api.example.com/items 2>&1 | grep -E '^[<>]'
Les lignes commençant par >
correspondent à votre requête, tandis que celles commençant par <
correspondent à la réponse. Vérifiez que la méthode, l'Content-Type
et le corps de la requête correspondent bien à ce que vous souhaitiez envoyer. Une part surprenante des signalements du type « l'API ne fonctionne pas » trouve ici une solution.
Lisez le code d'état et le corps de l'erreur :
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
Interprétez les erreurs courantes :
| État | Signifie généralement |
|---|---|
| 400 | Corps mal formé ou champ obligatoire manquant |
| 401 | Identifiants manquants ou non valides |
| 403 | Authentifié mais non autorisé |
| 405 | Le point de terminaison n'accepte pas les requêtes POST — vérifiez l'URL et la méthode |
| 413 | Corps trop volumineux |
| 415 | Content-Type |
incorrect — l’erreur classique « -d | |
| » avec JSON | |
| 422 | Type de contenu correct, mais les données n’ont pas passé la validation |
La distinction entre les codes 415 et 422 est celle qu’il convient de bien assimiler : 415 signifie que le conteneur est incorrect, tandis que 422 signifie que c’est le contenu qui l’est. Nous avons abordé ce sujet en détail dans Qu'est-ce qu'un code d'état 415 ?.
Mettez en évidence les échecs dans vos 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
renvoie un code de sortie différent de zéro en cas d'erreurs HTTP tout en affichant le corps de la réponse, ce qui est souhaitable lorsque l'API renvoie des messages d'erreur JSON utiles. La commande simple --fail
ignore le corps de la réponse, ce qui fait perdre l'explication.
Envoi de requêtes POST via un proxy
Ce sera bref, et le point essentiel relève davantage de la mise en garde que de la technique.
curl -x http://user:pass@proxy.example.com:9000 \
--json '{"a":1}' https://api.example.com/items
Le fonctionnement reste inchangé. Ce qui change, c’est la gestion des tentatives de réessai, et c’est cet aspect qu’il convient d’examiner avant d’ajouter l’--retry
.
La requête POST n’est généralement pas idempotente. L’envoyer deux fois peut créer deux enregistrements. Par défaut, la fonction --retry` ` de `curl` ne se déclenche que dans des conditions transitoires, mais --retry-all-errors élargit considérablement ce champ d’application — et via un proxy, un code 5xx signifie souvent que la cible vous a refusé l’accès plutôt qu’elle a connu un moment de dysfonctionnement. Réessayer est au mieux inutile et, au pire, entraîne une écriture en double.
Un délai d’expiration n’est pas une preuve d’échec. Si une requête expire après avoir été reçue par le serveur, l’opération a peut-être été menée à bien alors que vous avez vu s’afficher une erreur. Via un proxy, il y a un saut supplémentaire où cela peut se produire. Si l’opération est importante, utilisez une clé d’idempotence — la plupart des API sérieuses en prennent en charge une — plutôt que de vous fier à la logique de réessai pour plus de sécurité.
Et vérifiez à quel niveau vous avez été bloqué. %{http_connect}
indique la réponse du proxy à la requête CONNECT séparément du statut de la cible :
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
signifie que le proxy a demandé des identifiants. connect=200 status=403
signifie que le proxy a fonctionné et que la cible a refusé l’accès. À chaque problème sa solution.
Questions fréquentes
Comment envoyer une requête POST avec curl ?
curl -d "key=value" URL. L'option -d implique une requête POST ; l'utilisation de -X POST est donc superflue. Elle définit également Content-Type: application/x-www-form-urlencoded, ce qui est correct pour les soumissions de formulaires mais incorrect pour le JSON.
Comment envoyer une requête POST au format JSON avec curl ?
curl --json '{"key":"value"}' URL est le raccourci : il définit --data-binary ainsi que les en-têtes Content-Type et Accept sur application/json. La forme longue est -X POST -H "Content-Type: application/json" -d '...'. Notez que --json ne valide pas votre JSON.
Pourquoi curl me renvoie-t-il l’erreur 415 « Unsupported Media Type » ?
C’est presque toujours parce que vous avez utilisé -d avec un corps JSON sans définir le type de contenu. -d envoie les données sous forme de formulaire, ce qu’une API attendant du JSON rejette. Utilisez --json, ou ajoutez -H "Content-Type: application/json".
Quelle est la différence entre -d et --data-raw ?
-d interprète un @ en début de chaîne comme « lire à partir de ce fichier ». --data-raw ne le fait pas ; c’est donc ce dont vous avez besoin lorsque vos données littérales commencent par @ — une adresse e-mail ou un identifiant, par exemple. Sinon, ils se comportent de manière identique.
Comment puis-je envoyer un fichier via une requête POST avec curl ?
Utilisez curl -F "file=@document.pdf" URL, qui envoie multipart/form-data. Utilisez @ pour joindre le fichier en tant que fichier et < pour envoyer son contenu sous forme de valeur de champ texte. Ne définissez pas vous-même l’en-tête Content-Type — curl le génère avec la délimitation requise.
Comment envoyer des données POST à partir d’un fichier ?
Utilisez curl --json @payload.json URL pour le format JSON, ou --data-binary @file pour les octets exacts, y compris les sauts de ligne. Évitez -d @file lorsque les espaces ont de l’importance, car -d supprime les sauts de ligne et les retours chariot.
Comment envoyer une valeur contenant un « & » via une requête POST ?
Utilisez --data-urlencode "field=value with & inside". Avec -d, le « & » est interprété comme un séparateur de champs et votre valeur est tronquée en silence à cet endroit.
Dois-je réessayer une requête POST ayant échoué ?
Avec prudence. La méthode POST n’est généralement pas idempotente ; une nouvelle tentative peut donc créer un doublon. De plus, un délai d’expiration ne prouve pas que le serveur n’a pas traité la requête. Utilisez une clé d’idempotence lorsque l’API le permet, et soyez prudent avec les requêtes de type « --retry-all-errors », en particulier via un proxy où un code d’erreur 5xx signifie souvent un refus plutôt qu’une erreur temporaire.
Conclusion
Tout ce sujet se résume à une seule question : quel type de contenu le point de terminaison attend-il, et votre commande le lui envoie-t-elle ?
-d envoie des données encodées au format « form », ce qui convient pour les soumissions de formulaires mais ne convient pas pour le JSON — et c’est cette seule incompatibilité qui est à l’origine de la plupart des erreurs 415 rencontrées par les utilisateurs. --json est l’option à privilégier à la place, mais elle est peu connue : un seul indicateur qui définit la gestion du corps et les deux en-têtes, avec une prise en charge @ pour les fichiers et stdin.
Au-delà de cela, les variantes existent pour des raisons spécifiques qu’il convient de garder à l’esprit. --data-raw lorsque vos données commencent par @. --data-binary lorsque les sauts de ligne ont de l’importance. --data-urlencode lorsqu’une valeur contient des caractères qui perturberaient l’encodage du formulaire. -F pour les véritables téléchargements de fichiers, avec @ pour joindre un fichier et < pour lire le contenu d’un champ de texte à partir de celui-ci.
Et en cas d’échec, exécutez la requête avec -v et lisez les lignes > avant de modifier quoi que ce soit. La requête que vous avez envoyée n’est souvent pas celle que vous pensiez avoir envoyée, et c’est dans cet écart que réside la majeure partie de la confusion dans ce domaine.
