~/wiki

Cheatsheets — vue longue

retour à la liste

Toutes les pages concaténées sur un seul document, pour un Ctrl-F direct.

Les flags qui couvrent 90 % des cas

Flag Effet
-X POST méthode (inutile avec -d, qui implique POST)
-H "K: V" un header, répétable
-d '{"a":1}' corps de la requête
-G envoie les -d en query string au lieu du corps
--data-urlencode "q=a b" encode la valeur
-s silencieux, pas de barre de progression
-S affiche quand même les erreurs (à coupler avec -s)
-i inclut les headers de réponse
-I headers seulement, requête HEAD
-L suit les redirections
-o fichier écrit dans un fichier
-O garde le nom distant
-u user:pass authentification basique
--max-time 30 timeout global
-w '%{http_code}' formate une sortie après coup

POST JSON, le cas de référence

curl -sS -X POST "https://api.example.com/v1/sessions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "a_123", "task": "trouver le produit"}'
{"id":"s_88fe21","status":"queued","created_at":"2026-08-07T14:12:03Z"}

Sans Content-Type: application/json, beaucoup d'API répondent 415 Unsupported Media Type. C'est l'oubli le plus fréquent.

Corps trop long pour la ligne de commande : -d @payload.json lit un fichier.

GET avec des paramètres

curl -sS -G "https://api.example.com/v1/items" \
  --data-urlencode "q=chaussure noire" \
  --data-urlencode "page=2"

-G évite d'encoder soi-même les espaces et les accents.

Lire la réponse

curl -s https://api.github.com/repos/astral-sh/uv | jq '.stargazers_count, .license.name'
62841
"Apache License 2.0"
curl -s https://api.example.com/items | jq '.items[] | {id, name}'
curl -s https://api.example.com/items | jq -r '.items[].name'   # -r : sans guillemets

Debug

curl -i https://example.com          # headers + corps
curl -I https://example.com          # headers seuls
curl -v https://example.com          # tout le dialogue, TLS compris
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://example.com
200 0.184s

-w sert à mesurer sans polluer la sortie. Autres variables utiles : %{size_download}, %{redirect_url}, %{content_type}.

Fichiers

curl -O https://example.com/data.csv           # garde le nom
curl -o local.csv https://example.com/data.csv
curl -L -o file.zip https://example.com/dl     # suit la redirection
curl -F "file=@photo.jpg" -F "note=test" https://api.example.com/upload

-F fait du multipart, -d fait du form-urlencoded ou du brut : ce ne sont pas les mêmes requêtes.

Le pipe vers bash

curl -LsSf https://astral.sh/uv/install.sh | sh

Correct pour un installeur officiel. Pour tout le reste, lire d'abord :

curl -sSL https://exemple.com/install.sh | less

Équivalences avec Python

curl requests
-X POST requests.post(...)
-H "K: V" headers={"K": "V"}
-d '{"a":1}' json={"a": 1}
-G --data-urlencode "q=x" params={"q": "x"}
-u user:pass auth=("user", "pass")
--max-time 30 timeout=30
-L allow_redirects=True (défaut)

Astuce navigateur : dans l'onglet Réseau des DevTools, clic droit sur une requête puis « Copy as cURL » reproduit l'appel complet, cookies et headers inclus.

See also

Python requests

page dédiée →

Client HTTP synchrone. Pas de timeout par défaut — toujours le passer explicitement.

Le squelette à écrire de mémoire

import os
import requests

BASE = "https://api.example.com/v1"

r = requests.post(
    f"{BASE}/environments",
    headers={
        "Authorization": f"Bearer {os.environ['API_KEY']}",
        "Content-Type": "application/json",
    },
    json={"name": "demo", "url": "https://example.com"},
    timeout=30,
)
r.raise_for_status()
print(r.status_code, r.json())

json= sérialise le dict et pose Content-Type: application/json tout seul. data= envoie du form-urlencoded ou des bytes bruts — c'est la confusion la plus fréquente.

Réponse

Attribut Contenu
r.status_code entier, 200, 404
r.ok True si status_code < 400
r.json() corps parsé, lève JSONDecodeError si ce n'est pas du JSON
r.text corps brut décodé
r.headers dict insensible à la casse
r.raise_for_status() lève HTTPError sur 4xx/5xx, ne fait rien sinon

Session : réutiliser la connexion et les headers

with requests.Session() as s:
    s.headers.update({"Authorization": f"Bearer {os.environ['API_KEY']}"})
    for page in range(1, 6):
        r = s.get(f"{BASE}/items", params={"page": page}, timeout=30)
        r.raise_for_status()
        print(r.json()["items"])

params= construit la query string, pas besoin de la concaténer à la main.

Polling d'un job asynchrone

import time

def wait_for(session_id: str, timeout_s: int = 300, every_s: int = 3) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{BASE}/sessions/{session_id}", timeout=30)
        r.raise_for_status()
        payload = r.json()
        if payload["status"] in {"completed", "failed"}:
            return payload
        time.sleep(every_s)
    raise TimeoutError(f"session {session_id} toujours en cours après {timeout_s}s")

Erreurs à distinguer

try:
    r = requests.get(url, timeout=10)
    r.raise_for_status()
except requests.Timeout:              # dépassement du timeout
    ...
except requests.ConnectionError:      # DNS, refus de connexion, réseau
    ...
except requests.HTTPError as exc:     # 4xx / 5xx après raise_for_status
    print(exc.response.status_code, exc.response.text[:200])

Toutes héritent de requests.RequestException.

Charger la clé depuis un .env

from dotenv import load_dotenv   # pip install python-dotenv
load_dotenv()
key = os.environ["API_KEY"]      # KeyError explicite si absente

os.environ["X"] lève si la variable manque, os.getenv("X") renvoie None en silence. Préférer le premier pour une clé obligatoire.

Debug rapide

print(r.request.method, r.request.url)
print(r.request.headers)
print(r.request.body)

Équivalences avec curl

curl requests
-X POST requests.post(...)
-H "K: V" headers={"K": "V"}
-d '{"a":1}' json={"a": 1}
-G --data-urlencode "q=x" params={"q": "x"}
-u user:pass auth=("user", "pass")
--max-time 30 timeout=30

See also

  • httpx — même API, plus l'async et un timeout par défaut