api
POST /api/audits/:id/pdf und GET /api/pdfs/:id
Gebrandetes PDF aus abgeschlossenem Audit erzeugen. Zwei Schritte: POST reiht das Rendern ein, GET liefert Status oder PDF-Stream, sobald bereit.
Was diese Endpunkte machen
PDF-Erzeugung ist ein Zwei-Schritt-Flow. Zuerst das Rendern mit POST /api/audits/:id/pdf einreihen. Dann GET /api/pdfs/:id pollen, bis status === "ready" und die Datei streamen. Das Rendern dauert 5 bis 20 Sekunden, weil ein Headless-Chromium den White-Label-HTML-Report druckt.
POST /api/audits/:id/pdfreiht einen Render-Job ein. Body ist optional und akzeptiertlanguage(einer ausen,es,de,fr,pt,it) undbrand_id(ein gespeicherter Brand-Kit odernullfür reines MetricSpot-Branding).GET /api/pdfs/:idliefert standardmäßig JSON-Status. MitAccept: application/pdfdie Datei streamen, sobaldstatus === "ready".- Gebrandete PDFs sind Pro-only: White-Label-Branding (Logo, Farbe, Footer) wird per Plan gegated. Schlichte MetricSpot-gebrandete PDFs sind in jedem bezahlten Plan verfügbar.
- Der Audit muss
status: "completed"sein, bevor ein PDF eingereiht werden kann. PDF für einen noch laufenden Audit liefert409.
Warum das wichtig ist
PDFs sind das Deliverable für Agenturen, Freelancer und Berater. Ein nächtliches automatisches Rendern bedeutet, dass jeder Kunde morgens einen frischen Report im Postfach öffnet, ohne dass jemand in der Agentur auf einen Knopf gedrückt hat.
Konkrete Workflows:
- Ein Agentur-Cron iteriert jede gespeicherte Kunden-URL, ruft
POST /api/audits/:id/pdfmit derbrand_idder Agentur auf, pollt bis fertig und mailt das PDF an den Kunden. - Ein Zapier-Zap überwacht neue Audits in
GET /api/audits, triggert ein PDF-Rendering und lädt die Datei in einen mit dem Kunden geteilten Dropbox-Ordner hoch.
Wie du sie nutzt
Schritt 1: Rendern einreihen
POST /api/audits/12345/pdf HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "language": "de", "brand_id": null }
Schritt 2: Status pollen
GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Schritt 3: Datei herunterladen
GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/pdf
curl (voller Flow)
TOKEN="ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"
# 1. Einreihen
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":"de"}' | jq -r .pdf.id)
# 2. Status pollen
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 "render failed"; exit 1; }
sleep 2
done
# 3. Download
curl -o report.pdf https://app.metricspot.com/api/pdfs/$PDF_ID \
-H "authorization: Bearer $TOKEN" \
-H "accept: application/pdf"
Node (einreihen + pollen + Download)
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: "de" }),
});
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": "de"}, 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)
Antwort
POST /api/audits/:id/pdf
201 Created:
{
"pdf": {
"id": 9876,
"audit_id": 12345,
"brand_id": null,
"language": "de",
"status": "pending",
"created_at": "2026-05-14T10:20:01.000Z"
}
}
GET /api/pdfs/:id (JSON-Status)
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"
}
}
Felder:
status:pending(in Queue),rendering(Chromium läuft),readyoderfailed.error_message: gesetzt beistatus === "failed".generated_at: ISO-Zeitstempel, wenn die Datei fertig gerendert war, sonstnull.download_url: relativer Pfad zum streamenden Download-Endpunkt, nur beireadygesetzt.
GET /api/pdfs/:id mit Accept: application/pdf
Streamt die Datei mit Content-Type: application/pdf und Content-Disposition: attachment; filename="<domain>-audit.pdf". Statuscode 200. Fehlt die Datei auf der Platte (selten, deutet GC an), kommt 410 Gone.
Fehler-Envelope
{ "error": "Audit not yet complete" }
Häufige Fehler
| Code | Wann | Aktion |
|---|---|---|
UNAUTHORIZED (401) | Bearer-Token fehlt oder ist ungültig | Schlüssel erzeugen auf app.metricspot.com/settings/api-keys |
FORBIDDEN (403) | Audit oder PDF gehört einem anderen Konto | Schlüssel des Besitzerkontos verwenden |
AUDIT_NOT_FOUND (404) | Audit-id existiert nicht | Eigenen Audit übergeben |
INVALID_URL (400) | :id keine positive ganze Zahl | Ganzzahlige id übergeben |
CONFLICT (409) | Audit noch nicht completed oder PDF noch nicht ready | Auf Audit-Ende warten; PDF-Status pollen |
QUOTA_EXCEEDED (402) | Plan erlaubt kein White-Label-Branding für die gewünschte brand_id | brand_id weglassen für ein schlichtes PDF oder Pro buchen |
GONE (410) | PDF-Datei fehlt auf der Platte | Erneut mit POST /api/audits/:id/pdf einreihen |
Häufig gestellte Fragen
Wie lange dauert das Rendern?
5 bis 20 Sekunden. Die Render-Pipeline startet ein Headless-Chromium, navigiert zum White-Label-HTML-Report und druckt zu PDF. JS-lastige Charts und große Findings-Listen drücken in den oberen Bereich.
Kann ich ein PDF einreihen, bevor der Audit fertig ist?
Nein. POST /api/audits/:id/pdf liefert 409 Conflict, wenn der Audit noch queued oder running ist. Warten, bis GET /api/audits/:id status: "completed" meldet, dann einreihen.
Wie bekomme ich ein gebrandetes PDF?
Einen Brand-Kit (Logo, Primärfarbe, Footer-Text) auf app.metricspot.com/settings/brands speichern und seine id in brand_id übergeben. Wird brand_id weggelassen, wendet die API automatisch deinen einzigen gespeicherten Brand-Kit an, falls vorhanden, sonst MetricSpot-Standard.
Wo wird das PDF gespeichert?
Im Filesystem des App-Servers, hinter der auth-gegateten Route GET /api/pdfs/:id/download. Die Download-URL ist Teil der JSON-Statusantwort und wird nur an das besitzende Konto ausgeliefert.
Quellen
Zuletzt aktualisiert 2026-05-14