Outils, OpenAPI et exemples de code
GET /v1/openapi.json
Aucune permission, aucune clé. C'est la seule route ouverte de l'API.
| Document | Détail |
|---|---|
| Contenu | OpenAPI 3.1 : chaque route, paramètre, corps, réponse et erreur, plus x-scopes (le catalogue des permissions) et x-rate-limits |
| Génération | reflété depuis les contrôleurs au démarrage de l'API. Il décrit toujours le code qui le sert |
| Cache | Cache-Control: public, max-age=300, avec un ETag. Un If-None-Match répond 304 |
Le champ servers du document vaut /, une URL relative. Après un import, réglez la base sur https://api.freshperf.fr : sans cela, votre client construira des chemins relatifs à l'hôte courant.
| Usage | Comment |
|---|---|
| Le parcourir | freshperf.fr/api-reference. Recherche, exemples, console d'essai. Les appels partent de votre navigateur vers api.freshperf.fr |
| L'importer | Postman, Insomnia, Bruno, Hoppscotch acceptent l'URL directement. Réglez le jeton porteur au niveau de la collection |
| Générer un client | openapi-generator, openapi-typescript, oapi-codegen, Kiota |
bash
export FRESHPERF_KEY="fpk_9al41uPRzeglnYvHa3YfvsK86OfQwx6BmC2EKmI0Vhw" api() { curl -sS -H "Authorization: Bearer $FRESHPERF_KEY" "$@"; } # Chaque service avec son statut api https://api.freshperf.fr/v1/services | jq -r '.data[] | "\(.code)\t\(.status)\t\(.name)"' # En redémarrer un api -X POST -H "Content-Type: application/json" -d '{"action":"restart"}' \ https://api.freshperf.fr/v1/services/SRV-TJUAQ1-1951/power # Télécharger la dernière facture, si elle existe code=$(api "https://api.freshperf.fr/v1/invoices?limit=1" | jq -r '.data[0].code // empty') [ -n "$code" ] && api -o "$code.pdf" "https://api.freshperf.fr/v1/invoices/$code/pdf"
Python
import os, uuid, requests API = "https://api.freshperf.fr/v1" session = requests.Session() session.headers["Authorization"] = f"Bearer {os.environ['FRESHPERF_KEY']}" def get(path, **params): r = session.get(f"{API}{path}", params=params) if not r.ok: err = r.json().get("error", {}) raise RuntimeError(f"{r.status_code} {err.get('code')}: {err.get('message')} " f"(requête {r.headers.get('X-Request-Id')})") return r.json() def each(path, limit=100, **params): """Parcourt une liste à curseur. Ne convient pas à /account/notifications.""" cursor = None while True: page = get(path, cursor=cursor, limit=limit, **params) yield from page["data"] cursor = page["pagination"]["nextCursor"] if not cursor: break for service in each("/services", status="ACTIVE"): print(service["code"], service["nextBillingAt"]) # Les notifications paginent par page, avec un plafond de 50 page1 = get("/account/notifications", page=1, limit=50) print(page1["data"]["total"], len(page1["data"]["items"])) # Payer une facture par le solde. La clé d'idempotence est créée une fois # et gardée avec la tâche : c'est elle qui rend la reprise sûre. key = str(uuid.uuid4()) r = session.post(f"{API}/invoices/INV-202608-0082/pay", json={"method": "balance"}, headers={"Idempotency-Key": key}) print(r.status_code, r.json())
Node.js
const API = "https://api.freshperf.fr/v1"; async function call(method, path, body, extra = {}) { const res = await fetch(API + path, { method, headers: { Authorization: `Bearer ${process.env.FRESHPERF_KEY}`, ...(body ? { "Content-Type": "application/json" } : {}), ...extra, }, body: body ? JSON.stringify(body) : undefined, }); const json = await res.json().catch(() => ({})); if (!res.ok) { const e = json.error ?? {}; throw new Error(`${res.status} ${e.code}: ${e.message} (requête ${res.headers.get("x-request-id")})`); } return json; } const { data: me } = await call("GET", "/me"); console.log(me.key.scopes); const { data: status } = await call("GET", "/services/SRV-TJUAQ1-1951/status"); if (status.status !== "running") { await call("POST", "/services/SRV-TJUAQ1-1951/power", { action: "start" }); } // Commande payée par le solde. Les valeurs viennent du catalogue. const idempotencyKey = crypto.randomUUID(); const order = await call("POST", "/orders", { lines: [{ product: "vps-1", recurrence: "monthly", characteristics: { os: "debian-13", location: "paris", authType: "PASSWORD", authValue: process.env.VPS_ROOT_PASSWORD, }, }], payment: { method: "balance" }, }, { "Idempotency-Key": idempotencyKey }); console.log(order.data.order.orderNumber, order.data.order.serviceCodes);
Habitudes qui font gagner du temps
- La clé dans une variable d'environnement ou un gestionnaire de secrets, jamais dans le code.
- Journalisez le
X-Request-Idde toute réponse non 2xx. C'est ce que le support vous demandera. - Paginez avec
pagination.nextCursor, sauf sur/account/notificationsqui pagine parpage. - Réessayez les
429aprèsRetry-After, et les5xxavec un recul exponentiel. - Sur les quatre routes qui engagent de l'argent, gardez la même clé d'idempotence pour toutes les reprises d'une même tâche. Voir la table des refus qui libèrent la clé dans Limites de débit et idempotence.
- Sur
409 IDEMPOTENCY_IN_PROGRESS, attendezRetry-Afteret rejouez la même requête : le premier appel finira, et vous récupérerez son résultat. - Interrogez
/statustoutes les 5 à 10 secondes au plus : chaque appel interroge l'infrastructure en direct. - Relisez le journal de la clé le premier jour où l'intégration tourne. Un refus qui y traîne est une permission ou une restriction oubliée.