Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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 avec curl et jq ;
  • 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

le scriptPowerShellou Bashsrv-journauxAPI Zabbixvérifie le jetonrequête HTTPSPOST /zabbix/api_jsonrpc.phpAuthorization: Bearer <jeton>Content-Type: application/json-rpc{"method": "host.get", …}réponse : code 200{"result": [{"hostid": "10084", "host": "srv-ad"}, …]}Invoke-RestMethoddes objets PowerShell :$r.result.hostcurldu texte JSON, filtré par jq :| jq -r '.result[].host'
Une requête d'API : méthode, URL, en-têtes et corps à l'aller ; code de statut et JSON au retour
ÉlémentRôleExemple
URL (point d'accès, endpoint)L'adresse de la ressourcehttps://pve1.ad.sorbier.example:8006/api2/json/nodes
MéthodeL'actionGET lire, POST créer ou appeler, PUT/PATCH modifier, DELETE supprimer
En-têtesLes informations sur la requêteAuthorization (le jeton), Content-Type: application/json, Accept
CorpsLes données envoyées{"name": "srv-test", "memory": 2048}
Code de statutLe résultat200 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 ?

powershell
$version = Invoke-RestMethod -Uri 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest'
$version.tag_name
$version.published_at
bash
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 :

powershell
$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 liste output), 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é error dans la réponse. Invoke-RestMethod ne lève donc aucune exception : le script doit tester $reponse.error lui-même.
  • severity est un code de 0 (non classé) à 5 (désastre), clock une date au format Unix (le nombre de secondes depuis le 1er janvier 1970) : [DateTimeOffset]::FromUnixTimeSeconds($p.clock).LocalDateTime la convertit.

Le même appel en Bash, pour la méthode host.get qui liste les hôtes supervisés :

bash
#!/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.

powershell
$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 } }
bash
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).

powershell
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 :

bash
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 :

bash
corps=$(jq -n --arg nom "srv-test" --argjson mem 2048 '{name: $nom, memory: $mem}')
echo "$corps"
json
{
  "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 :

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}
]}
  1. 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.
  2. Fais la même chose en Bash avec jq.

Donne les résultats.

Voir la réponseMasquer la réponse de l'exercice 1
powershell
$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
bash
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 :

text
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 :

powershell
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.

bash
#!/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
  1. 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.
  2. 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".
  3. -k dé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.
  4. /tmp/vms.json est un nom prévisible dans un dossier partagé, lisible par d'autres comptes, et rien ne vérifie que curl a réussi. Correction : set -euo pipefail, curl -sS --fail-with-body, et un fichier créé par mktemp, 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é ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Exécution à distance : PowerShell Remoting, WinRM et SSHRevoir le sommaire du cours