API pública v1

Documentação da API do HashDoc

Integre ERPs e sistemas de terceiros para criar, enviar, acompanhar e validar documentos assinados sem passar pelo portal web. Autenticação OAuth 2.0 Client Credentials, respostas em JSON e escrita idempotente.

URL base: https://docu-flow-masters.lovable.app/api/public/v1

Antes de começar

  1. No portal, abra API e integrações e gere uma credencial. Oclient_secret aparece uma única vez.
  2. Troque as credenciais por um access token (válido por 1 hora) e envie-o no header Authorization: Bearer.
  3. Em toda escrita, envie Idempotency-Key com uma chave estável do seu sistema (nº do pedido, por exemplo).
  4. A credencial está presa a um escopo (pessoal ou empresa): ela só enxerga documentos daquele contexto.
POST
/oauth/token
sem token

Obter access token

Troca client_id e client_secret por um token opaco válido por 1 hora. Aceita credenciais no corpo (JSON ou form-urlencoded) ou no header Basic.

CampoTipoObrig.Observação
grant_typestringsimSempre client_credentials.
client_idstringsimGerado na tela API e integrações.
client_secretstringsimMostrado apenas na criação.
scopestringnãoSubconjunto dos escopos do cliente, separado por espaço.
Requisição
curl -X POST https://docu-flow-masters.lovable.app/api/public/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "hdc_9f2b...",
    "client_secret": "s3cr3t...",
    "scope": "documents:read documents:write documents:send files:read"
  }'
Resposta — 200 OK
{
  "access_token": "hd_kJ8x2p...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "documents:read documents:write documents:send files:read"
}
GET
/documents
escopo: documents:read

Listar documentos

Lista os documentos do escopo (pessoal ou empresa) vinculado à credencial, do mais recente para o mais antigo.

CampoTipoObrig.Observação
statusstringnãodraft, awaiting_signatures, completed, cancelled.
limitnumbernãoPadrão 50.
offsetnumbernãoPadrão 0, para paginação.
Requisição
curl "https://docu-flow-masters.lovable.app/api/public/v1/documents?status=awaiting_signatures&limit=20" \
  -H "Authorization: Bearer $TOKEN"
Resposta — 200 OK
{
  "documents": [
    {
      "id": "7d0c1f4e-2a55-4a2f-9a1c-8f6f2f1b90aa",
      "title": "Contrato 4711",
      "status": "awaiting_signatures",
      "document_hash": "9b1c...e4",
      "external_ref": "4711",
      "created_at": "2026-08-31T18:04:11.220Z",
      "sealed_at": null
    }
  ],
  "limit": 20,
  "offset": 0
}
POST
/documents
escopo: documents:write (+ documents:send se send_immediately)

Criar documento com signatários

Cria o rascunho, envia o PDF para o cofre (content-addressable) e opcionalmente já dispara os convites em broadcast. Envie o header Idempotency-Key para evitar duplicidade em reprocessamentos do ERP.

CampoTipoObrig.Observação
titlestringsim2 a 200 caracteres.
file_base64stringnãoPDF em base64. Exige file_sha256.
file_sha256stringnãoSHA-256 do PDF, validado no servidor.
file_iduuidnãoAlternativa ao base64: arquivo já existente no cofre.
external_refstringnãoSua chave de negócio (nº do pedido, contrato).
parties[]arraysimname, cpf, email; opcionais: role, signing_order, representation, company_cnpj, company_name, company_role.
send_immediatelybooleannãoDispara os convites logo após criar.
Requisição
curl -X POST https://docu-flow-masters.lovable.app/api/public/v1/documents \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: pedido-4711" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Contrato 4711",
    "external_ref": "4711",
    "file_base64": "JVBERi0xLjcKJc...",
    "file_sha256": "9b1c8d...e4",
    "parties": [
      {
        "name": "Maria Souza",
        "cpf": "00000000000",
        "email": "maria@exemplo.com.br",
        "role": "signer",
        "representation": "company",
        "company_cnpj": "00000000000191",
        "company_name": "Exemplo Ltda",
        "company_role": "Diretora"
      }
    ],
    "send_immediately": true
  }'
Resposta — 201 Created (200 OK quando deduped=true)
{
  "document_id": "7d0c1f4e-2a55-4a2f-9a1c-8f6f2f1b90aa",
  "deduped": false,
  "status": "awaiting_signatures",
  "send": {
    "document_id": "7d0c1f4e-2a55-4a2f-9a1c-8f6f2f1b90aa",
    "document_hash": "9b1c8d...e4",
    "invites": [
      { "party_id": "1f3e...", "email": "maria@exemplo.com.br", "sent": true }
    ],
    "invites_ok": true
  }
}
GET
/documents/{id}
escopo: documents:read

Consultar status do documento

Retorna o status, o hash e o andamento das assinaturas. O CPF dos signatários sempre volta mascarado.

Requisição
curl https://docu-flow-masters.lovable.app/api/public/v1/documents/7d0c1f4e-2a55-4a2f-9a1c-8f6f2f1b90aa \
  -H "Authorization: Bearer $TOKEN"
Resposta — 200 OK
{
  "id": "7d0c1f4e-2a55-4a2f-9a1c-8f6f2f1b90aa",
  "title": "Contrato 4711",
  "description": null,
  "status": "awaiting_signatures",
  "document_hash": "9b1c8d...e4",
  "external_ref": "4711",
  "created_at": "2026-08-31T18:04:11.220Z",
  "updated_at": "2026-08-31T18:06:02.001Z",
  "sealed_at": null,
  "signatures": { "total": 2, "signed": 1, "pending": 1 },
  "parties": [
    {
      "id": "1f3e...",
      "name": "Maria Souza",
      "email": "maria@exemplo.com.br",
      "cpf_masked": "000.***.***-00",
      "role": "signer",
      "signing_order": 1,
      "representation": "company",
      "company_cnpj": "00000000000191",
      "company_name": "Exemplo Ltda",
      "signed_at": "2026-08-31T18:20:44.910Z"
    }
  ]
}
POST
/documents/{id}/send
escopo: documents:send

Enviar para assinatura (broadcast)

Coloca o rascunho em assinatura e dispara os convites simultaneamente para todos os signatários. A operação é idempotente: reenviar não duplica convites.

Requisição
curl -X POST https://docu-flow-masters.lovable.app/api/public/v1/documents/7d0c1f4e-.../send \
  -H "Authorization: Bearer $TOKEN"
Resposta — 200 OK
{
  "status": "awaiting_signatures",
  "document_id": "7d0c1f4e-2a55-4a2f-9a1c-8f6f2f1b90aa",
  "document_hash": "9b1c8d...e4",
  "invites": [
    { "party_id": "1f3e...", "email": "maria@exemplo.com.br", "sent": true },
    { "party_id": "2a7d...", "email": "joao@exemplo.com.br", "sent": true }
  ],
  "invites_ok": true
}
GET
/documents/{id}/download
escopo: files:read

Baixar o PDF

Devolve uma URL temporária do arquivo. Quando o documento já foi concluído, a URL aponta para a versão selada com os carimbos de assinatura.

CampoTipoObrig.Observação
expires_innumbernãoValidade da URL em segundos: 30 a 3600 (padrão 300).
Requisição
curl "https://docu-flow-masters.lovable.app/api/public/v1/documents/7d0c1f4e-.../download?expires_in=600" \
  -H "Authorization: Bearer $TOKEN"
Resposta — 200 OK
{
  "url": "https://.../vault/9b1c8d...e4.pdf?token=...",
  "expires_in": 600,
  "sealed": true,
  "status": "completed"
}
GET
/validate/{hash}
sem token

Validar autenticidade por hash

Endpoint aberto (não exige token). Confirma a existência e a integridade de um documento a partir do seu hash SHA-256, com os mesmos dados do validador público.

Requisição
curl https://docu-flow-masters.lovable.app/api/public/v1/validate/9b1c8d5f0a...e4
Resposta — 200 OK (404 quando found=false)
{
  "found": true,
  "title": "Contrato 4711",
  "status": "completed",
  "document_hash": "9b1c8d5f0a...e4",
  "sealed_at": "2026-08-31T19:02:10.441Z",
  "signatures": [
    { "name": "Maria Souza", "cpf_masked": "000.***.***-00", "signed_at": "2026-08-31T18:20:44.910Z" }
  ]
}
eventos

Webhooks assinados (HMAC-SHA256)

Em vez de consultar o status em loop, cadastre um endpoint HTTPS na tela API e integrações e receba os eventos do ciclo de assinatura assim que acontecem. O segredo de assinatura (whsec_…) é exibido uma única vez e pode ser rotacionado a qualquer momento.

EventoQuando dispara
document.sentConvites de assinatura foram disparados (broadcast).
document.signedUma parte assinou; o payload traz o total assinado/pendente.
document.completedTodos assinaram e o PDF selado já está disponível para download.
document.cancelledO remetente cancelou o documento.
pingDisparo manual de teste feito pelo portal.
Requisição enviada ao seu endpoint
POST /hashdoc/webhook HTTP/1.1
Content-Type: application/json
User-Agent: HashDoc-Webhooks/1
X-HashDoc-Event: document.completed
X-HashDoc-Delivery: 6b0f...c1
X-HashDoc-Timestamp: 1756672931
X-HashDoc-Signature: t=1756672931,v1=6f6a...9d

{
  "id": "6b0f...c1",
  "type": "document.completed",
  "created_at": "2026-08-31T21:22:11.000Z",
  "attempt": 1,
  "data": {
    "document": {
      "id": "7e2c...aa",
      "title": "Contrato 4711",
      "status": "completed",
      "document_hash": "9b1c8d5f0a...e4",
      "external_ref": "4711",
      "sealed_at": "2026-08-31T21:22:09.880Z"
    },
    "signatures": { "total": 2, "signed": 2, "pending": 0 },
    "parties": [
      { "id": "a1...", "name": "Maria Souza", "email": "maria@ex.com",
        "signed_at": "2026-08-31T21:22:08.100Z" }
    ]
  }
}
Validação da assinatura (Node.js)
import crypto from "node:crypto";

app.post("/hashdoc/webhook", express.raw({ type: "*/*" }), (req, res) => {
  const header = req.get("X-HashDoc-Signature") ?? "";     // t=...,v1=...
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  const body = req.body.toString("utf8");

  // Rejeite eventos com mais de 5 minutos (proteção contra replay)
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || age > 300) return res.sendStatus(400);

  const expected = crypto
    .createHmac("sha256", process.env.HASHDOC_WEBHOOK_SECRET)
    .update(parts.t + "." + body)
    .digest("hex");

  const ok =
    parts.v1 &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(body);
  // Trate de forma idempotente usando event.id
  processarEvento(event);
  res.sendStatus(200);  // qualquer 2xx confirma a entrega
});

Entrega e retentativas

  • Considera-se sucesso qualquer resposta 2xx em até 10 segundos.
  • Falhas são reprocessadas automaticamente em 1, 5, 15, 60, 180 e 360 minutos; após 6 tentativas a entrega fica marcada como exhausted.
  • O mesmo evento pode chegar mais de uma vez: use id do evento como chave de deduplicação no seu ERP.
  • O histórico de entregas, o reenvio manual e o disparo de teste (ping) ficam na tela API e integrações.

Erros

Todo erro devolve o mesmo formato: { "error": "codigo", "message": "descrição" }

HTTPerrorQuando acontece
400invalid_inputCampo obrigatório ausente ou fora do formato esperado.
400invalid_jsonCorpo enviado não é um JSON válido.
400invalid_scopeEscopo solicitado no token não foi concedido ao cliente.
401unauthorizedHeader Authorization ausente ou malformado.
401invalid_tokenToken inválido, expirado ou revogado.
401invalid_clientCredenciais incorretas ou credencial revogada.
403insufficient_scopeO token não possui o escopo exigido pela rota.
404not_foundDocumento fora do escopo da credencial ou inexistente.
409conflictEstado incompatível (ex.: enviar documento já concluído).
500server_errorFalha interna. Repita a chamada com o mesmo Idempotency-Key.