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/pdf encua una feina de render. El cos és opcional i accepta language (un de en, es, de, fr, pt, it) i brand_id (un brand kit desat, o null per a la marca per defecte de MetricSpot).
  • GET /api/pdfs/:id retorna l’estat JSON per defecte. Envia Accept: application/pdf per descarregar el fitxer un cop status === "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ó retorna 409.

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/pdf amb el brand_id de 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), ready o failed.
  • error_message: omplert quan status === "failed".
  • generated_at: timestamp ISO en què el fitxer ha acabat de renderitzar-se, null fins que està llest.
  • download_url: ruta relativa a l’endpoint de descàrrega, només present quan ready.

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

CodiQuanAcció
UNAUTHORIZED (401)Token Bearer absent o invàlidEmet una clau a app.metricspot.com/settings/api-keys
FORBIDDEN (403)L’auditoria o el PDF pertanyen a un altre compteUtilitza la clau del compte propietari
AUDIT_NOT_FOUND (404)L’id d’auditoria no existeixPassa una auditoria que sigui teva
INVALID_URL (400):id no és un enter positiuPassa un id enter
CONFLICT (409)L’auditoria encara no està completed, o el PDF encara no està readyEspera 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 demanatTreu brand_id per a un PDF estàndard, o actualitza a Pro
GONE (410)El fitxer PDF no és al discReencua 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