api
POST /api/audits/:id/pdf e GET /api/pdfs/:id
Gere um PDF com a sua marca para uma auditoria completada. Fluxo em duas etapas: POST para enfileirar o render, depois GET o estado JSON ou o stream PDF.
O que estes endpoints fazem
A geração de PDF é um fluxo em duas etapas. Primeiro, enfileire o render com POST /api/audits/:id/pdf. Depois, faça polling em GET /api/pdfs/:id até status === "ready" e streame o ficheiro. O render demora 5 a 20 segundos porque inicia um Chromium headless para imprimir o relatório HTML white-label.
POST /api/audits/:id/pdfenfileira um job de render. O body é opcional e aceitalanguage(um deen,es,de,fr,pt,it) ebrand_id(um kit de marca guardado, ounullpara marca MetricSpot simples).GET /api/pdfs/:iddevolve estado JSON por padrão. EnvieAccept: application/pdfpara streamar o ficheiro quandostatus === "ready".- PDFs brandeados são apenas Pro: branding white-label (logo, cor, rodapé) é gatado pelo plano. PDFs com marca MetricSpot simples estão disponíveis em todos os planos pagos.
- A auditoria deve estar
status: "completed"antes de um PDF poder ser enfileirado. PDF para uma auditoria ainda em execução devolve409.
Por que importa
PDFs são o entregável de agências, freelancers e consultores. Um render automatizado todas as noites significa que cada cliente abre o email com um relatório fresco sem que ninguém na agência clique num botão.
Fluxos concretos:
- Um cron de agência itera cada URL de cliente guardado, chama
POST /api/audits/:id/pdfcom obrand_idda agência, faz polling até pronto, e envia o PDF por email ao cliente. - Um zap Zapier observa novas auditorias em
GET /api/audits, dispara um render PDF, e faz upload do ficheiro para uma pasta Dropbox partilhada com o cliente.
Como usar
Etapa 1: enfileirar o render
POST /api/audits/12345/pdf HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "language": "pt", "brand_id": null }
Etapa 2: polling do estado
GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Etapa 3: descarregar o ficheiro
GET /api/pdfs/9876 HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/pdf
curl (fluxo completo)
TOKEN="ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"
# 1. Enfileirar
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":"pt"}' | jq -r .pdf.id)
# 2. Polling
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 falhou"; 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 (enfileirar + polling + 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: "pt" }),
});
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": "pt"}, 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": "pt",
"status": "pending",
"created_at": "2026-05-14T10:20:01.000Z"
}
}
GET /api/pdfs/:id (estado 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"
}
}
Campos:
status:pending(enfileirado),rendering(Chromium a correr),readyoufailed.error_message: preenchido quandostatus === "failed".generated_at: timestamp ISO de quando o ficheiro acabou de renderizar,nullaté pronto.download_url: caminho relativo para o endpoint streamável de download, definido apenas quandoready.
GET /api/pdfs/:id com Accept: application/pdf
Streama o ficheiro com Content-Type: application/pdf e Content-Disposition: attachment; filename="<domain>-audit.pdf". Código de estado é 200. Se o ficheiro estiver ausente do disco (raro, indica GC), devolve 410 Gone.
Envelope de erro
{ "error": "Audit not yet complete" }
Erros comuns
| Código | Quando | Ação |
|---|---|---|
UNAUTHORIZED (401) | Bearer ausente ou inválido | Gere uma chave em app.metricspot.com/settings/api-keys |
FORBIDDEN (403) | Auditoria ou PDF pertence a outra conta | Use a chave da conta proprietária |
AUDIT_NOT_FOUND (404) | Audit id não existe | Passe uma auditoria que possui |
INVALID_URL (400) | :id não é um inteiro positivo | Passe um id inteiro |
CONFLICT (409) | Auditoria ainda não completed, ou PDF ainda não ready | Aguarde a auditoria terminar; faça polling do estado do PDF |
QUOTA_EXCEEDED (402) | O plano não permite branding white-label para o brand_id pedido | Omita brand_id para um PDF simples, ou faça upgrade para Pro |
GONE (410) | Ficheiro PDF ausente do disco | Reenfileire com POST /api/audits/:id/pdf |
Perguntas frequentes
Quanto tempo demora a renderização?
5 a 20 segundos. O pipeline de render inicia um Chromium headless, navega para o relatório HTML white-label, e imprime para PDF. Gráficos pesados em JS e listas grandes de findings empurram para o limite superior.
Posso enfileirar um PDF antes de a auditoria completar?
Não. POST /api/audits/:id/pdf devolve 409 Conflict se a auditoria ainda estiver queued ou running. Aguarde até GET /api/audits/:id reportar status: "completed", depois enfileire.
Como obtenho um PDF brandeado?
Guarde um kit de marca (logo, cor primária, texto de rodapé) em app.metricspot.com/settings/brands, depois passe o id em brand_id. Se omitir brand_id, a API aplica automaticamente o seu kit de marca único guardado quando existe, caindo para marca MetricSpot simples se não.
Onde é o PDF armazenado?
No sistema de ficheiros do servidor, atrás da rota auth-gated GET /api/pdfs/:id/download. O URL de download assinado faz parte da resposta JSON de estado e só é servido à conta proprietária da auditoria.
Fontes
Última atualização 2026-05-14