Endpoints de integração

A superfície pública da API, curada para integração — v0.1.0. Convenções (autenticação, idempotência, erros) na página de introdução.

Autenticação

Cria a conta ou faz login para obter o token Bearer usado em todas as chamadas.

POST/v1/auth/register

Auto-registo (cria tenant em Trial + conta fundadora + sessão)

  • organizationstring · obrigatórioNome da organização (novo tenant)
  • namestring · obrigatório
  • emailstring · obrigatório
  • passwordstring · obrigatório
201Sessão: {token, expires_at, user, tenant, roles}409Email já registado
POST/v1/auth/login

Login por email e password (devolve o token Bearer da sessão)

  • emailstring · obrigatório
  • passwordstring · obrigatório
200Sessão: {token, expires_at, user, tenant, roles}401Credenciais inválidas

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "ana@empresa.pt", "password": "•••"}'

Resposta

{
  "token": "eyJ…",
  "expires_at": "2026-07-09T10:00:00Z",
  "tenant": { "id": "…", "name": "Empresa, Lda" },
  "roles": ["tenant_admin"]
}

Usa o token em todas as chamadas: Authorization: Bearer {token}.

Templates

Consulta o catálogo e as versões publicadas — a geração parte sempre de uma versão publicada.

GET/v1/templates

Listar templates do tenant

200Lista de templates
GET/v1/templates/{id}

Obter template

  • idpath · obrigatório · string
200Template404Recurso não encontrado
GET/v1/templates/{id}/versions

Listar versões

  • idpath · obrigatório · string
200Lista de versões
GET/v1/templates/{id}/versions/{versionId}/content

Conteúdo da versão (HTML/Handlebars — para extração de variáveis e previews)

  • idpath · obrigatório · string
  • versionIdpath · obrigatório · string
200Conteúdo do template404Versão não encontrada

Geração

Renderiza documentos (PDF, HTML, TXT) com idempotência garantida por Idempotency-Key.

POST/v1/render

Gerar documento (síncrono, persistido)

Requer header Idempotency-Key — replays devolvem 200 com o resultado original sem consumir quota. Quota renders_per_month verificada antes do render: limite hard excedido → 402 quota-exceeded. Emite DocumentRendered via outbox; artefacto vai para o storage provider.

  • Idempotency-Keyheader · obrigatório · string
  • templateVersionIdstring · obrigatório
  • dataobject · obrigatório
  • format"html" | "pdf" | "txt" | "docx"
  • titlestring
  • localestring
200Replay idempotente (resultado original)201Documento gerado402Quota do plano excedida (enforcement hard)422Validação/render falhou ou versão não publicada

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/render \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5f1c2e3a-…" \
  -d '{
    "templateVersionId": "9d2f…",
    "format": "pdf",
    "title": "Apólice 2026-104",
    "data": { "customer": { "name": "Ana Silva" }, "premium": 1250.5 }
  }'

Resposta

{
  "instanceId": "01a2…",
  "documentId": "01a3…",
  "format": "pdf",
  "checksum": "sha256:…",
  "replayed": false
}

Repetir com a mesma Idempotency-Key devolve o MESMO documento (replayed: true) sem consumir quota.

POST/v1/render/preview

Preview (mesmo pipeline, sem persistir nem consumir quota; watermark PREVIEW)

  • templateVersionIdstring · obrigatório
  • dataobject · obrigatório
200HTML renderizado com watermark

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/render/preview \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"templateVersionId": "9d2f…", "data": {"customer": {"name": "Teste"}}}'

Resposta

<!doctype html>… (HTML com watermark PREVIEW)

Não persiste nem consome quota — ideal para pré-visualizações na tua UI.

Documentos

Repositório com linhagem completa; visualização sempre mediada por tokens temporários auditados.

GET/v1/documents

Repositório de documentos do tenant (com linhagem template/versão/origem)

  • template_idquery · stringRestringe aos documentos gerados por este template
200Lista com template_id/name, template_version, source (manual|trigger|form_request) e form_request quando aplicável

Pedido

curl https://api.doptizer.pavulla.com/v1/documents?template_id=7bc1… \
  -H "Authorization: Bearer $TOKEN"

Resposta

{
  "data": [{
    "id": "01a3…",
    "title": "Apólice 2026-104",
    "template_name": "Apólice Multirriscos",
    "template_version": "1.2.0",
    "source": "manual",
    "lifecycle_state": "active",
    "created_at": "2026-07-07T09:55:31Z"
  }]
}
GET/v1/documents/{id}

Metadados e linhagem de um documento

  • idpath · obrigatório · string
200Documento com linhagem completa404Documento não encontrado
POST/v1/documents/{id}/view-tokens

Emitir token temporário de visualização (15 min, auditado)

O conteúdo nunca é servido por URL permanente — cada visualização passa por um token assinado e fica no audit trail.

  • idpath · obrigatório · string
201{view_url, expires_at} — abrir em GET /v1/view/{token}404Documento não encontrado

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/documents/01a3…/view-tokens \
  -H "Authorization: Bearer $TOKEN"

Resposta

{ "view_url": "/v1/view/eyJ…", "expires_at": "2026-07-07T10:15:00Z" }

Nunca há URLs permanentes: cada visualização usa um token de 15 minutos e fica no audit trail.

Importação Word

Converte .docx existentes em templates: placeholders viram variáveis, macros nunca são executadas.

POST/v1/import/docx

Importar documento Word (Import Studio)

Converte um .docx em blocos do designer + variáveis + sugestões. Placeholders em chavetas duplas, parênteses angulares duplos, aspas latinas, sintaxe dólar-chaveta ou parênteses retos duplos, e merge fields, são normalizados. Macros/VBA nunca são executados. Máx. 10 MiB.

  • filestring · obrigatório
200Resultado da análise (blocks, variables, warnings, suggestions, score)422Ficheiro inválido

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/import/docx \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@minuta-contrato.docx"

Resposta

{
  "blocks": [ … ],
  "variables": ["cliente.nome", "cliente.nif", "valor"],
  "warnings": [],
  "quality": 92
}

Placeholders {{X}}, «X», ${X} e [[X]] são convertidos em variáveis; macros nunca são executadas.

Formulários e pedidos

Formulários multi-parte: cria pedidos, distribui links tokenizados e gera o documento no fim.

GET/v1/forms

Listar formulários do tenant

200Lista com template_name e contagens de papéis/secções
POST/v1/form-requests

Enviar formulário a partes concretas (um link tokenizado por papel)

Congela o schema e a versão publicada corrente do template. Cada papel do schema exige exatamente uma parte (nome+email). A validade dos links é configurável por pedido (5 min a 90 dias; default 14 dias).

  • form_idstring · obrigatório
  • titlestring · obrigatório
  • link_ttl_minutesinteger
  • partiesobject[] · obrigatório
201{request, parties, fill_urls: {roleKey: url}}409forms/form_not_active

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/form-requests \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "3fa8…",
    "title": "Contrato — Ana Silva",
    "link_ttl_minutes": 20160,
    "parties": [
      { "role": "cliente", "name": "Ana Silva", "email": "ana@mail.pt" },
      { "role": "fiador", "name": "Rui Costa", "email": "rui@mail.pt" }
    ]
  }'

Resposta

{
  "request": { "id": "5cd0…", "status": "open", "progress_pct": 0 },
  "fill_urls": {
    "cliente": "https://app.doptizer.com/fill?token=eyJ…",
    "fiador": "https://app.doptizer.com/fill?token=eyJ…"
  }
}

Cada parte recebe o seu link e só vê as suas secções — sem PII de terceiros.

GET/v1/form-requests/{id}

Detalhe operacional — o que falta e quem falta preencher o quê

progress.sections[].missing traz paths+labels dos required por preencher (nunca valores).

  • idpath · obrigatório · string
200{request, form_name, parties, progress}404Recurso não encontrado
POST/v1/form-requests/{id}/generate

Gerar/repetir o documento de um pedido concluído (idempotente)

  • idpath · obrigatório · string
200{document_id, instance_id, format}409forms/not_completed

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/form-requests/5cd0…/generate \
  -H "Authorization: Bearer $TOKEN"

Resposta

{ "document_id": "01a4…", "instance_id": "01a5…", "format": "pdf" }

Disponível quando todas as partes concluíram; o template usado é o congelado no envio.

POST/v1/form-requests/{id}/parties/{partyId}/link

Reemitir o link de preenchimento de uma parte

  • idpath · obrigatório · string
  • partyIdpath · obrigatório · string
200{fill_url, expires_at}409forms/request_not_open

Preenchimento público

Rotas públicas autenticadas pelo token do link — para as partes externas ou para embeds próprios.

GET/v1/fill/{token}

Página pública de preenchimento: só as secções do papel da parte

  • tokenpath · obrigatório · string
200Secções com valores atuais + progresso global. Cada campo pode trazer visible_if {path, op, value} — o cliente mostra/esconde em tempo real; campos ocultos não são exigidos nem persistidos (issue #80).401Link inválido ou expirado409forms/request_cancelled

Pedido

curl https://api.doptizer.pavulla.com/v1/fill/eyJ…

Resposta

{
  "title": "Contrato — Ana Silva",
  "role_label": "Cliente",
  "sections": [{ "title": "Dados pessoais", "fields": [
    { "path": "cliente.nome", "label": "Nome", "kind": "text", "required": true },
    { "path": "cliente.email", "label": "Email", "kind": "text", "input": "email", "required": true }
  ]}],
  "pct": 0
}

Rota pública autenticada pelo token do link — sem sessão. Ideal para embeds próprios.

POST/v1/fill/{token}

Guardar progresso e/ou concluir a parte

values é um mapa path→valor restrito aos campos do papel da parte. done=true exige os required da parte preenchidos. Quando todas as partes concluem, o pedido fica completed, o evento FormSubmissionFinal entra no outbox e o documento é gerado do template congelado.

  • tokenpath · obrigatório · string
  • valuesobject
  • doneboolean
200{party_status, pct, parties, completed, document_id?}401Link inválido ou expirado409forms/party_done · forms/request_not_open422forms/path_not_allowed · forms/required_missing · forms/invalid_value

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/fill/eyJ… \
  -H "Content-Type: application/json" \
  -d '{"values": {"cliente.nome": "Ana Silva"}, "done": false}'

Resposta

{ "party_status": "in_progress", "pct": 25, "completed": false }

done:false guarda progresso; done:true conclui a parte (validação de obrigatórios → 422 acionável).

POST/v1/fill/{token}/preview

Preview em tempo real do documento durante o preenchimento

Renderiza o template congelado do pedido com os valores da PRÓPRIA parte (guardados + em edição, filtrados pelo papel); campos das outras partes aparecem como placeholders «A preencher por {Papel}» — nunca PII de terceiros. Puro: sem gravação, sem quota, watermark PREVIEW embebido. Debounce recomendado no cliente (~800ms).

  • tokenpath · obrigatório · string
  • valuesobject
200{html, page_setup?: {size, orientation, margins}}401Link inválido ou expirado409forms/request_cancelled

Automação

Gatilhos de evento: renderiza automaticamente quando algo acontece na plataforma.

GET/v1/render-triggers

Listar gatilhos de render do tenant

200Lista de gatilhos + supported_events
POST/v1/render-triggers

Criar gatilho (evento de domínio → documento gerado)

Eventos acionáveis: FormSubmissionFinal, DocumentSigned, InvoiceIssued (DocumentRendered excluído para evitar loops). O template tem de estar publicado; o disparo é assíncrono no worker com Idempotency-Key trigger:{triggerId}:{eventId}.

  • event_type"FormSubmissionFinal" | "DocumentSigned" | "InvoiceIssued" · obrigatório
  • template_idstring · obrigatório
  • format"html" | "pdf" | "txt"
201Gatilho criado422Evento fora da allowlist ou template não publicado

Pedido

curl -X POST https://api.doptizer.pavulla.com/v1/render-triggers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "FormSubmissionFinal", "template_id": "7bc1…", "format": "pdf"}'

Resposta

{ "id": "88e1…", "event_type": "FormSubmissionFinal", "template_id": "7bc1…" }

O worker gera automaticamente quando o evento ocorre; idempotência por trigger+evento evita duplicados.

DELETE/v1/render-triggers/{id}

Remover gatilho

  • idpath · obrigatório · string
204Removido404Recurso não encontrado

Observabilidade

Métricas do tenant e sinal de vida da API.

GET/v1/analytics/overview

Métricas tenant-scoped em direto (dashboard e página Analytics)

KPIs (documentos no mês, renders hoje, templates publicados, revisões pendentes, conclusão de assinaturas, eventos de auditoria 24h) e séries (volume de renders 7 dias, conclusão de assinaturas 6 meses, tempo médio de revisão por workspace em 90 dias). Qualquer membro autenticado do tenant pode ler; o overview platform-level continua em /v1/admin/analytics.

200Overview analítico do tenant401Sem contexto de tenant

Pedido

curl https://api.doptizer.pavulla.com/v1/analytics/overview \
  -H "Authorization: Bearer $TOKEN"

Resposta

{
  "kpis": { "documents_month": 214, "renders_today": 12, "active_templates": 8, … },
  "render_volume_7d": [{ "date": "2026-07-01", "count": 31 }, …]
}
GET/healthz

Liveness probe

200Processo vivo