api
POST /api/audits/:id/pdf i GET /api/pdfs/:id
Genera un informe PDF amb la teva marca per a una auditoria completada. Dos passos: POST per encuar i GET per consultar estat o descarregar.
Què fan aquests endpoints
La generació de PDFs és un flux en dos passos. Primer, encua el render amb POST /api/audits/:id/pdf. Després, fes poll a GET /api/pdfs/:id fins que status === "ready" i descarrega el fitxer. El render triga de 5 a 20 segons perquè aixeca un Chromium headless per imprimir l’informe HTML amb la teva marca.
POST /api/audits/:id/pdfencua una feina de render. El cos és opcional i acceptalanguage(un deen,es,de,fr,pt,it) ibrand_id(un brand kit desat, onullper a la marca per defecte de MetricSpot).GET /api/pdfs/:idretorna l’estat JSON per defecte. EnviaAccept: application/pdfper descarregar el fitxer un copstatus === "ready".- Els PDFs amb marca pròpia són només per a Pro: la marca white-label (logo, color, peu) està restringida pel pla. Els PDFs amb la marca de MetricSpot estan disponibles a tots els plans de pagament.
- L’auditoria ha d’estar amb
status: "completed"abans de poder encuar un PDF. El PDF d’una auditoria encara en execució retorna409.
Per què importa
Els PDFs són el lliurable per a agències, freelancers i consultors. Un render programat cada nit fa que cada client obri la safata d’entrada amb un informe nou sense que ningú de l’agència hagi de clicar cap botó.
Fluxos concrets:
- Un cron d’agència itera totes les URLs de clients desades, crida
POST /api/audits/:id/pdfamb elbrand_idde l’agència, fa poll fins que està llest, i envia el PDF al client per correu. - Un zap de Zapier observa noves auditories a
GET /api/audits, dispara un render de PDF, i puja el fitxer a una carpeta de Dropbox compartida amb el client.
Com utilitzar-los
Pas 1: encuar el render
POST /api/audits/12345/pdf HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "language": "en", "brand_id": null }
Pas 2: fer poll de l’estat
GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Pas 3: descarregar el fitxer
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. Encuar
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":"en"}' | jq -r .pdf.id)
# 2. Poll de l'estat
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. Descarregar
curl -o report.pdf https://app.metricspot.com/api/pdfs/$PDF_ID \
-H "authorization: Bearer $TOKEN" \
-H "accept: application/pdf"
Node (encuar + poll + descarregar)
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: "en" }),
});
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": "en"}, 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)
Resposta
POST /api/audits/:id/pdf
201 Created:
{
"pdf": {
"id": 9876,
"audit_id": 12345,
"brand_id": null,
"language": "en",
"status": "pending",
"created_at": "2026-05-14T10:20:01.000Z"
}
}
GET /api/pdfs/:id (estat 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"
}
}
Camps:
status:pending(encuat),rendering(Chromium executant-se),readyofailed.error_message: omplert quanstatus === "failed".generated_at: timestamp ISO en què el fitxer ha acabat de renderitzar-se,nullfins que està llest.download_url: ruta relativa a l’endpoint de descàrrega, només present quanready.
GET /api/pdfs/:id amb Accept: application/pdf
Descarrega el fitxer amb Content-Type: application/pdf i Content-Disposition: attachment; filename="<domini>-audit.pdf". El codi d’estat és 200. Si el fitxer no és al disc (rar, indica GC), retorna 410 Gone.
Sobre d’error
{ "error": "Audit not yet complete" }
Errors habituals
| Codi | Quan | Acció |
|---|---|---|
UNAUTHORIZED (401) | Token Bearer absent o invàlid | Emet una clau a app.metricspot.com/settings/api-keys |
FORBIDDEN (403) | L’auditoria o el PDF pertanyen a un altre compte | Utilitza la clau del compte propietari |
AUDIT_NOT_FOUND (404) | L’id d’auditoria no existeix | Passa una auditoria que sigui teva |
INVALID_URL (400) | :id no és un enter positiu | Passa un id enter |
CONFLICT (409) | L’auditoria encara no està completed, o el PDF encara no està ready | Espera que acabi l’auditoria; fes poll de l’estat del PDF |
QUOTA_EXCEEDED (402) | El pla no permet marca white-label per al brand_id demanat | Treu brand_id per a un PDF estàndard, o actualitza a Pro |
GONE (410) | El fitxer PDF no és al disc | Reencua amb POST /api/audits/:id/pdf |
Preguntes freqüents
Quant triga el render?
5 a 20 segons. La cadena de render aixeca un Chromium headless, navega a l’informe HTML white-label i imprimeix a PDF. Els gràfics amb molt JS i les llistes llargues de findings empenyen cap a la part alta.
Puc encuar un PDF abans que acabi l’auditoria?
No. POST /api/audits/:id/pdf retorna 409 Conflict si l’auditoria encara està queued o running. Espera fins que GET /api/audits/:id reporti status: "completed", i llavors encua.
Com obtinc un PDF amb marca pròpia?
Desa un brand kit (logo, color primari, text de peu) a app.metricspot.com/settings/brands, i passa el seu id a brand_id. Si ometres brand_id, l’API aplica automàticament l’únic brand kit desat si existeix, i si no, recau a la marca estàndard de MetricSpot.
On es desa el PDF?
Al sistema de fitxers del servidor de l’app, darrere de la ruta autenticada GET /api/pdfs/:id/download. La URL de descàrrega signada forma part de la resposta JSON d’estat i només es serveix al compte propietari de l’auditoria.
Fonts
Última actualització 2026-05-14