Chapitre 1016 minDébutant
Appeler une API REST : Invoke-RestMethod, curl et jq
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- décrire une requête d'API : URL, méthode, en-têtes, corps, code de statut ;
- appeler une API avec
Invoke-RestMethod, et aveccurletjq; - s'authentifier avec un jeton sans l'exposer ;
- interroger les API de Zabbix et de Proxmox VE et traiter leurs erreurs.
3 exercices corrigés1 schéma
Appeler une API REST, c'est envoyer une requête HTTP à une adresse (l'URL d'une ressource), avec une méthode (GET pour lire, POST pour créer…), des en-têtes (dont l'authentification) et parfois un corps en JSON, puis lire la réponse, en JSON elle aussi. Les outils d'infrastructure modernes s'administrent ainsi : Proxmox VE, Zabbix, pfSense, TrueNAS, GLPI, Wazuh exposent tous une API. En PowerShell, Invoke-RestMethod envoie la requête et transforme la réponse en objets ; en Bash, curl envoie la requête et jq lit le JSON.
Le vocabulaire d'une requête
| Élément | Rôle | Exemple |
|---|---|---|
| URL (point d'accès, endpoint) | L'adresse de la ressource | https://pve1.ad.sorbier.example:8006/api2/json/nodes |
| Méthode | L'action | GET lire, POST créer ou appeler, PUT/PATCH modifier, DELETE supprimer |
| En-têtes | Les informations sur la requête | Authorization (le jeton), Content-Type: application/json, Accept |
| Corps | Les données envoyées | {"name": "srv-test", "memory": 2048} |
| Code de statut | Le résultat | 200 OK, 201 créé, 401 non authentifié, 403 interdit, 404 introuvable, 500 erreur du serveur |
Le protocole HTTP, les codes de statut et l'authentification par jeton sont détaillés dans le cours API REST : ce chapitre se concentre sur la façon de les utiliser depuis un script.
Un premier appel : la dernière version de PowerShell
L'API publique de GitHub répond sans authentification (dans la limite de 60 requêtes par heure). C'est un bon terrain d'essai, et un vrai outil de veille : quelle est la dernière version publiée de PowerShell ?
$version = Invoke-RestMethod -Uri 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest'
$version.tag_name
$version.published_at
curl -sS https://api.github.com/repos/PowerShell/PowerShell/releases/latest | jq -r '.tag_name'
La réponse change au fil des versions (v7.6.9 début novembre 2026). Invoke-RestMethod a reconnu le JSON et renvoyé un objet dont tag_name est une propriété : aucune conversion à écrire. curl affiche le texte brut, jq en extrait le champ. Les options de curl à retenir : -s (pas de barre de progression), -S (mais affiche quand même les erreurs), -f ou --fail-with-body (code de sortie différent de 0 si le serveur répond une erreur HTTP).
Invoke-WebRequest est la commande voisine : elle renvoie la réponse HTTP complète (code de statut, en-têtes, contenu brut). Utilise Invoke-RestMethod pour les API qui répondent en JSON, Invoke-WebRequest pour télécharger un fichier (-OutFile) ou lire les en-têtes.
S'authentifier avec un jeton
Une API d'administration exige une authentification. La forme la plus courante dans un script est le jeton d'API (API token) : une longue chaîne créée dans l'interface de l'outil, associée à un compte et à des droits, envoyée dans l'en-tête Authorization de chaque requête.
Interroger Zabbix
L'API de Zabbix suit le protocole JSON-RPC : toutes les requêtes sont des POST vers la même adresse, api_jsonrpc.php, et le corps JSON dit quelle méthode appeler. Depuis Zabbix 6.4, le jeton se passe dans l'en-tête Authorization: Bearer ; l'ancienne propriété auth du corps, dépréciée, est refusée par les versions récentes : un vieux script qui l'utilise doit être corrigé. Le jeton se crée dans l'interface, menu Users > API tokens, pour un compte qui n'a que les droits de lecture nécessaires. Chez Sorbier, Zabbix tourne sur srv-journaux ; la configuration de la supervision elle-même est dans le cours Supervision.
Lister les problèmes en cours, du plus récent au plus ancien :
$url = 'https://srv-journaux.ad.sorbier.example/zabbix/api_jsonrpc.php'
$jeton = Get-Secret -Name ZabbixLecture -AsPlainText # le coffre de secrets du chapitre 15
$entetes = @{ Authorization = "Bearer $jeton" }
$corps = @{
jsonrpc = '2.0'
method = 'problem.get'
params = @{
output = @('eventid', 'name', 'severity', 'clock')
recent = $true
sortfield = 'eventid'
sortorder = 'DESC'
}
id = 1
} | ConvertTo-Json -Depth 5
$reponse = Invoke-RestMethod -Uri $url -Method Post -Headers $entetes -ContentType 'application/json-rpc' -Body $corps
if ($reponse.error) {
throw "Zabbix a refusé la requête : $($reponse.error.message) $($reponse.error.data)"
}
$reponse.result | Select-Object eventid, name, severity
Trois détails comptent :
ConvertTo-Json -Depth 5: le corps a trois niveaux (la requête,params, la listeoutput), et la profondeur par défaut de 2 aplatirait la liste en texte (chapitre 8).- Une erreur JSON-RPC (méthode inconnue, paramètre invalide, jeton expiré) revient avec le code HTTP 200 et une propriété
errordans la réponse.Invoke-RestMethodne lève donc aucune exception : le script doit tester$reponse.errorlui-même. severityest un code de 0 (non classé) à 5 (désastre),clockune date au format Unix (le nombre de secondes depuis le 1er janvier 1970) :[DateTimeOffset]::FromUnixTimeSeconds($p.clock).LocalDateTimela convertit.
Le même appel en Bash, pour la méthode host.get qui liste les hôtes supervisés :
#!/usr/bin/env bash
set -euo pipefail
url="https://srv-journaux.ad.sorbier.example/zabbix/api_jsonrpc.php"
: "${ZABBIX_TOKEN:?variable ZABBIX_TOKEN non définie}" # s'arrête si le jeton manque
corps='{"jsonrpc":"2.0","method":"host.get","params":{"output":["hostid","host"]},"id":1}'
reponse=$(curl -sS --fail-with-body -X POST "$url" \
-H "Authorization: Bearer $ZABBIX_TOKEN" \
-H "Content-Type: application/json-rpc" \
-d "$corps")
if jq -e '.error' <<< "$reponse" > /dev/null; then
echo "Erreur Zabbix : $(jq -r '.error.message + " " + .error.data' <<< "$reponse")" >&2
exit 1
fi
jq -r '.result[] | "\(.hostid) \(.host)"' <<< "$reponse"
${ZABBIX_TOKEN:?message} arrête le script avec ce message si la variable est vide ou absente. jq -e renvoie un code de sortie qui dépend du résultat : 0 si le filtre donne une valeur, 1 s'il donne null ou false, ce qui permet de tester la présence d'une erreur avec if. <<< envoie le contenu d'une variable sur l'entrée standard d'une commande.
Interroger Proxmox VE
L'API de Proxmox VE répond sur le port 8006, sous /api2/json/. Un jeton d'API se crée dans l'interface web, menu Datacenter > Permissions > API Tokens ; il appartient à un utilisateur (par exemple supervision@pve), porte un identifiant (scripts) et un secret affiché une seule fois. L'en-tête prend la forme Authorization: PVEAPIToken=UTILISATEUR@DOMAINE!IDENTIFIANT=SECRET. Toutes les réponses rangent leur contenu dans une propriété data.
$pve = 'https://pve1.ad.sorbier.example:8006/api2/json'
$secret = Get-Secret -Name PveScripts -AsPlainText
$entetes = @{ Authorization = "PVEAPIToken=supervision@pve!scripts=$secret" }
$vms = (Invoke-RestMethod -Uri "$pve/nodes/pve1/qemu" -Headers $entetes).data
$vms | Sort-Object vmid | Select-Object vmid, name, status,
@{ Name = 'MemoireGo'; Expression = { $_.maxmem / 1GB } }
curl -sS --fail-with-body \
-H "Authorization: PVEAPIToken=supervision@pve!scripts=${PVE_SECRET}" \
"https://pve1.ad.sorbier.example:8006/api2/json/nodes/pve1/qemu" \
| jq -r '.data[] | select(.status == "running") | .name'
Le point d'exclamation de l'identifiant ne pose pas de problème dans un script ; tapé dans un terminal Bash interactif entre guillemets doubles, il déclenche l'expansion de l'historique : mets alors l'en-tête entre apostrophes, ou lance set +H.
Les erreurs HTTP
Quand le serveur répond un code d'erreur (4xx ou 5xx), Invoke-RestMethod lève une erreur bloquante : on la traite avec try/catch (chapitre 7).
try {
$r = Invoke-RestMethod -Uri "$pve/nodes/pve1/qemu" -Headers $entetes
}
catch {
$statut = $_.Exception.Response.StatusCode.value__ # 401, 403, 404…
switch ($statut) {
401 { throw 'Jeton refusé : vérifier le secret ou sa date d''expiration' }
403 { throw 'Jeton valide mais droits insuffisants sur cette ressource' }
default { throw "Échec de l'appel ($statut) : $($_.Exception.Message)" }
}
}
PowerShell 7 ajoute des paramètres pratiques : -StatusCodeVariable code range le code de statut dans $code, -SkipHttpErrorCheck empêche l'exception pour traiter soi-même la réponse d'erreur, -MaximumRetryCount 3 -RetryIntervalSec 5 réessaie automatiquement en cas d'échec passager.
En Bash, curl -f (ou --fail-with-body, qui garde le corps de la réponse d'erreur) sort avec le code 22 quand le serveur répond 400 ou plus. Pour récupérer le code de statut lui-même, -w '%{http_code}' l'écrit après la réponse :
code=$(curl -sS -o reponse.json -w '%{http_code}' -H "Authorization: PVEAPIToken=…" "$url")
if [[ $code != 200 ]]; then echo "Échec HTTP $code" >&2; exit 1; fi
curl --retry 3 réessaie en cas d'erreur passagère (délai dépassé, code 429 ou 5xx).
Envoyer des données
Pour une requête POST, PUT ou PATCH, le corps est presque toujours du JSON. En PowerShell, on construit une table de hachage, puis ConvertTo-Json. En Bash, construire le JSON à la main par concaténation de chaînes est dangereux : une valeur qui contient un guillemet casse la syntaxe. jq -n le construit proprement :
corps=$(jq -n --arg nom "srv-test" --argjson mem 2048 '{name: $nom, memory: $mem}')
echo "$corps"
{
"name": "srv-test",
"memory": 2048
}
--arg passe une chaîne, --argjson une valeur JSON (un nombre, ici). Pour un corps long, on l'écrit dans un fichier et curl -d @corps.json l'envoie.
Exercices
Exercice 1 sur 3 Exploiter une réponse Réussi À revoir
L'appel GET /api2/json/nodes/pve1/qemu a renvoyé ce JSON, enregistré dans vms.json :
{"data":[
{"vmid":101,"name":"srv-ad","status":"running","maxmem":4294967296},
{"vmid":102,"name":"srv-ad2","status":"running","maxmem":4294967296},
{"vmid":110,"name":"srv-fichiers","status":"running","maxmem":8589934592},
{"vmid":190,"name":"srv-test","status":"stopped","maxmem":2147483648}
]}
- En PowerShell, à partir du fichier, affiche le nom des machines démarrées, puis la mémoire totale allouée aux quatre machines, en Go.
- Fais la même chose en Bash avec
jq.
Donne les résultats.
Voir la réponseMasquer la réponse de l'exercice 1
$r = Get-Content -Path .\vms.json -Raw | ConvertFrom-Json
($r.data | Where-Object status -eq 'running').name
($r.data | Measure-Object -Property maxmem -Sum).Sum / 1GB
jq -r '.data[] | select(.status == "running") | .name' vms.json
jq '[.data[].maxmem] | add / 1073741824' vms.json
Les deux langages affichent srv-ad, srv-ad2 et srv-fichiers (une ligne chacun), puis 18 : 4 + 4 + 8 + 2 Go, puisque 4 294 967 296 octets font 4 × 1 073 741 824. En PowerShell, .name appliqué à la liste filtrée renvoie le nom de chaque élément : c'est l'énumération des membres.
Tu avais trouvé ?
Exercice 2 sur 3 L'appel qui « marche » mais ne renvoie rien Réussi À revoir
Un script appelle l'API Zabbix avec Invoke-RestMethod. Il ne lève aucune erreur, mais $reponse.result est vide. En affichant $reponse, Moussa lit :
jsonrpc error id
------- ----- --
2.0 @{code=-32602; message=Invalid params.; data=Not authorized.} 1
Explique ce qui se passe, pourquoi aucune exception n'a été levée, et ce qu'il faut ajouter au script.
Voir la réponseMasquer la réponse de l'exercice 2
Zabbix a refusé la requête : « Not authorized » signifie que le jeton est absent, invalide, expiré ou qu'il appartient à un compte sans droits sur la méthode demandée. L'API JSON-RPC renvoie ses erreurs avec le code HTTP 200 et une propriété error dans le corps : du point de vue de HTTP, la requête a réussi, donc Invoke-RestMethod ne lève rien. Le script doit tester la réponse :
if ($reponse.error) {
throw "Zabbix : $($reponse.error.message) $($reponse.error.data)"
}
Il faut ensuite vérifier le jeton dans Users > API tokens (date d'expiration, état activé) et les droits du compte qui le porte. Si tout est correct, une dernière cause est connue : un serveur web ou un proxy placé devant Zabbix qui ne transmet pas l'en-tête Authorization ; la documentation de Zabbix décrit le réglage à faire dans ce cas.
Tu avais trouvé ?
Exercice 3 sur 3 Trouver les problèmes de sécurité Réussi À revoir
Relève les quatre problèmes de ce script, qui tourne chaque nuit sur le poste d'administration, et propose une correction pour chacun.
#!/bin/bash
TOKEN="9f2c4a7e1b..."
curl -k "https://pve1.ad.sorbier.example:8006/api2/json/nodes/pve1/qemu?token=$TOKEN" > /tmp/vms.json
NOM=$(cat /tmp/vms.json | jq .data[0].name)
echo "Première VM : $NOM"
Voir la réponseMasquer la réponse de l'exercice 3
- Le jeton est écrit dans le script : quiconque lit le fichier (ou le dépôt Git où il est rangé) peut administrer Proxmox. Il doit venir d'un coffre ou d'un fichier lisible par le seul compte qui lance le script (
chmod 600), chargé dans une variable d'environnement. - Le jeton est dans l'URL : il finit dans les journaux du serveur et de tout proxy. Et Proxmox ne lit pas le jeton à cet endroit : il attend l'en-tête
Authorization: PVEAPIToken=…. Correction :-H "Authorization: PVEAPIToken=supervision@pve!scripts=$PVE_SECRET". -kdésactive la vérification du certificat : une machine qui se fait passer pour Proxmox récupère le jeton. Il faut installer le certificat de l'autorité interne et retirer-k./tmp/vms.jsonest un nom prévisible dans un dossier partagé, lisible par d'autres comptes, et rien ne vérifie quecurla réussi. Correction :set -euo pipefail,curl -sS --fail-with-body, et un fichier créé parmktemp, ou pas de fichier du tout (reponse=$(curl …)).
Enfin, jq .data[0].name sans -r garde les guillemets ("srv-ad"), et sans apostrophes autour du filtre, le shell pourrait interpréter les crochets comme un motif de fichiers. Écris jq -r '.data[0].name'.
Tu avais trouvé ?