API de integração
Crie recolhas a partir do seu CRM, envie o link ao cliente e receba os documentos por webhook. Especificação completa em /openapi.json (OpenAPI 3.1).
Autenticação
Crie uma chave em Escritório Virtual → Chaves de API e envie-a em todos os pedidos no cabeçalho Authorization: Bearer td_live_…. A chave só é mostrada uma vez; guarde-a num cofre e nunca a ponha em código do lado do navegador.
1. Ver o catálogo
GET /api/v1/integrations/document-types?locale=pt GET /api/v1/integrations/document-types?kind=collect
Devolve value (o identificador a usar), source (AT, SS, BdP, IRN, ADENE, AIMA, IMT), kind (collect para recolha por robô, template para modelo em PDF), name e description.
2. Criar uma recolha
curl -X POST https://docs.titusone.pt/api/v1/integrations/recolhas \
-H "Authorization: Bearer td_live_..." \
-H "Idempotency-Key: crm-processo-118" \
-H "Content-Type: application/json" \
-d '{
"client": { "name": "Titus Gorila", "email": "titus@exemplo.pt", "phone": "929999991", "externalId": "13838" },
"documents": ["declaracao-modelo-3-irs", "mapa-responsabilidades-credito", "declaracao-situacao-contributiva"],
"notes": "Para o pedido de crédito habitação.",
"reference": "Processo 2026/118",
"expiresInDays": 7
}'HTTP/1.1 201 Created
{
"recolhaId": "4f7c…",
"seqId": 1043,
"clientId": "9b21…",
"status": "criada",
"paymentMode": "credits",
"shareUrl": "https://docs.titusone.pt/r/Xk3…",
"expiresAt": "2026-11-18T10:00:00.000Z"
}- Idempotency-Key é obrigatória. Repetir com a mesma chave e o mesmo corpo devolve a mesma resposta (sem gastar créditos outra vez); com outro corpo dá
409 idempotency_conflict. - O cliente é encontrado por
externalIdou pelo e-mail. ComupdateIfExists: true, a ficha é atualizada. requester.email(opcional) tem de ser um membro ativo da sua conta; senão dá403 requester_not_approved.- Cada documento gasta 1 crédito; sem créditos dá
402 insufficient_credits. Os documentos não obtidos devolvem o crédito.
3. Acompanhar
GET /api/v1/integrations/recolhas/{recolhaId}
GET /api/v1/integrations/recolhas/{recolhaId}/files/{fileId}Estados da recolha: criada, aberta, em_curso, concluida, parcial, falhou, expirada, cancelada. Por documento: pendente, em_curso, obtido, sem_dados, erro, modelo.
4. Webhooks
Crie o endpoint em Escritório Virtual → Webhooks e guarde o segredo de assinatura (whsec_…). Quando a recolha termina enviamos recolha.completed:
POST https://o-seu-crm.pt/webhooks/titus-docs
X-TitusDocs-Event: recolha.completed
X-TitusDocs-Delivery: 812
X-TitusDocs-Signature: t=1762858800,v1=5d41402abc4b2a76b9719d911017c592...
{
"event": "recolha.completed",
"recolhaId": "4f7c…", "seqId": 1043, "clientId": "9b21…",
"status": "concluida", "completedAt": "2026-11-11T10:07:41.000Z",
"documents": [
{ "type": "declaracao-modelo-3-irs", "status": "obtido",
"files": [{ "id": "…", "name": "Modelo 3 IRS 2025.pdf", "bytes": 182311, "sha256": "…",
"url": "https://docs.titusone.pt/api/v1/integrations/recolhas/4f7c…/files/…" }] }
]
}Responda com um código 2xx em menos de 10 segundos. Se falhar, repetimos com espera crescente, até 8 tentativas, e pode reenviar à mão no Escritório. Pode haver repetições: deduplique por X-TitusDocs-Delivery ou por event + recolhaId.
Verificar a assinatura (Node.js)
import crypto from "node:crypto";
function assinaturaValida(segredo, cabecalho, corpoEmTexto) {
const { t, v1 } = Object.fromEntries(cabecalho.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // mais de 5 min: rejeitar
const esperado = crypto.createHmac("sha256", segredo).update(`${t}.${corpoEmTexto}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}Erros
Todos os erros têm o formato { "error": "texto", "code": "codigo" }. Os 500 e 503 podem ser repetidos com a mesma Idempotency-Key.