Guia rápido
- 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. - 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. - Envie os dados.
POST /api/v1/reportscom o id do modelo e o seu payload. A resposta traz o link hospedado e o PDF. Essa é a integração inteira.
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" }
} 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.
| Campo | Tipo | Observações |
|---|---|---|
template | texto, obrigatório | Um 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_version | inteiro | A 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. |
payload | qualquer JSON, obrigatório | Precisa 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. |
options | objeto | Valores 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. |
brand | texto | Uma 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. |
access | objeto | kind mais o campo dele; veja políticas de acesso. Padrão { "kind": "link" }. |
pdf_password | texto | Uma 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. |
language | texto | O 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_from | texto | O 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. |
formats | lista de textos | Saí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. |
title | texto | Até 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_days | inteiro | Guarda 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:
| Campo | Tipo | Observações |
|---|---|---|
report_id | texto | rpt_… |
title | texto | O título do relatório, fixado na criação: o seu, ou o padrão do modelo preenchido com o payload. |
template_id, template_key | texto | O modelo de onde o relatório veio; a chave como está agora. |
template_version | inteiro | O número da versão com que o relatório foi renderizado, fixada ou atual no momento. Nunca muda para um relatório. |
status | texto | "queued", "rendering", "ready", "failed" ou "deleted" |
mode | texto | "live" ou "test", pela chave |
language | texto | O idioma do relatório, fixado na criação: o seu, ou o idioma de origem do modelo. |
url | texto | A 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_url | texto | url 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_url | texto ou null | url 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_protected | booleano | true quando o PDF só abre com uma senha. A senha nunca é devolvida. |
pages, units | inteiro | O número de páginas e as unidades cobradas, ceil(pages / 50). 0 até ficar pronto. |
created_at, ready_at | RFC 3339 ou null | UTC com microssegundos e fuso, como nos exemplos. ready_at é null até ficar pronto. |
retain_until | RFC 3339 ou null | Quando 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. |
error | texto ou null | O que o plano de renderização recusou, quando status é "failed". Null nos outros casos. |
message | texto | Uma mensagem de uma linha, para pessoas, para o log. Não é para ser interpretada por código. |
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."
} 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.
| Tipo | Campos | Planos | A página hospedada |
|---|---|---|---|
link | nenhum | todos | Qualquer pessoa com o link. O padrão. |
password | password, com pelo menos 8 caracteres | Standard e acima | Pede 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. |
expiry | expires_at, um horário ISO 8601 no futuro | Starter e acima | Responde 410 depois que o horário passa. |
signed | nenhum | Pro e acima | Abre 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}. |
private | nenhum | Pro e acima | Abre 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"comaccess.kindpasswordpara 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-07vira19840307) 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 recebe422 invalid_valueindicando-o. Umpdf_passwordna 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_languagedele, 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 recebe422 language_not_available, que lista os que ela tem;GET /api/v1/templatestambé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.
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.
---
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,templatecomokey@N,language,report_id,created_at,pages,mode, e ourlhospedado 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órioslink, 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
@3enquanto outro envia@4ou 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/contractdevolve o esquema com que a versão 3 valida;GET /api/v1/templateslista 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.
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 } ] }
} 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 (
422indicando 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 espere202e consulte ou use o webhook. - Falhas. Uma imagem que não pode ser baixada faz o relatório falhar, com
errorindicando 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.
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"
} 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.
{ "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ódigo | Status | Significado |
|---|---|---|
missing_field | 422 | Falta um campo obrigatório. |
wrong_type | 422 | Um campo tem o tipo JSON errado. |
empty_array | 422 | Uma lista que precisa ter itens está vazia. |
invalid_value | 422 | Um 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_option | 422 | options indica uma opção que a versão não declara. |
template_not_published | 422 | O modelo não tem versão publicada. |
unsupported_asset_type, asset_too_large | 422 | O arquivo enviado não é um tipo de imagem aceito, ou passa de 10 MiB. |
invalid_request | 400 | O corpo não pôde ser lido como JSON, ou a requisição está malformada de um jeito que nenhum outro código cobre. |
unauthorized | 401 | Chave de API ausente ou inválida. |
not_found | 404 | Não há esse modelo, marca, relatório ou arquivo neste espaço de trabalho e modo. |
version_not_found | 404 | O modelo não tem uma versão com o número fixado. |
version_retired | 410 | A versão fixada está aposentada; mude para a que a mensagem indica. |
conflict | 409 | Chave de idempotência reutilizada com outra requisição, ou ainda em andamento. |
payload_too_large | 413 | O payload passa de 8 MiB. |
rate_limited | 429 | Requisiçõ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_included | 402 | Um 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.
| Janela | Por chave de API | Por espaço de trabalho (todas as chaves) |
|---|---|---|
| Todas as requisições | 600 por minuto | 1.200 por minuto |
Criação de relatórios (POST /api/v1/reports) | 120 por minuto | 240 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.
| Campo | Observações |
|---|---|
type | "report.ready" ou "report.failed" |
id | O id do evento, evt_…, o mesmo valor do cabeçalho webhook-id. Elimine duplicatas por ele; uma reentrega leva o mesmo id. |
created_at | Quando 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.
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"}} 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.
{
"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 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 | Ferramenta | Chama | Devolve |
|---|---|---|
list_templates() | GET /api/v1/templates | A 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/brands | A 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}/contract | A 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 renderiza | report_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_markdown | A 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/reports | Os relatórios, do mais novo para o mais antigo. |
share_report(report_id, emails, duration="7d", message?) | POST /api/v1/reports/{id}/shares | O 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ó.