api

POST /api/audits/:id/pdf et GET /api/pdfs/:id

Générez un PDF avec votre marque pour un audit terminé. Flux en deux étapes : POST pour lancer le rendu, puis GET le statut JSON ou le flux PDF.

Ce que font ces endpoints

La génération de PDF est un flux en deux étapes. D’abord, mettez le rendu en file avec POST /api/audits/:id/pdf. Ensuite, pollez GET /api/pdfs/:id jusqu’à status === "ready" et streamez le fichier. Le rendu prend 5 à 20 secondes parce qu’il lance un Chromium headless pour imprimer le rapport HTML white-label.

  • POST /api/audits/:id/pdf met en file un job de rendu. Le body est optionnel et accepte language (parmi en, es, de, fr, pt, it) et brand_id (un kit de marque enregistré, ou null pour le branding MetricSpot par défaut).
  • GET /api/pdfs/:id renvoie un statut JSON par défaut. Envoyez Accept: application/pdf pour streamer le fichier une fois status === "ready".
  • Les PDF brandés sont réservés au plan Pro : la marque blanche (logo, couleur, pied de page) est gatée par le plan. Les PDF MetricSpot simples sont disponibles sur tous les plans payants.
  • L’audit doit être status: "completed" avant qu’un PDF puisse être mis en file. Lancer un PDF sur un audit en cours renvoie 409.

Pourquoi c’est important

Les PDF sont le livrable des agences, freelances et consultants. Un rendu automatisé chaque nuit signifie que chaque client ouvre sa boîte mail avec un rapport frais sans que personne dans l’agence ne clique sur un bouton.

Workflows concrets :

  • Un cron d’agence parcourt chaque URL client enregistrée, appelle POST /api/audits/:id/pdf avec le brand_id de l’agence, poll jusqu’à prêt et envoie le PDF par email au client.
  • Un zap Zapier observe les nouveaux audits dans GET /api/audits, déclenche un rendu PDF et envoie le fichier dans un dossier Dropbox partagé avec le client.

Comment l’utiliser

Étape 1 : mettre le rendu en file

POST /api/audits/12345/pdf HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{ "language": "fr", "brand_id": null }

Étape 2 : poller le statut

GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx

Étape 3 : télécharger le fichier

GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/pdf

curl (flux complet)

TOKEN="ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"

# 1. Mettre en file
PDF_ID=$(curl -s -X POST https://app.metricspot.com/api/audits/12345/pdf \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{"language":"fr"}' | jq -r .pdf.id)

# 2. Poller le statut
while true; do
  STATUS=$(curl -s https://app.metricspot.com/api/pdfs/$PDF_ID \
    -H "authorization: Bearer $TOKEN" | jq -r .pdf.status)
  [ "$STATUS" = "ready" ] && break
  [ "$STATUS" = "failed" ] && { echo "rendu échoué"; exit 1; }
  sleep 2
done

# 3. Télécharger
curl -o report.pdf https://app.metricspot.com/api/pdfs/$PDF_ID \
  -H "authorization: Bearer $TOKEN" \
  -H "accept: application/pdf"

Node (mise en file + poll + téléchargement)

const headers = { authorization: "Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx" };

const queue = await fetch("https://app.metricspot.com/api/audits/12345/pdf", {
  method: "POST",
  headers: { ...headers, "content-type": "application/json" },
  body: JSON.stringify({ language: "fr" }),
});
const { pdf } = await queue.json();

while (true) {
  await new Promise((r) => setTimeout(r, 2000));
  const r = await fetch(`https://app.metricspot.com/api/pdfs/${pdf.id}`, { headers });
  const data = await r.json();
  if (data.pdf.status === "ready") break;
  if (data.pdf.status === "failed") throw new Error(data.pdf.error_message);
}

const file = await fetch(`https://app.metricspot.com/api/pdfs/${pdf.id}`, {
  headers: { ...headers, accept: "application/pdf" },
});
const buf = await file.arrayBuffer();
await Bun.write("report.pdf", buf);

Python httpx

import httpx, time, pathlib

HEADERS = {"authorization": "Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"}

r = httpx.post(
    "https://app.metricspot.com/api/audits/12345/pdf",
    headers=HEADERS, json={"language": "fr"}, timeout=30.0,
)
pdf_id = r.json()["pdf"]["id"]

while True:
    time.sleep(2)
    s = httpx.get(f"https://app.metricspot.com/api/pdfs/{pdf_id}", headers=HEADERS).json()
    if s["pdf"]["status"] == "ready":
        break
    if s["pdf"]["status"] == "failed":
        raise RuntimeError(s["pdf"]["error_message"])

resp = httpx.get(
    f"https://app.metricspot.com/api/pdfs/{pdf_id}",
    headers={**HEADERS, "accept": "application/pdf"},
    timeout=60.0,
)
pathlib.Path("report.pdf").write_bytes(resp.content)

Réponse

POST /api/audits/:id/pdf

201 Created :

{
  "pdf": {
    "id": 9876,
    "audit_id": 12345,
    "brand_id": null,
    "language": "fr",
    "status": "pending",
    "created_at": "2026-05-14T10:20:01.000Z"
  }
}

GET /api/pdfs/:id (statut JSON)

200 OK :

{
  "pdf": {
    "id": 9876,
    "audit_id": 12345,
    "status": "ready",
    "error_message": null,
    "created_at": "2026-05-14T10:20:01.000Z",
    "generated_at": "2026-05-14T10:20:18.000Z",
    "download_url": "/api/pdfs/9876/download"
  }
}

Champs :

  • status : pending (en file), rendering (Chromium en cours), ready ou failed.
  • error_message : rempli quand status === "failed".
  • generated_at : timestamp ISO de fin de rendu, null tant que pas prêt.
  • download_url : chemin relatif vers le téléchargement streamé, défini uniquement quand ready.

GET /api/pdfs/:id avec Accept: application/pdf

Stream le fichier avec Content-Type: application/pdf et Content-Disposition: attachment; filename="<domain>-audit.pdf". Le code est 200. Si le fichier est absent du disque (rare, indique un GC), renvoie 410 Gone.

Enveloppe d’erreur

{ "error": "Audit not yet complete" }

Erreurs courantes

CodeQuandAction
UNAUTHORIZED (401)Bearer manquant ou invalideCréez une clé sur app.metricspot.com/settings/api-keys
FORBIDDEN (403)Audit ou PDF appartient à un autre compteUtilisez la clé du compte propriétaire
AUDIT_NOT_FOUND (404)Audit id inexistantPassez un audit que vous possédez
INVALID_URL (400):id n’est pas un entier positifPassez un id entier
CONFLICT (409)Audit pas encore completed, ou PDF pas encore readyAttendez la fin de l’audit ; pollez le statut PDF
QUOTA_EXCEEDED (402)Le plan ne permet pas la marque blanche pour le brand_id demandéOmettez brand_id pour un PDF simple, ou passez à Pro
GONE (410)Fichier PDF absent du disqueRelancez avec POST /api/audits/:id/pdf

Questions fréquentes

Combien de temps prend le rendu ?

5 à 20 secondes. Le pipeline lance un Chromium headless, navigue vers le rapport HTML white-label, et imprime en PDF. Les graphiques riches en JS et les longues listes de findings poussent vers le haut.

Puis-je mettre en file un PDF avant la fin de l’audit ?

Non. POST /api/audits/:id/pdf renvoie 409 Conflict si l’audit est encore queued ou running. Attendez que GET /api/audits/:id rapporte status: "completed", puis lancez.

Comment obtenir un PDF brandé ?

Enregistrez un kit de marque (logo, couleur primaire, texte de pied) sur app.metricspot.com/settings/brands, puis passez son id dans brand_id. Si vous omettez brand_id, l’API applique automatiquement votre kit unique enregistré, sinon retombe sur la marque MetricSpot.

Où est stocké le PDF ?

Sur le système de fichiers du serveur, derrière la route auth-gated GET /api/pdfs/:id/download. L’URL de téléchargement signée fait partie de la réponse JSON de statut et n’est servie qu’au compte propriétaire de l’audit.

Sources

Dernière mise à jour 2026-05-14