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/pdfmet en file un job de rendu. Le body est optionnel et acceptelanguage(parmien,es,de,fr,pt,it) etbrand_id(un kit de marque enregistré, ounullpour le branding MetricSpot par défaut).GET /api/pdfs/:idrenvoie un statut JSON par défaut. EnvoyezAccept: application/pdfpour streamer le fichier une foisstatus === "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 renvoie409.
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/pdfavec lebrand_idde 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),readyoufailed.error_message: rempli quandstatus === "failed".generated_at: timestamp ISO de fin de rendu,nulltant que pas prêt.download_url: chemin relatif vers le téléchargement streamé, défini uniquement quandready.
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
| Code | Quand | Action |
|---|---|---|
UNAUTHORIZED (401) | Bearer manquant ou invalide | Créez une clé sur app.metricspot.com/settings/api-keys |
FORBIDDEN (403) | Audit ou PDF appartient à un autre compte | Utilisez la clé du compte propriétaire |
AUDIT_NOT_FOUND (404) | Audit id inexistant | Passez un audit que vous possédez |
INVALID_URL (400) | :id n’est pas un entier positif | Passez un id entier |
CONFLICT (409) | Audit pas encore completed, ou PDF pas encore ready | Attendez 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 disque | Relancez 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