Guia rápido da API

URL base https://api.pressfox.io. JSON na entrada, JSON na saída. Os ids públicos têm prefixo: ws_ espaço de trabalho, tpl_ modelo, ver_ versão do modelo, rpt_ relatório, brd_ perfil de marca, ast_ arquivo, key_ chave de API, evt_ evento de webhook.

Nesta página

Guia rápido

  1. Crie uma chave. No app, abra Settings → API keys e crie uma chave de teste. Ela começa com pf_test_, é mostrada uma vez e gera relatórios com marca d'água, que não são cobrados e ficam guardados por 24 horas. Crie uma chave real (pf_live_) quando estiver pronto para enviar documentos de verdade.
  2. Publique um modelo. Copie um pacote pronto ou crie o seu, ligue os campos aos caminhos do seu payload de exemplo e publique. Publicar cria uma versão imutável e gera o contrato do payload. Copie a chave do modelo na janela Publish, ou liste os modelos com GET /api/v1/templates.
  3. Envie os dados. POST /api/v1/reports com o id do modelo e o seu payload. A resposta traz o link hospedado e o PDF. Essa é a integração inteira.
Requisição
POST /api/v1/reports
Authorization: Bearer pf_live_7f3a…
Idempotency-Key: visit-48213
Content-Type: application/json

{
  "template": "clinic-visit-summary",
  "payload": { "patient": { "name": "A. Rivera" }, "results": [ { "test": "HbA1c", "value": 6.1 } ] },
  "brand": "tolland",
  "access": { "kind": "password", "password": "correct-horse-battery" }
}
Resposta quando a renderização terminou dentro da requisição200
HTTP/1.1 200 OK
Content-Type: application/json

{
  "report_id": "rpt_01j9xk7q4",
  "title": "Clinic visit summary – 2026-10-07",
  "template_id": "tpl_01j9xk2m8",
  "template_key": "clinic-visit-summary",
  "template_version": 4,
  "status": "ready",
  "mode": "live",
  "language": "en",
  "url": "https://app.pressfox.io/report/k7Qm2Zb9pX4nT1vW8sLq0A",
  "pdf_url": "https://app.pressfox.io/report/k7Qm2Zb9pX4nT1vW8sLq0A/pdf",
  "markdown_url": null,
  "pages": 2,
  "units": 1,
  "created_at": "2026-10-07T09:14:02.118402+00:00",
  "ready_at": "2026-10-07T09:14:03.402911+00:00",
  "error": null,
  "message": "Filed. Don't lose the link."
}

Autenticação

Envie Authorization: Bearer pf_live_… ou pf_test_… em toda requisição. Uma chave pertence a um espaço de trabalho e age pelo espaço de trabalho inteiro; ela não tem usuário nem papel. Revogue-a no app a qualquer momento. Requisições sem uma chave válida recebem 401 unauthorized. GET /api/v1/whoami devolve { workspace_id, workspace, mode, key } da chave enviada, uma forma barata de conferir uma implantação.

Idempotência. Envie um cabeçalho Idempotency-Key em POST /api/v1/reports: qualquer texto de até 128 caracteres, único no seu espaço de trabalho. Use o id do seu próprio registro, como o número de uma fatura ou de uma consulta. Por 24 horas, a mesma chave devolve o status e o corpo originais. A mesma chave com um corpo diferente, ou com o outro modo de chave, recebe 409 conflict (That Idempotency-Key was already used with a different request). Uma segunda requisição enquanto a primeira ainda está rodando também recebe 409 (still in flight). Uma requisição recusada libera a chave, então você pode tentar de novo com ela depois de corrigir o payload. Chaves com mais de 128 caracteres são recusadas com 422 invalid_value.

Relatórios

POST /api/v1/reports

Cria um relatório a partir de uma versão publicada do modelo e do seu payload: a versão que você fixar ("template": "clinic-visit-summary@3", veja versões e fixação) ou a atual, se você não indicar nenhuma. O payload é validado contra o contrato daquela versão antes de qualquer coisa entrar na fila; um payload recusado nunca vira relatório. A requisição então espera até 2,5 segundos pela renderização. Devolve 200 com o objeto completo quando a renderização terminou a tempo, 202 com status: "queued" ou "rendering" quando não terminou, e 500 com status: "failed" e o motivo em error quando a renderização falhou durante a espera. Payloads acima de 1 MiB, ou estimados em mais de 50 páginas pelo exemplo da versão, vão para uma fila separada com prazo de renderização maior (120 segundos em vez de 20); eles ainda podem responder 200 quando terminam rápido.

CampoTipoObservações
templatetexto, obrigatórioUm modelo deste espaço de trabalho, pela chave (clinic-visit-summary) ou pelo id (tpl_…). A chave é definida no app e não muda quando o modelo é renomeado; trocá-la lá faz a chave antiga deixar de funcionar, enquanto o id nunca muda. Modelos desconhecidos ou arquivados recebem 404 not_found; um modelo sem versão publicada recebe 422 template_not_published. Acrescente @N para fixar a versão N (clinic-visit-summary@3, tpl_…@3); sem isso, o relatório usa a versão atual. Uma versão desconhecida recebe 404 version_not_found, uma aposentada 410 version_retired, e um sufixo malformado (key@, key@0, key@x, key@1@2) 422 invalid_value. template_id, o nome original deste campo, aceita os mesmos valores; envie um ou outro, não os dois.
template_versioninteiroA versão a renderizar, um inteiro positivo: o mesmo que @N em template, para clientes que guardam o número à parte. Enviar os dois com números diferentes recebe 422 invalid_value em $.template_version; o mesmo número duas vezes não tem problema. Omita os dois para usar a versão atual.
payloadqualquer JSON, obrigatórioPrecisa satisfazer o contrato da versão. Até 8 MiB (8.388.608 bytes) em JSON canônico; acima disso recebe 413 payload_too_large. As imagens vão como links: uma URL https (pré-assinada serve) ou asset:<id>; veja imagens.
optionsobjetoValores para as chaves de opção que a versão do modelo declara, pelo nome. Uma chave que a versão não declara recebe 422 unknown_option, e a mensagem lista as que ela declara.
brandtextoUma marca do app, pela chave (tolland) ou pelo id (brd_…): o relatório sai com as cores, a fonte e o logo dela. Omita para renderizar com o tema da própria versão. Liste as chaves com GET /api/v1/brands. Uma desconhecida recebe 404 brand_not_found, com o valor. brand_id é o campo original e aceita os mesmos valores; enviar os dois recebe 422 invalid_value.
accessobjetokind mais o campo dele; veja políticas de acesso. Padrão { "kind": "link" }.
pdf_passwordtextoUma senha no próprio PDF: o arquivo só abre com ela. Pelo menos 4 caracteres e no máximo 127 bytes, ou "same_as_link" para usar a access.password de um link password. Standard e acima; abaixo, 402 pdf_password_not_included. Sem ela, um modelo configurado para tirar a senha de um campo dos dados usa esse campo. Nunca é guardada com o relatório nem devolvida; veja uma senha no PDF.
languagetextoO idioma do relatório: en, es, pt, de ou fr. Padrão: o idioma em que o modelo foi escrito. Qualquer outro precisa das traduções do modelo para ele, ou recebe 422 language_not_available com a lista dos que ele tem; veja idiomas.
translate_fromtextoO idioma em que o texto livre do seu payload está escrito, quando é diferente de language: os campos que o modelo marca como Translate são traduzidos automaticamente para o idioma do relatório antes da renderização, e o relatório avisa isso. Todos os planos pagos, a partir do Starter; veja idiomas.
formatslista de textosSaídas extras: ["markdown"] acrescenta uma versão em Markdown do relatório (veja saída em Markdown). HTML e PDF são sempre gerados; os valores são "html", "pdf" e "markdown", e qualquer outro recebe 422 unknown_format. Omita para seguir a configuração do espaço de trabalho Always include Markdown; [] tira um relatório dela.
titletextoAté 200 caracteres, uma linha. Dá nome ao relatório no app e nos e-mails de compartilhamento, e aos arquivos dele: o PDF baixa como <title>.pdf. Omita para usar o padrão de título do modelo, por padrão o nome do modelo e o dia (Clinic visit summary – 2026-10-07). Os títulos são guardados sem criptografia, ao contrário do payload: deixe dados pessoais de fora deles.
retain_daysinteiroGuarda este relatório por esse número de dias (1 a 3.650) e depois o apaga, com a página, o PDF e os links. No máximo a retenção de relatórios do espaço de trabalho, se um proprietário definiu uma; acima dela recebe 422 retain_days_too_long. Sem ele, o relatório fica guardado como o espaço de trabalho define: por padrão, enquanto a assinatura estiver ativa. Veja por quanto tempo os relatórios ficam guardados.

Objeto de resposta, o mesmo em todos os endpoints de relatórios:

CampoTipoObservações
report_idtextorpt_…
titletextoO título do relatório, fixado na criação: o seu, ou o padrão do modelo preenchido com o payload.
template_id, template_keytextoO modelo de onde o relatório veio; a chave como está agora.
template_versioninteiroO número da versão com que o relatório foi renderizado, fixada ou atual no momento. Nunca muda para um relatório.
statustexto"queued", "rendering", "ready", "failed" ou "deleted"
modetexto"live" ou "test", pela chave
languagetextoO idioma do relatório, fixado na criação: o seu, ou o idioma de origem do modelo.
urltextoA página hospedada, …/report/<token>. Presente desde a criação; a página responde pelo status do relatório até ele ficar pronto. O token é aleatório e separado do id.
pdf_urltextourl mais /pdf. Presente desde a criação; o arquivo existe quando o relatório fica pronto. Ele abre no navegador; acrescente ?download=1 para salvar. Nos dois casos, o arquivo leva o título no nome.
markdown_urltexto ou nullurl mais /md: o relatório em Markdown, quando foi pedido (formats ou a configuração do espaço de trabalho); null nos outros casos. Mesma política de acesso do PDF; veja saída em Markdown.
pdf_protectedbooleanotrue quando o PDF só abre com uma senha. A senha nunca é devolvida.
pages, unitsinteiroO número de páginas e as unidades cobradas, ceil(pages / 50). 0 até ficar pronto.
created_at, ready_atRFC 3339 ou nullUTC com microssegundos e fuso, como nos exemplos. ready_at é null até ficar pronto.
retain_untilRFC 3339 ou nullQuando o relatório, os links e o PDF dele terminam. null: sem fim enquanto a assinatura do espaço de trabalho estiver ativa. Veja por quanto tempo os relatórios ficam guardados.
errortexto ou nullO que o plano de renderização recusou, quando status é "failed". Null nos outros casos.
messagetextoUma mensagem de uma linha, para pessoas, para o log. Não é para ser interpretada por código.
Resposta quando a renderização ainda está rodando202
HTTP/1.1 202 Accepted

{
  "report_id": "rpt_01j9xkb2w",
  "title": "Clinic visit summary – 2026-10-07",
  "template_id": "tpl_01j9xk2m8",
  "template_key": "clinic-visit-summary",
  "template_version": 4,
  "status": "rendering",
  "mode": "live",
  "language": "en",
  "url": "https://app.pressfox.io/report/Qh3Lm8Tz2dR5yB7cN9kE1F",
  "pdf_url": "https://app.pressfox.io/report/Qh3Lm8Tz2dR5yB7cN9kE1F/pdf",
  "markdown_url": null,
  "pages": 0,
  "units": 0,
  "created_at": "2026-10-07T09:20:41.007310+00:00",
  "ready_at": null,
  "error": null,
  "message": "Rendering. Poll this report or wait for the webhook."
}
Resposta quando a renderização falhou durante a espera500
HTTP/1.1 500 Internal Server Error

{
  "report_id": "rpt_01j9xkc9f",
  "title": "Clinic visit summary – 2026-10-07",
  "template_id": "tpl_01j9xk2m8",
  "template_key": "clinic-visit-summary",
  "template_version": 4,
  "status": "failed",
  "mode": "live",
  "language": "en",
  "url": "https://app.pressfox.io/report/Vb8Nq1Rw5kJ2hG6mP3xD4S",
  "pdf_url": "https://app.pressfox.io/report/Vb8Nq1Rw5kJ2hG6mP3xD4S/pdf",
  "markdown_url": null,
  "pages": 0,
  "units": 0,
  "created_at": "2026-10-07T09:22:10.551004+00:00",
  "ready_at": null,
  "error": "too-many-pages: document lays out to about 7 pages; the cap is 3",
  "message": "The render failed. Fix what the error names and send it again."
}

Um relatório que falhou é um relatório: ele tem id, aparece na lista e o seu error indica a causa. Uma renderização que falha depois de a requisição ter respondido é informada pelo GET, pelo webhook report.failed, ou pelos dois.

GET /api/v1/reports/{id}

Devolve o objeto do relatório. Consulte depois de um 202 num intervalo razoável, ou prefira um webhook. Uma chave de teste vê só relatórios de teste, uma chave real só os reais; os ids do outro modo respondem 404.

GET /api/v1/reports?template=&status=&cursor=&limit=

Lista os relatórios do modo da chave, do mais novo para o mais antigo, como { items, next_cursor }. Todos os filtros são opcionais: template (a chave ou o id de um modelo, ou key@N para os relatórios feitos com a versão N), status (um dos cinco status; sem ele, os relatórios apagados ficam de fora), limit (de 1 a 200, padrão 50) e cursor, o next_cursor da página anterior. next_cursor é null na última página. Um cursor que esta API não emitiu recebe 422 invalid_value.

GET /api/v1/reports?template=clinic-visit-summary&status=ready&limit=50

{
  "items": [ { "report_id": "rpt_01j9xk7q4", "template_key": "clinic-visit-summary", "template_version": 4, "status": "ready", "mode": "live", "language": "en", "pages": 2, "units": 1, … } ],
  "next_cursor": "MjAyNi0xMC0wN1QwOToxNDowMi4xMTg0MDIrMDA6MDB8MDE5OWI…"
}

DELETE /api/v1/reports/{id}

Marca o relatório como apagado e remove do armazenamento o payload, o HTML, o PDF e o Markdown; o link para de responder. Devolve 204, também quando o relatório já estava apagado. A linha continua, sem chave, então o GET ainda responde com status: "deleted". As unidades cobradas não são devolvidas.

Políticas de acesso

access.kind decide quem pode abrir o link hospedado. A política é definida na criação e não pode ser editada por esta API; o app pode revogar um link, o que gera um token novo.

TipoCamposPlanosA página hospedada
linknenhumtodosQualquer pessoa com o link. O padrão.
passwordpassword, com pelo menos 8 caracteresStandard e acimaPede a senha (401) e grava um cookie por 12 horas. Cinco tentativas erradas em 15 minutos fecham a porta com 429 e uma espera crescente.
expiryexpires_at, um horário ISO 8601 no futuroStarter e acimaResponde 410 depois que o horário passa.
signednenhumPro e acimaAbre só com ?exp=<unix seconds>&sig=<hex HMAC-SHA256 over "token.exp"> gerados com a chave de assinatura do espaço de trabalho; caso contrário, 403. O url e o pdf_url do corpo do relatório já trazem uma assinatura válida por sete dias; POST /api/v1/reports/{id}/links com {"expires_in": seconds} (de 60 segundos a 30 dias) gera novos e responde {url, pdf_url, markdown_url, expires_at}.
privatenenhumPro e acimaAbre só para um membro do espaço de trabalho com login; para todos os outros, 404.

Um tipo fora desta lista recebe 422 invalid_value em $.access.kind. Um password ou expires_at faltando recebe 422 missing_field. expiry no Free, ou password no Free ou no Starter, recebe 402 access_policy_not_included; signed ou private abaixo do Pro recebe 402 signed_urls_not_included.

Uma senha no próprio PDF

Uma política de acesso decide quem abre o link hospedado; uma senha no PDF acompanha o arquivo aonde ele for: como anexo, encaminhado ou baixado. O PDF é criptografado com AES-256 e abre em qualquer leitor comum quando o leitor digita a senha. O leitor continua podendo imprimir, copiar e usar um leitor de tela: a senha deixa estranhos de fora, não restringe o leitor. Standard e acima.

  • Por relatório: envie "pdf_password": "N0rthwind-1984", ou "same_as_link" com access.kind password para usar a senha do link também no arquivo.
  • Pelos dados: no designer, PDF password indica um campo dos dados e um formato: As it is (como está), Digits only (só dígitos: 1984-03-07 vira 19840307) ou Last 4 (os 4 últimos, sem espaços). A configuração fica congelada com a versão; todo relatório precisa desse campo, ou recebe 422 invalid_value indicando-o. Um pdf_password na requisição vale mais que a configuração.

Receita: extratos que abrem com a data de nascimento do cliente. Inclua client.birth_date nos dados, configure PDF password com esse campo e Digits only, e publique. Explique uma vez aos clientes como saber a senha; o botão de download da página hospedada avisa que o PDF precisa dela, sem dizê-la.

A senha é usada para criptografar o arquivo e apagada junto com os dados enviados quando o relatório fica pronto: a Pressfox não consegue recuperá-la, e ela não aparece em nenhuma resposta, log ou webhook. Um relatório cujo PDF não pôde ser criptografado falha em vez de sair sem proteção.

Compartilhar por e-mail

POST /api/v1/reports/{id}/shares com {"emails": [...], "duration": "24h" | "48h" | "7d" | "30d" | "none", "message"?: "..."} envia um relatório pronto por e-mail para até 50 pessoas, um e-mail para cada uma, enviado pela Pressfox em nome do seu espaço de trabalho. Cada pessoa recebe o próprio link para a página hospedada e o PDF: /report/<token>?share=<rcp_id>&exp=<unix seconds, 0 for none>&ssig=<hex>, um HMAC-SHA256 com a chave de assinatura do espaço de trabalho sobre "token.share.<rcp_id>.<exp>". Ele não funciona como link assinado nem como link de QR code, e eles não funcionam como ele.

Um link de compartilhamento abre aquele relatório em qualquer tipo de acesso, com senha e privado incluídos: a pessoa foi escolhida pelo endereço. Ele responde 410 quando o prazo dele passa, quando passa a validade do próprio relatório e quando o compartilhamento ou aquele destinatário é revogado no app; revogar o link do relatório ou apagar o relatório também o fecha (404). "none" continua limitado pela validade do próprio relatório e pela retenção do plano; o expires_at da resposta diz quando os links param. Os endereços são aparados, passados para minúsculas e sem repetição; um endereço inválido recusa a requisição inteira com 422 invalid_email, indicando qual. Um espaço de trabalho compartilha com no máximo 25 pessoas por dia no Free, 100 no Starter e 500 do Standard para cima (429 share_limit com Retry-After). A resposta é 201 com o compartilhamento e os destinatários; as aberturas são contadas por destinatário no app, nunca para robôs ou prévias de link, e o e-mail não tem pixel de rastreamento.

Idiomas, tradução e resumos

Um relatório está em um de cinco idiomas: inglês (en), espanhol (es), português (pt), alemão (de) e francês (fr). Três coisas seguem o idioma.

  • O texto do modelo. Um modelo é escrito em um idioma (o source_language dele, inglês a menos que o autor tenha mudado) e pode ter traduções para os outros, rascunhadas no editor, revisadas pelo autor e congeladas em cada versão publicada. "language": "es" renderiza os rótulos, títulos e rodapés em espanhol; nenhum modelo de IA participa. Um idioma para o qual a versão não tem traduções recebe 422 language_not_available, que lista os que ela tem; GET /api/v1/templates também lista. Um rótulo sem tradução sai no idioma de origem.
  • Formatação e palavras fixas. Números e datas usam o formato regional do idioma (es-ES, pt-BR, de-DE, fr-FR), a menos que o modelo defina o seu; as palavras que a Pressfox imprime por conta própria (números de página, o anexo de fotos, os botões e as páginas de status da página hospedada) saem no idioma do relatório.
  • O seu texto, quando você pede. Os valores do payload saem como você os envia. Para traduzir texto livre escrito em outro idioma, envie "translate_from": "pt": os campos que o autor do modelo marcou como Translate (no contrato, "x-pressfox-translate": true) são traduzidos automaticamente para o idioma do relatório quando a renderização começa. Ids, códigos, números e datas nunca mudam, e todo número do original precisa sobreviver à tradução, ou nada é usado. O PDF e a página hospedada trazem um aviso no idioma do relatório, como Text fields machine-translated from Portuguese.

Tradução de texto: as regras. translate_from igual a language não faz nada. Um modelo que não marca nenhum campo recebe 422 nothing_to_translate; mais de 5.000 textos distintos ou 400.000 caracteres para traduzir, 422 invalid_value em $.payload. É preciso um plano com unidades de relatório traduzidas: sem isso, 402 translated_units_per_month_not_included; passadas as unidades do mês, 402 translated_units_limit. Um relatório conta pelas unidades estimadas, uma vez, por mais que a renderização seja repetida. Um espaço de trabalho do setor de saúde recebe 403 ai_off. Uma tradução que não pode ser concluída com segurança faz o relatório falhar, com error indicando a causa e nenhum texto do payload; envie de novo, ou sem translate_from para manter o texto original.

Resumos com IA. Um modelo pode ter um widget de resumo com IA que lê campos do payload escolhidos pelo autor (por exemplo a descrição de cada constatação) e imprime algumas frases sobre eles, com o aviso Escrito por IA no idioma do relatório, depois de qualquer tradução. Ele é escrito uma vez, quando a renderização começa, e congelado com o relatório. Precisa de um plano que inclua resumos (todos os planos pagos, a partir do Starter); caso contrário, num espaço de trabalho de saúde, quando os campos estão vazios ou quando a resposta do modelo não serve, o widget não imprime nada e o relatório sai mesmo assim. Você não precisa enviar nada a mais.

As chamadas ao modelo de IA veem só os campos marcados daquele relatório, nunca imagens, e nada é guardado: o payload é apagado quando o relatório fica pronto, como sempre.

Saída em Markdown

Todo relatório também pode ser lido em Markdown: o mesmo conteúdo do HTML e do PDF, na ordem de leitura, com títulos, tabelas e rótulos intactos, para que pessoas e ferramentas de IA (assistentes, busca, pipelines de RAG, agentes via MCP) o leiam sem interpretar um PDF. Ele é gerado por código a partir do documento do relatório na renderização, sem IA: o mesmo relatório sempre dá os mesmos bytes. O PDF continua sendo o documento oficial. Está disponível em todos os planos e não muda as unidades.

Como pedir. Envie "formats": ["markdown"] em POST /api/v1/reports. HTML e PDF são sempre gerados; formats acrescenta saídas e aceita "html", "pdf" e "markdown". Qualquer outro valor recebe 422 unknown_format em $.formats[i]. A configuração do espaço de trabalho Always include Markdown (Settings → Reports no app) liga para todo relatório; o campo da requisição vale quando enviado, então "formats": [] deixa um relatório sem ela. Um relatório feito sem ela tem markdown_url: null e nunca a recebe depois: os insumos dele são apagados quando fica pronto.

Requisição
POST /api/v1/reports
Authorization: Bearer pf_test_7f3a…
Content-Type: application/json

{ "template": "vessel-inspection", "payload": { … }, "formats": ["markdown"] }

Como obter. O markdown_url no objeto do relatório, no webhook report.ready e em POST /links é o arquivo hospedado, …/report/<token>/md. Ele abre com a política de acesso do relatório exatamente como o PDF (link, senha, validade, assinado, privado, o link do QR code do PDF e os links de compartilhamento), responde text/markdown; charset=utf-8 e leva o título no nome (<title>.md); acrescente ?download=1 para salvar. Num relatório signed, ele traz a mesma assinatura do pdf_url. O menu de ações da página hospedada tem Download as Markdown, e os downloads entram nas estatísticas do relatório como os do PDF, num contador próprio.

GET /api/v1/reports/{id}/markdown

Devolve o próprio texto a uma chave de API: 200 com text/markdown; charset=utf-8. Um relatório feito sem versão em Markdown recebe 404 markdown_not_requested; um ainda renderizando, ou que falhou, 409 report_not_ready.

Como fica
---
title: "Fleet inspection 2025"
template: fleet-inspection-full-year@1
language: en
report_id: rpt_2Qq3i6Fz6JN8pDfBkPyzcu
created_at: 2026-10-09T02:41:07Z
pages: 271
mode: test
url: https://reports.pressfox.io/report/UXO3Hy5NvORcqsAeKJIUcg
---

> TEST report — not valid as a record.

# Fleet inspection report

## MV OCEAN HORIZON · Genoa, Italy · 2025-01-20

- **Date:** 20 Jan 2025
- **Result:** Satisfactory

### Checklist

- [x] Means of escape unobstructed — Pass
- [ ] Main engine and auxiliaries — Fail

| ID | Area | Description | Severity | Status |
|---|---|---|---|---|
| 3.1 | Machinery | Emergency generator failed to take load within 45 seconds. | Observation | Rectified |

*Photo 13 (p. 116): Internal ladders / stairs — Cargo Hold No.2*
  • Front matter (YAML): title, template como key@N, language, report_id, created_at, pages, mode, e o url hospedado só quando o tipo de acesso do relatório é link. O corpo de um relatório de teste começa com > TEST report — not valid as a record.
  • Corpo, na ordem de leitura do documento impresso e no idioma do relatório (as traduções do modelo e as palavras da própria Pressfox, como no PDF): capas como texto; títulos por nível; texto com negrito e itálico mantidos; destaques como citações; grades de campos, pares chave-valor, partes e totais como listas - **Label:** value; tabelas como tabelas Markdown, com as linhas de total e o texto de vazio; checklists como listas de tarefas (- [x] … — Pass, - [ ] … — Fail, N/A como item simples); cartões de status, nota, medidor e KPI, uma linha cada; listas e linhas do tempo como listas; gráficos como os seus dados numa tabela e uma linha com o tipo de gráfico, nunca uma imagem; assinaturas como **Signed:** name, role, date; resumos com IA seguidos do aviso Escrito por IA; fotos como *Photo N (p. X): caption*, com link para a imagem hospedada só em relatórios link, e o anexo de fotos como uma lista de legendas.
  • Fica de fora: cabeçalhos e rodapés repetidos, números de página, o sumário, marcas d'água, logos, QR codes e divisores (elementos da página, não conteúdo). Um código de barras com rótulo mantém o valor como texto.
  • HTML de reserva e escape. Uma tabela cujas células têm quebras de linha é escrita como <table> HTML, com cada célula escapada. Os caracteres de Markdown nos seus valores são escapados, inclusive as barras verticais nas células; nada num payload consegue injetar HTML.

O Markdown é guardado criptografado ao lado do HTML e do PDF e apagado junto com eles: depois de 24 horas nos relatórios de teste, no fim da retenção do plano nos outros, ou quando o relatório é apagado.

Verificar um documento

A partir do Standard, o PDF de cada relatório real recebe um registro de verificação quando fica pronto: a impressão digital SHA-256, o nome do emissor e a hora da emissão. Quem tem o PDF pode conferir em /verify, no app.pressfox.io ou no seu próprio endereço de relatórios; o navegador calcula a impressão digital e o arquivo nunca é enviado. Quando o seu espaço de trabalho tem o próprio endereço de relatórios, cada PDF imprime uma linha dizendo onde conferir, nesse endereço. Relatórios de teste e de retenção zero não recebem registro.

A página consulta GET /api/verification/check/{sha256}: pública, sem chave, com limite de requisições por endereço e nunca em cache. Responde { "status": "genuine" | "replaced" | "revoked" | "not_found" }, com issuer e issued_at para um arquivo conhecido, replaced_at quando uma correção o substituiu e revoked_at com o reason do emissor quando foi revogado. Um valor que não tem 64 caracteres hexadecimais recebe 422 invalid_value. No seu próprio endereço de relatórios, responde só pelos documentos do seu espaço de trabalho.

Não encontrado quer dizer que o arquivo não foi emitido pela Pressfox ou mudou depois de emitido: anotar um PDF ou salvá-lo de novo muda a impressão digital, e uma cópia assinada pelo seu provedor de assinaturas é um arquivo novo (veja assinaturas, em inglês). Um registro não guarda conteúdo do relatório e é mantido por 10 anos, até o espaço de trabalho ser apagado. A verificação prova que um arquivo não mudou desde a emissão; não é uma assinatura nem um selo eletrônico.

Modelos e contratos

GET /api/v1/templates?cursor=&limit=

Lista os modelos do espaço de trabalho, dos atualizados mais recentemente para os mais antigos, como { items, next_cursor }. Cada item é { id, key, name, current_version, source_language, languages }, em que current_version é { id, number, kit_version } da versão publicada usada nos relatórios, ou null para um modelo que nunca foi publicado. versions lista as versões publicadas, da mais nova para a mais antiga (no máximo 50), como { number, current, retired, created_at }, para você ver quais números pode fixar. source_language é o idioma em que aquela versão está escrita, e languages os idiomas em que um relatório pode ser criado: o de origem e todos os que têm tradução (veja idiomas); null e [] antes da primeira publicação. limit vai de 1 a 200, padrão 50. Modelos arquivados não aparecem.

GET /api/v1/templates?limit=50

{
  "items": [
    { "id": "tpl_01j9xk2m8", "key": "clinic-visit-summary", "name": "Clinic visit summary", "current_version": { "id": "ver_01j9xk5p2", "number": 4, "kit_version": "1.4.0" }, "source_language": "en", "languages": ["en", "es", "pt"],
      "versions": [
        { "number": 4, "current": true, "retired": false, "created_at": "2026-10-06T15:02:11.204118+00:00" },
        { "number": 3, "current": false, "retired": false, "created_at": "2026-09-21T10:40:52.880315+00:00" },
        { "number": 2, "current": false, "retired": true, "created_at": "2026-09-02T08:12:30.117902+00:00" },
        { "number": 1, "current": false, "retired": true, "created_at": "2026-08-28T13:55:04.661020+00:00" }
      ] },
    { "id": "tpl_01j9xkz0a", "key": "invoice", "name": "Invoice (draft)", "current_version": null, "source_language": null, "languages": [], "versions": [] }
  ],
  "next_cursor": null
}

GET /api/v1/brands

Lista as marcas do espaço de trabalho, das mais antigas para as mais novas, como { items }. Cada item é { id, key, name, is_default }. Envie uma key como "brand" ao criar um relatório. A marca padrão é onde os novos modelos começam no editor; um relatório sem "brand" mantém o tema do próprio modelo. As marcas são criadas no app (o Free e o Standard têm uma, o Pro ilimitadas); este endpoint é só de leitura.

GET /api/v1/brands
Authorization: Bearer pf_live_…

200 OK
{ "items": [ { "id": "brd_01j9xk3n1", "key": "tolland", "name": "Tolland & Reese", "is_default": true } ] }

GET /api/v1/templates/{key or id}/contract

Devolve o contrato da versão publicada atual, ou da versão N quando {key or id} termina em @N ou a consulta diz ?version=N (os dois com números diferentes é 422 invalid_value em version). O cabeçalho X-Pressfox-Template-Version indica a versão recebida. schema é o JSON Schema contra o qual a API valida os payloads, deduzido do exemplo da versão, com cada caminho lido por um widget marcado como obrigatório; reads lista esses caminhos, com pontos e sem índices de lista; unread lista as folhas do exemplo que nenhum widget lê. Gere tipos a partir de schema, ou valide do seu lado antes de enviar. Um modelo sem versão publicada recebe 422 template_not_published; uma versão desconhecida, 404 version_not_found, uma aposentada, 410 version_retired.

GET /api/v1/templates/clinic-visit-summary/contract          (the current version)
GET /api/v1/templates/clinic-visit-summary@3/contract        (version 3; ?version=3 is the same)

HTTP/1.1 200 OK
X-Pressfox-Template-Version: 4

{
  "schema": {
    "type": "object",
    "additionalProperties": true,
    "required": ["patient", "results"],
    "properties": {
      "patient": { "type": "object", "additionalProperties": true, "required": ["name"], "properties": { "name": { "type": "string" }, "dob": { "type": "string", "format": "date" } } },
      "results": { "type": "array", "items": { "type": "object", "additionalProperties": true, "required": ["test", "value"], "properties": { "test": { "type": "string" }, "value": { "type": "number" } } } }
    }
  },
  "reads": ["patient.name", "results.test", "results.value"],
  "unread": ["patient.dob"]
}

Versões e fixação

Cada publicação congela o modelo numa nova versão numerada: 1, 2, 3 e assim por diante. Uma versão nunca muda depois de publicada: o layout, o contrato, o padrão de título, as opções e as traduções dela ficam como estavam, e cada relatório registra a versão com que foi feito (template_version). Publicar torna a nova versão a atual.

  • Padrão: a versão atual. "template": "clinic-visit-summary" renderiza a versão atual, como sempre. Quando uma publicação muda o contrato, quem envia o formato antigo começa a receber 422.
  • Fixar: key@N. "template": "clinic-visit-summary@3" (ou "template_version": 3) renderiza a versão 3, seja qual for a atual. As chaves não podem conter @, então a referência não é ambígua; funciona também com ids (tpl_…@3).
  • As versões rodam lado a lado. Um sistema pode enviar @3 enquanto outro envia @4 ou nada, ao mesmo tempo. Cada relatório é validado e renderizado com a sua própria versão: contrato, padrão de título, idiomas, opções, imagens e resumos com IA.
  • Um contrato por versão. GET /api/v1/templates/clinic-visit-summary@3/contract devolve o esquema com que a versão 3 valida; GET /api/v1/templates lista os números que cada modelo tem.
  • Aposentar. No app, uma versão que não deve mais ser usada pode ser aposentada. Novos relatórios fixados nela recebem 410 version_retired, cuja mensagem indica a versão atual para a qual mudar; os relatórios já feitos com ela mantêm os seus links e PDFs. A versão atual não pode ser aposentada, e uma versão aposentada pode ser reativada.
  • Tornar atual: reverter. No app, qualquer versão publicada que não esteja aposentada pode voltar a ser a atual. As requisições sem versão passam a usá-la; nada é republicado e nenhum número novo é criado.

Recomendação. Fixe a versão em produção (key@N) e deixe-a de fora enquanto constrói com uma chave de teste. Quando uma nova versão for publicada, busque o contrato dela, atualize o seu payload e mude a fixação quando estiver pronto. A janela de publicação no app mostra como o contrato mudou em relação à versão anterior.

Uma requisição fixada na versão 3
POST /api/v1/reports
Authorization: Bearer pf_live_7f3a…
Content-Type: application/json

{
  "template": "clinic-visit-summary@3",
  "payload": { "patient": { "name": "A. Rivera" }, "results": [ { "test": "HbA1c", "value": 6.1 } ] }
}
Uma versão desconhecida, e uma aposentada
HTTP/1.1 404 Not Found

{
  "error": {
    "type": "not_found",
    "code": "version_not_found",
    "message": "Template clinic-visit-summary has no version 7. It has 4, 3, 2, 1. Send one of those, or leave the version out to use the current one (4).",
    "field": "$.template",
    …
  }
}

HTTP/1.1 410 Gone

{
  "error": {
    "type": "gone",
    "code": "version_retired",
    "message": "Version 1 of clinic-visit-summary is retired. Move to clinic-visit-summary@4, the current version, or ask an admin to unretire it.",
    "field": "$.template",
    …
  }
}

Imagens

Envie cada imagem como um link para onde ela já está: { "src": "https://your-bucket.s3.eu-west-1.amazonaws.com/visits/48213/hull.jpg?X-Amz-Signature=…", "caption": "Forward hull" }. Buckets privados funcionam com URLs pré-assinadas; alguns minutos de validade bastam. A Pressfox baixa cada imagem uma vez, quando o relatório começa a renderizar, e guarda uma cópia junto com o relatório, então ele nunca mais depende do link: continua funcionando depois que o link expira e é apagado junto com o relatório.

  • Onde. Só os campos que o modelo usa como imagem são baixados (no contrato, "description": "asset id"). Uma URL em qualquer outro lugar do payload continua sendo texto.
  • O quê. Só https. JPEG, PNG ou WebP, decidido pelos bytes do arquivo, não pelo Content-Type. Até 20 MiB por imagem, 500 imagens distintas e 1 GiB no total por relatório. Até três redirecionamentos são seguidos; cada endereço é conferido, e endereços privados, de loopback e de metadados de nuvem nunca são acessados.
  • Quando. O formato das URLs é conferido quando você cria o relatório (422 indicando o campo). O download acontece na renderização: até 16 de cada vez, a mesma URL baixada uma vez, um armazenamento ocupado ou com falha (429, 5xx, timeouts) tentado de novo duas vezes. Um relatório com mais de 8 imagens vai para a fila de grandes, então espere 202 e consulte ou use o webhook.
  • Falhas. Uma imagem que não pode ser baixada faz o relatório falhar, com error indicando o caminho e o motivo, por exemplo $.inspections[3].photos[1].src could not be fetched: the storage answered 403: the link may have expired or need signing again. O próprio link nunca é escrito num erro ou num log.

A mesma foto enviada em muitos relatórios é guardada uma vez. Imagens que você reutiliza em todo lugar (logos, assinaturas) podem, em vez disso, ser enviadas uma vez como arquivos (assets) e referenciadas como asset:<id>; as duas formas podem ser misturadas num payload.

Arquivos (assets)

POST /api/v1/assets

Envia uma imagem uma vez para usar em muitos payloads: logos, assinaturas, papel timbrado. Para fotos que já estão no seu armazenamento, envie as URLs (imagens). Dois formatos de corpo: JSON { content_type, data, name? } com data em base64 (um prefixo data:…;base64, é aceito e removido), ou multipart/form-data com uma parte file e um campo name opcional. Tipos aceitos: image/png, image/jpeg, image/webp, image/gif, image/svg+xml; os bytes precisam bater com o tipo declarado, e um SVG não pode ter script nem manipulador de evento. Limite de 10 MiB por arquivo. Os arquivos são deduplicados pelo hash do conteúdo dentro do espaço de trabalho: bytes novos respondem 201, bytes já guardados respondem 200 com o arquivo existente. Os bytes guardados contam na franquia de arquivos do seu plano; acima dela, 402 asset_bytes_limit.

Envio, JSON
POST /api/v1/assets
Authorization: Bearer pf_live_7f3a…
Content-Type: application/json

{ "content_type": "image/jpeg", "data": "<base64>", "name": "hull-forward.jpg" }

HTTP/1.1 201 Created

{
  "asset_id": "asset:ast_01j9xkd8m3",
  "bytes": 184322,
  "content_type": "image/jpeg",
  "name": "hull-forward.jpg",
  "width": 1600,
  "height": 1200,
  "created_at": "2026-10-07T09:10:12.330117+00:00"
}
Envio, multipart
curl https://api.pressfox.io/api/v1/assets \
  -H "Authorization: Bearer pf_live_7f3a…" \
  -F "file=@hull-forward.jpg;type=image/jpeg" \
  -F "name=hull-forward.jpg"

Referencie o arquivo como asset:<id> em qualquer lugar onde haja um texto no payload (ou num tema de marca). Cada referência é resolvida antes da renderização; um id que não está no seu espaço de trabalho fica sem resolver, e o bloco de imagem renderiza o espaço vazio no lugar; o relatório sai mesmo assim, então confira os ids antes de enviar. GET /api/v1/assets/{id} devolve o mesmo objeto do envio, com ou sem o prefixo asset: no id.

Uso num payload
{ "template": "vessel-inspection", "payload": { "photos": [ { "image": "asset:ast_01j9xkd8m3", "caption": "Forward hull, port side" } ] } }

Erros

Todo erro, de todo endpoint, usa um envelope e uma voz. type é a classe geral, code é estável e documentado, message é para pessoas (em inglês, como toda a API), field é um caminho JSON na sua requisição quando o erro é sobre um campo, expected e received aparecem quando há algo para comparar, e docs_url leva à entrada do catálogo. Chaves sem nada a dizer ficam de fora em vez de virem como null. Informe o request_id quando escrever para o suporte.

HTTP/1.1 422 Unprocessable Content

{
  "error": {
    "type": "validation_error",
    "code": "wrong_type",
    "message": "$.results[0].value should be a number. You sent a string. I'll be here.",
    "field": "$.results[0].value",
    "expected": "number",
    "received": "string",
    "docs_url": "https://pressfox.io/docs/errors#wrong_type",
    "request_id": "req_01j9xk8d2"
  }
}

Os limites de plano são uma classe à parte: type: "limit_exceeded", HTTP 402, e um código que nomeia o limite. field é o nome do limite ou o campo da requisição que o disparou. upgrade_plan indica o plano mais baixo que libera a recusa ("starter", "standard", "pro" ou "enterprise"), e a mensagem cita o mesmo plano; ele não aparece quando nenhum plano liberaria.

HTTP/1.1 402 Payment Required

{
  "error": {
    "type": "limit_exceeded",
    "code": "signed_urls_not_included",
    "message": "Signed and private links are not included in the Standard plan. The Pro plan lifts it.",
    "field": "$.access.kind",
    "upgrade_plan": "pro",
    "docs_url": "https://pressfox.io/docs/errors#signed_urls_not_included",
    "request_id": "req_01j9xk9f1"
  }
}
CódigoStatusSignificado
missing_field422Falta um campo obrigatório.
wrong_type422Um campo tem o tipo JSON errado.
empty_array422Uma lista que precisa ter itens está vazia.
invalid_value422Um valor não cumpre uma regra de formato, de lista de valores ou de faixa, ou um valor de cabeçalho ou de consulta não está certo.
unknown_option422options indica uma opção que a versão não declara.
template_not_published422O modelo não tem versão publicada.
unsupported_asset_type, asset_too_large422O arquivo enviado não é um tipo de imagem aceito, ou passa de 10 MiB.
invalid_request400O corpo não pôde ser lido como JSON, ou a requisição está malformada de um jeito que nenhum outro código cobre.
unauthorized401Chave de API ausente ou inválida.
not_found404Não há esse modelo, marca, relatório ou arquivo neste espaço de trabalho e modo.
version_not_found404O modelo não tem uma versão com o número fixado.
version_retired410A versão fixada está aposentada; mude para a que a mensagem indica.
conflict409Chave de idempotência reutilizada com outra requisição, ou ainda em andamento.
payload_too_large413O payload passa de 8 MiB.
rate_limited429Requisições demais neste minuto, da chave ou do espaço de trabalho; espere Retry-After segundos. Veja limites de requisições.
live_units_limit, report_storage_limit, *_limit, *_not_included402Um limite ou um recurso do plano. O código diz qual, e upgrade_plan o plano que o libera.

O texto completo e o que fazer em cada caso estão no catálogo de erros, em inglês. Além de rate_limited, a API responde 429 para compartilhamentos acima do limite diário (share_limit), e a página de um relatório hospedado para senhas erradas demais. O Playground do app tem limites próprios (60 requisições por membro por hora, 10 execuções com IA por espaço de trabalho por dia: playground_ai_daily_limit).

Limites de requisições

As requisições para /api/v1 são contadas em janelas de um minuto que começam na virada do minuto, por chave de API e somando todas as chaves de um espaço de trabalho. Chaves de teste e reais compartilham as janelas do espaço de trabalho; um app de IA conectado conta como a chave da sua conexão. Requisições que falham na autenticação não contam, e páginas de relatórios hospedados, PDFs e entregas de webhook não são requisições de API.

JanelaPor chave de APIPor espaço de trabalho (todas as chaves)
Todas as requisições600 por minuto1.200 por minuto
Criação de relatórios (POST /api/v1/reports)120 por minuto240 por minuto

Uma criação de relatório conta nas duas linhas. Toda resposta contada traz três cabeçalhos da janela mais perto do limite: RateLimit-Limit (o tamanho da janela), RateLimit-Remaining (as requisições que restam nela) e RateLimit-Reset (os segundos até ela recomeçar). Uma requisição acima de um limite não é contada e é recusada com 429 rate_limited e um cabeçalho Retry-After em segundos; espere esse tempo e envie de novo, com a mesma Idempotency-Key se ela criava um relatório. Lotes grandes andam mais rápido espalhados pelo minuto do que enviados de uma vez. Se o seu volume precisar de mais, escreva para nós.

Webhooks

Espaços de trabalho pagos cadastram endpoints HTTPS no app (Settings → Webhooks): 1 no Starter, até 10 no Standard, quantos precisar no Pro e no Enterprise; no Free, o app responde 402 webhooks_not_included. Cada endpoint assina qualquer um dos dois eventos, report.ready e report.failed. Os payloads são enxutos: ids, status e URLs, nunca dados do relatório. Os eventos de relatórios feitos com uma chave de teste levam "mode": "test", então filtre por ele se o seu endpoint deve agir só em relatórios reais; relatórios enviados pelo Playground do app nunca são entregues. Os relatórios que um membro cria a partir de um formulário no app são relatórios reais e são entregues, com "source": "dashboard" ("api" para os relatórios pedidos pela sua integração), para que um endpoint deixe de lado os relatórios que não pediu.

CampoObservações
type"report.ready" ou "report.failed"
idO id do evento, evt_…, o mesmo valor do cabeçalho webhook-id. Elimine duplicatas por ele; uma reentrega leva o mesmo id.
created_atQuando o evento foi registrado, RFC 3339.
data{ report_id, status, url, pdf_url, markdown_url, template_id, template_key, template_version, mode, source } (markdown_url é null quando o relatório não tem versão em Markdown). Busque o relatório para ter as páginas, as unidades e o texto do erro.

As entregas são assinadas no formato Standard Webhooks. Três cabeçalhos: webhook-id, webhook-timestamp (segundos Unix) e webhook-signature, cujo valor é v1,<base64 HMAC-SHA256>. A chave é o segredo do endpoint mostrado uma vez na criação, whsec_ seguido de base64; tire o prefixo e decodifique o base64. O texto assinado é id + . + timestamp + . + os bytes brutos do corpo, então verifique contra os bytes que você recebeu, não contra um objeto serializado de novo. Hoje vai uma assinatura; não há rotação de segredo. Rejeite horários com mais de cinco minutos de diferença do seu relógio.

Entrega
POST https://example.com/hooks/pressfox
content-type: application/json
user-agent: Pressfox-Webhooks/1.0
webhook-id: evt_01j9xkf0s7
webhook-timestamp: 1791364443
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

{"type":"report.ready","id":"evt_01j9xkf0s7","created_at":"2026-10-07T09:14:03.402911+00:00","data":{"report_id":"rpt_01j9xk7q4","status":"ready","url":"https://app.pressfox.io/report/k7Qm2Zb9pX4nT1vW8sLq0A","pdf_url":"https://app.pressfox.io/report/k7Qm2Zb9pX4nT1vW8sLq0A/pdf","markdown_url":null,"template_id":"tpl_01j9xk2m8","template_key":"clinic-visit-summary","template_version":4,"mode":"live","source":"api"}}
Verificar (Node)
import { createHmac, timingSafeEqual } from "node:crypto";

// secret: the "whsec_…" value shown once when the endpoint was created.
// headers: lower-cased request headers. rawBody: the body as received, a Buffer, not re-serialised.
export function verify(secret, headers, rawBody) {
  const id = headers["webhook-id"];
  const ts = headers["webhook-timestamp"];
  if (!/^\d+$/.test(ts ?? "")) return false;
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(ts)) > 300) return false;                   // the sender's own tolerance is 5 minutes

  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const signed = Buffer.concat([Buffer.from(`${id}.${ts}.`), rawBody]); // "id.timestamp." + body bytes
  const expected = createHmac("sha256", key).update(signed).digest();

  // One "v1,<base64>" value is sent today; the header is still parsed as a space-separated list.
  return (headers["webhook-signature"] ?? "").split(" ").some((part) => {
    const [version, sig] = part.split(",");
    if (version !== "v1" || !sig) return false;
    const given = Buffer.from(sig, "base64");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

Responda com qualquer 2xx em até 10 segundos, medidos da conexão até o último byte lido; redirecionamentos não são seguidos e contam como falha, e o corpo da resposta é ignorado depois de 4 KB. As entregas que falham são tentadas de novo 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas e 24 horas depois da tentativa anterior. Um endpoint que está falhando sem nenhum sucesso há 3 dias é desativado: as novas tentativas pendentes param e os novos eventos não são enviados a ele. O app o mostra como desativado, com quando e por quê (e anota no registro de auditoria). Corrija o receptor e escolha Enable again no endpoint (proprietários e administradores): ele mantém a URL e o segredo de assinatura, a contagem de falhas volta a zero e os 3 dias recomeçam a partir dali. Os eventos perdidos enquanto ele esteve desativado não são enviados automaticamente; o app mostra cada entrega com as suas tentativas e permite reenviar uma.

Modo de teste e modo real

O modo vem da chave. Chaves de teste geram documentos com a marca d'água TEST e não contam nada no seu plano. Chaves reais geram documentos limpos nos planos pagos (o Free leva a marca d'água PRESSFOX) e medem unidades: cada relatório real é cobrado em ceil(pages / 50) unidades, uma vez, na renderização. Modelos, marcas e arquivos são compartilhados entre os modos; os relatórios não: uma chave de teste lista, lê e apaga só relatórios de teste, e uma chave real só os reais. O registro de idempotência é por espaço de trabalho, então a mesma chave enviada por uma chave de teste e por uma real é um conflito, não uma repetição.

Os relatórios de teste ficam guardados por 24 horas em todos os planos, venham de uma chave de teste ou do Playground do app: uma limpeza de hora em hora os apaga, e os links, QR codes e compartilhamentos deles param ao mesmo tempo (a página hospedada diz Available until). Os relatórios reais ficam guardados como diz a próxima seção. Os relatórios de teste de uma chave de teste são enviados aos seus webhooks com "mode": "test"; os enviados pelo Playground nunca são.

Por quanto tempo os relatórios ficam guardados

Nos planos pagos, um relatório real não tem fim enquanto a assinatura do espaço de trabalho estiver ativa: o retain_until dele é null. Quando a assinatura termina (é cancelada, ou o espaço de trabalho passa para o Free), cada relatório sem fim ganha um, 30 dias depois; assinar de novo dentro desse prazo o remove. Trocar entre planos pagos não muda nada. No Free, os relatórios reais ficam guardados por 7 dias; no Enterprise, conforme contrato. Quando o prazo de um relatório acaba, a página, o PDF, o Markdown, os links, o QR code e os compartilhamentos param juntos, e o relatório é apagado.

Um prazo menor é escolha sua. Um proprietário define a retenção de relatórios do espaço de trabalho no app (Settings → Reports, report_retention_days: de 1 a 3.650 dias, ou vazio para "enquanto a assinatura estiver ativa"); ela vale para os relatórios feitos depois da mudança. retain_days em POST /api/v1/reports define o de um relatório, no máximo o do espaço de trabalho. O fim de um relatório pode ser antecipado no app, nunca adiado.

O armazenamento de relatórios tem um limite por plano: 1 GB no Free, 10 GB no Starter, 50 GB no Standard, 500 GB no Pro e 2 TB no Enterprise. Um relatório real acima dele recebe 402 report_storage_limit; nada é apagado para abrir espaço, então apague relatórios de que não precisa mais, contrate mais armazenamento num plano pago ou mude de plano. Relatórios de teste nunca contam.

Use a Pressfox a partir de agentes de IA (MCP)

O servidor MCP da Pressfox permite que um assistente de IA, como o Claude, o ChatGPT ou qualquer outro cliente do Model Context Protocol, faça o que a sua integração faz: encontrar o modelo, ler o contrato dele, criar o relatório e devolver o link. Há duas formas de conectar, com as mesmas ferramentas: por URL, em que você entra na Pressfox e autoriza o assistente (sem chave para colar), ou localmente, um pequeno pacote Python que roda na sua máquina com uma chave do espaço de trabalho. Nos dois casos, o assistente não pode fazer nada que a sua autorização ou a sua chave não permitam, e nenhum modelo de IA roda do nosso lado: o assistente é seu.

Conectar por URL (OAuth)

A URL do servidor é https://mcp.pressfox.io/mcp. No Claude (claude.ai, Desktop ou celular): Settings, Connectors, Add custom connector, cole a URL e depois Connect. No Claude Code: claude mcp add --transport http pressfox https://mcp.pressfox.io/mcp, depois /mcp para entrar. Outros assistentes que aceitam servidores MCP remotos com OAuth usam a mesma URL.

Uma página da Pressfox se abre. Ela mostra o app, o endereço para onde ele vai devolver você e o que o app poderá fazer. Escolha o espaço de trabalho e o modo, e autorize:

  • Teste gera relatórios com marca d'água e sem cobrança, o começo certo. Editores e acima podem conectar em modo de teste.
  • Real gera relatórios de verdade, que contam no seu plano. Só administradores e proprietários podem conectar em modo real.

O app recebe três permissões: ler modelos e os seus contratos (templates:read), ler relatórios (reports:read) e criar, gerar links e compartilhar relatórios (reports:write). Ele não pode apagar relatórios, mudar modelos, ver outros espaços de trabalho nem gerenciar membros, chaves ou cobrança. Ele nunca vê uma chave de API: guarda tokens de curta duração ligados ao servidor MCP da Pressfox, que o servidor confere em cada chamada. Para desconectar, abra Settings, Security, Connected AI apps na Pressfox; o app para de funcionar na hora. Se você sair do espaço de trabalho ou perder o papel que o modo exige, a conexão também para.

Para quem desenvolve clientes: o servidor segue a especificação de autorização do MCP (revisão 2026-07-28). Os metadados do recurso protegido estão em https://mcp.pressfox.io/.well-known/oauth-protected-resource/mcp; o servidor de autorização é https://api.pressfox.io (metadados em /.well-known/oauth-authorization-server). Ele aceita Client ID Metadata Documents e registro dinâmico de clientes, o fluxo de código de autorização com PKCE (só S256), indicadores de recurso, iss na resposta de autorização, tokens de atualização rotativos e revogação de tokens.

Rodar localmente com uma chave

Configure o servidor local só com variáveis de ambiente: PRESSFOX_API_KEY (obrigatória; uma chave de teste gera relatórios com marca d'água e sem cobrança, o começo certo), PRESSFOX_API_URL (https://api.pressfox.io) e PRESSFOX_TIMEOUT_S (padrão 60). A chave nunca é impressa nem registrada, e os payloads também não. Ele precisa do uv; pressfox-mcp --check confere a URL e a chave e mostra o espaço de trabalho.

Claude Desktop, claude_desktop_config.json
{
  "mcpServers": {
    "pressfox": {
      "command": "uvx",
      "args": ["--from", "/path/to/pressfox-mcp", "pressfox-mcp"],
      "env": {
        "PRESSFOX_API_KEY": "pf_test_…",
        "PRESSFOX_API_URL": "https://api.pressfox.io"
      }
    }
  }
}
Claude Code
claude mcp add pressfox \
  --env PRESSFOX_API_KEY=pf_test_… \
  --env PRESSFOX_API_URL=https://api.pressfox.io \
  -- uvx --from /path/to/pressfox-mcp pressfox-mcp
FerramentaChamaDevolve
list_templates()GET /api/v1/templatesA chave, o id e o nome de cada modelo, se está publicado, e os números das versões (a atual e as aposentadas marcadas).
list_brands()GET /api/v1/brandsA chave e o nome de cada marca e se é a padrão; passe uma chave para create_report como brand.
get_template_contract(template, version?)GET /api/v1/templates/{key}/contractA versão que ele descreve, os campos obrigatórios, o caminho e o tipo de cada campo (os de imagem marcados) e um payload de exemplo no formato certo.
create_report(template, payload, template_version?, title?, language?, brand?, access?, markdown=true, wait_seconds=10, idempotency_key?)POST /api/v1/reports (pedindo Markdown, a menos que markdown=false), depois GET enquanto renderizareport_id, status, url, pdf_url, markdown_url, pages, title, e error quando a renderização falhou.
get_report(report_id, include_markdown=false)GET /api/v1/reports/{id}, e …/markdown com include_markdownA mesma visão do relatório; com include_markdown, o Markdown do relatório no corpo (até 200.000 caracteres, o link além disso), para o assistente responder perguntas sobre ele.
list_reports(template?, status?, limit=20)GET /api/v1/reportsOs relatórios, do mais novo para o mais antigo.
share_report(report_id, emails, duration="7d", message?)POST /api/v1/reports/{id}/sharesO compartilhamento e o status de entrega de cada destinatário. Ele envia e-mail de verdade, então o agente é orientado a confirmar os endereços antes.

template aceita uma chave, um id ou key@N para fixar uma versão, como em versões e fixação. As imagens vão dentro do payload como URLs https, exatamente como em imagens; não há ferramenta de envio. Quando a API recusa um payload, o agente lê o mesmo envelope de erro que você leria, com o caminho do campo, corrige aquele campo e envia de novo. Agentes que escrevem código de integração, em vez de chamar ferramentas, podem ler o /llms.txt, que leva a este guia em Markdown (em inglês) e a tudo num arquivo só.