Parcourir la documentation

Outils, OpenAPI et exemples de code

GET /v1/openapi.json

Aucune permission, aucune clé. C'est la seule route ouverte de l'API.

DocumentDétail
ContenuOpenAPI 3.1 : chaque route, paramètre, corps, réponse et erreur, plus x-scopes (le catalogue des permissions) et x-rate-limits
Générationreflété depuis les contrôleurs au démarrage de l'API. Il décrit toujours le code qui le sert
CacheCache-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.

UsageComment
Le parcourirfreshperf.fr/api-reference. Recherche, exemples, console d'essai. Les appels partent de votre navigateur vers api.freshperf.fr
L'importerPostman, Insomnia, Bruno, Hoppscotch acceptent l'URL directement. Réglez le jeton porteur au niveau de la collection
Générer un clientopenapi-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-Id de toute réponse non 2xx. C'est ce que le support vous demandera.
  • Paginez avec pagination.nextCursor, sauf sur /account/notifications qui pagine par page.
  • Réessayez les 429 après Retry-After, et les 5xx avec 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, attendez Retry-After et rejouez la même requête : le premier appel finira, et vous récupérerez son résultat.
  • Interrogez /status toutes 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.
    Outils, OpenAPI et exemples de code | FreshPerf