Documentação pública

API de documentos e assinaturas

Versão v1 · Integração por API REST

Integre seu sistema ao Doc Chain para criar documentos, acompanhar transições, coletar assinaturas com OTP e baixar o dossiê de evidências. A API usa JSON, é versionada em /api/v1 e mantém as regras de negócio no mesmo núcleo da plataforma.

1. Visão geral

Todas as requisições da API devem apontar para a URL pública da sua instalação e usar o prefixo /api/v1. A aplicação precisa estar aprovada pelo super-admin; uma chave de aplicação pendente, rejeitada, suspensa ou revogada não autentica.

Os exemplos abaixo usam $BASE_URL,$API_KEY, $DOCUMENT_ID e$SIGNER_ID como variáveis de shell. Substitua-os pelos valores retornados pelo portal e pelas respostas da API.

2. Autenticação e assinatura das requisições

Cada chamada combina uma API key no Bearer com uma assinatura HMAC-SHA256. A própria chave completa é o segredo da assinatura; ela é exibida uma vez na criação ou regeneração e nunca deve ser enviada para logs ou para o navegador.

HeaderValorObrigatório
AuthorizationBearer sk_live_…Sim
X-TimestampUnix time em segundosSim
X-NonceUUID único por requisiçãoSim
X-SignatureHMAC-SHA256 em hexadecimalSim

Calcule a assinatura sobre o corpo exato que será transmitido:HMAC_SHA256(chave, timestamp + "." + nonce + "." + SHA256(body)). Em requisições sem corpo, use SHA256(""). O timestamp deve estar dentro de uma janela de ±300 segundos. O nonce é armazenado por 10 minutos e não pode ser reutilizado.

BODY='{"titulo":"Contrato"}'
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen)
BODY_HASH=$(printf '%s' "$BODY" | sha256sum | cut -d' ' -f1)
SIGNATURE=$(printf '%s' "$TIMESTAMP.$NONCE.$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$API_KEY" -hex \
  | sed 's/^.* //')
curl -X POST "$BASE_URL/api/v1/documentos" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  --data "$BODY"

3. Endpoints

A API v1 oferece os dez endpoints abaixo. Os corpos usam os nomes do contrato REST, diferentes dos nomes internos das procedures tRPC. A camada HTTP apenas traduz a chamada para os serviços de documentos, armazenamento e transições.

POST/api/v1/documentos

Cria um envelope em rascunho. Use Idempotency-Key para repetir uma tentativa com segurança.

Requisição

curl -X POST "$BASE_URL/api/v1/documentos" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Idempotency-Key: pedido-contrato-2026-0001" \
  -H "Content-Type: application/json" \
  --data '{"titulo":"Contrato de prestação de serviços","mensagem":"Assinatura do contrato","prazo":"2026-10-30T23:59:59Z","ordem":"serial"}'

Resposta

HTTP/1.1 201 Created

{"id":"3d7e8f5a-1d3c-4cb3-8e39-7c6a9d9f1c01","status":"draft"}

POST/api/v1/documentos/{id}/arquivos

O upload usa duas fases. Primeiro prepare o documento, depois envie os bytes para a URL assinada e conclua no mesmo recurso com o documentId retornado.

Requisição

curl -X POST "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/arquivos" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  --data '{"fileName":"contrato.pdf","mimeType":"application/pdf","sizeBytes":184320}'

# Resposta da preparação:
{"documentId":"6f8b7c2d-6e24-4f85-8c9b-19c7f6d5a401","uploadUrl":"https://storage.example/upload/…","headers":{"Content-Type":"application/pdf"}}

# Envie o arquivo para a URL recebida (sem os headers da API):
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --upload-file contrato.pdf

# Conclua o upload no mesmo endpoint:
curl -X POST "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/arquivos" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  --data '{"documentId":"6f8b7c2d-6e24-4f85-8c9b-19c7f6d5a401"}'

Resposta

HTTP/1.1 200 OK

{"documentId":"6f8b7c2d-6e24-4f85-8c9b-19c7f6d5a401"}

POST/api/v1/documentos/{id}/signatarios

Adiciona um signatário L1 (e-mail + OTP) ou delegada. A modalidade delegada só funciona quando a capability foi habilitada para a aplicação.

Requisição

curl -X POST "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/signatarios" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  --data '{"nome":"Marina Costa","email":"marina@example.com","nivel":"l1","enviarConvite":false}'

Resposta

HTTP/1.1 201 Created

{"id":"b5f7c8d9-0e12-4a34-8b56-7890c1d2e345","nome":"Marina Costa","email":"marina@example.com","nivel":"l1","status":"pending"}

POST/api/v1/documentos/{id}/enviar

Transiciona o documento para envio e dispara os convites configurados. No fluxo integrado, crie o signatário com enviarConvite=false e peça o OTP em seguida.

Requisição

curl -X POST "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/enviar" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Resposta

HTTP/1.1 200 OK

{"id":"3d7e8f5a-1d3c-4cb3-8e39-7c6a9d9f1c01","status":"in_progress"}

POST/api/v1/documentos/{id}/signatarios/{sid}/assinar/iniciar

Disponível para signatários l1. Envia um código de uso único por e-mail; o código expira em 10 minutos e tem limite de tentativas.

Requisição

curl -X POST "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/signatarios/$SIGNER_ID/assinar/iniciar" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Resposta

HTTP/1.1 200 OK

{"signerId":"b5f7c8d9-0e12-4a34-8b56-7890c1d2e345","expiresAt":"2026-09-14T15:10:00.000Z"}

POST/api/v1/documentos/{id}/signatarios/{sid}/assinar/confirmar

Valida o OTP e registra a assinatura L1 com consentimento e contexto. Para uma aplicação autorizada, modo=delegada dispensa OTP e registra auth_level=delegado_app.

Requisição

curl -X POST "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/signatarios/$SIGNER_ID/assinar/confirmar" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  --data '{"codigo":"123456","nome":"Marina Costa","email":"marina@example.com","cpf":"000.000.000-00","ipUsuario":"203.0.113.10","userAgentUsuario":"doc-trust/1.0","consentimentoTexto":"Li e concordo com o documento.","consentimentoVersao":"2026-09-01","motivo":"Aprovação da revisão"}'

Resposta

HTTP/1.1 200 OK

{"id":"3d7e8f5a-1d3c-4cb3-8e39-7c6a9d9f1c01","status":"completed","completed":true,"hash":"4f9e52b3a17c85d0e0d4b7d3eaf3e36ac7c9316f65f7bd79c93c09b5f8f8b1a2"}

GET/api/v1/documentos

Lista os documentos da aplicação. pagina e porPagina são opcionais; porPagina tem teto de 50.

Requisição

curl -X GET "$BASE_URL/api/v1/documentos?pagina=1&porPagina=20" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Resposta

HTTP/1.1 200 OK

{"items":[{"id":"3d7e8f5a-1d3c-4cb3-8e39-7c6a9d9f1c01","title":"Contrato de prestação de serviços","message":"Assinatura integrada","status":"completed","deadlineAt":"2026-10-30T23:59:59.000Z","completedAt":"2026-09-14T14:10:00.000Z","createdAt":"2026-09-14T14:00:00.000Z","updatedAt":"2026-09-14T14:10:00.000Z","signerCount":1,"documentCount":1}],"pagina":1,"porPagina":20,"total":1,"hasMore":false,"nextOffset":null}

GET/api/v1/documentos/{id}

Retorna o status do envelope e o estado de cada signatário.

Requisição

curl -X GET "$BASE_URL/api/v1/documentos/$DOCUMENT_ID" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Resposta

HTTP/1.1 200 OK

{"id":"3d7e8f5a-1d3c-4cb3-8e39-7c6a9d9f1c01","title":"Contrato de prestação de serviços","message":"Assinatura integrada","status":"completed","deadlineAt":"2026-10-30T23:59:59.000Z","completedAt":"2026-09-14T14:10:00.000Z","createdAt":"2026-09-14T14:00:00.000Z","updatedAt":"2026-09-14T14:10:00.000Z","signers":[{"id":"b5f7c8d9-0e12-4a34-8b56-7890c1d2e345","name":"Marina Costa","email":"marina@example.com","cpf":null,"orderIndex":0,"authLevel":"l1","status":"signed","signedAt":"2026-09-14T14:05:00.000Z"}],"documents":[{"id":"6f8b7c2d-6e24-4f85-8c9b-19c7f6d5a401","kind":"original","fileName":"contrato.pdf","mimeType":"application/pdf","sizeBytes":184320,"sha256":"4f9e52b3a17c85d0e0d4b7d3eaf3e36ac7c9316f65f7bd79c93c09b5f8f8b1a2"}]}

GET/api/v1/documentos/{id}/dossie

Retorna uma URL pré-assinada para o dossiê do documento concluído. Antes de completed, a resposta é 404.

Requisição

curl -X GET "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/dossie" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Resposta

HTTP/1.1 200 OK

{"url":"https://storage.example/download/dossie-…"}

GET/api/v1/documentos/{id}/documentos-assinados

Retorna, para cada original assinado do documento concluído, o nome do arquivo, o SHA-256 e uma URL pré-assinada para download. Use para guardar o PDF assinado no seu sistema.

Requisição

curl -X GET "$BASE_URL/api/v1/documentos/$DOCUMENT_ID/documentos-assinados" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Resposta

HTTP/1.1 200 OK

{"documentos":[{"documentId":"8c2a…","fileName":"contrato.pdf","sha256":"4f9e52b3…","url":"https://storage.example/download/assinado-…"}]}

4. Erros

Erros usam sempre o envelope JSON abaixo. A mensagem é segura para exibição e não contém stack trace, SQL ou detalhes internos.

{
  "erro": {
    "codigo": "chave_invalida",
    "mensagem": "A chave da API é inválida ou a aplicação não está aprovada."
  }
}
HTTPCódigoQuando ocorre
400validacaoCorpo, parâmetro ou header fora do contrato.
401chave_invalidaBearer ausente, inválido, revogado ou aplicação não aprovada.
403capability_negadaA aplicação não tem a capability solicitada, como assinatura delegada.
404nao_encontradoDocumento, signatário ou dossiê indisponível.
409nonce_repetidoO nonce já foi usado; gere um UUID novo.
409estado_invalidoA transição não é permitida no estado atual.
429rate_limitMais de 60 requisições na janela; respeite Retry-After.

5. Webhooks

Cadastre a URL e o segredo no detalhe da aplicação; o portal exibe o segredo ao criar ou rotacionar, para você copiá-lo uma vez. Cada transição do envelope pode gerar um POST com o corpo{evento, envelopeId, applicationId, dados}. Os eventos são envelope.created,envelope.sent, envelope.viewed,envelope.signed, envelope.completed,envelope.declined e envelope.canceled.

A entrega inclui X-Webhook-Signature no formatot=<unix>, v1=<hex>. O valor hexadecimal é calculado sobre o corpo bruto recebido, sem parsear e serializar o JSON novamente:HMAC_SHA256(webhookSecret, timestamp + "." + corpo).

const signature = request.headers.get("X-Webhook-Signature") ?? "";
const [timestampPart, valuePart] = signature.split(", ");
const timestamp = Number(timestampPart?.replace("t=", ""));
const received = valuePart?.replace("v1=", "") ?? "";
const signedPayload = timestamp + "." + rawBody;
const expected = createHmac("sha256", WEBHOOK_SECRET)
  .update(signedPayload, "utf8")
  .digest("hex");
if (
  !Number.isFinite(timestamp) ||
  Math.abs(Date.now() / 1000 - timestamp) > 300 ||
  received.length !== expected.length ||
  !timingSafeEqual(Buffer.from(received), Buffer.from(expected))
) {
  return new Response("assinatura inv\u00E1lida", { status: 400 });
}

Responda com qualquer status 2xx após validar a assinatura e processar o evento. A plataforma tenta novamente com backoff de 30 segundos × 2n, até cinco tentativas; depois registra a entrega como failed.

6. Limites e retenção

  • Rate limit: 60 requisições por minuto, por API key, em janela fixa de 60 segundos. Ao exceder, a resposta é 429 e incluiRetry-After.
  • Replay: timestamp com tolerância de ±300 segundos; cada nonce fica reservado por 10 minutos.
  • Paginação: porPagina aceita no máximo 50 itens.
  • Logs de uso: guardam apenas método, rota, status, duração e API key; a retenção é de 180 dias. Payloads, tokens, OTPs e PDFs não entram nesses logs.

7. Quickstart ponta a ponta

Crie a aplicação em Aplicações no portal, informe a URL de webhook se precisar de notificações e aguarde a aprovação manual do super-admin. Copie a API key exibida uma única vez; o script a seguir cria, envia e assina um documento com OTP, consulta o status e baixa o dossiê.

export BASE_URL="https://seu-dominio.example"
export API_KEY="sk_live_copie-a-chave-exibida-no-portal"
request() {
  local method="$1"
  local path="$2"
  local body="$3"
  local timestamp nonce body_hash signature
  timestamp=$(date +%s)
  nonce=$(uuidgen)
  body_hash=$(printf '%s' "$body" | sha256sum | cut -d' ' -f1)
  signature=$(printf '%s' "$timestamp.$nonce.$body_hash" \
    | openssl dgst -sha256 -hmac "$API_KEY" -hex \
    | sed 's/^.* //')
  if [ -n "$body" ]; then
    curl --fail-with-body -X "$method" "$BASE_URL$path" \
      -H "Authorization: Bearer $API_KEY" \
      -H "X-Timestamp: $timestamp" \
      -H "X-Nonce: $nonce" \
      -H "X-Signature: $signature" \
      -H "Content-Type: application/json" \
      --data "$body"
  else
    curl --fail-with-body -X "$method" "$BASE_URL$path" \
      -H "Authorization: Bearer $API_KEY" \
      -H "X-Timestamp: $timestamp" \
      -H "X-Nonce: $nonce" \
      -H "X-Signature: $signature"
  fi
}
# 1) Crie a aplica\u00E7\u00E3o em Aplica\u00E7\u00F5es no portal.
# 2) Aguarde a aprova\u00E7\u00E3o do super-admin e copie a API key uma \u00FAnica vez.
DOCUMENT=$(request POST /api/v1/documentos '{"titulo":"Contrato de presta\u00E7\u00E3o de servi\u00E7os","mensagem":"Assinatura integrada","prazo":"2026-10-30T23:59:59Z","ordem":"serial"}')
DOCUMENT_ID=$(printf '%s' "$DOCUMENT" | jq -r '.id')
PREPARED=$(request POST "/api/v1/documentos/$DOCUMENT_ID/arquivos" '{"fileName":"contrato.pdf","mimeType":"application/pdf","sizeBytes":184320}')
UPLOAD_URL=$(printf '%s' "$PREPARED" | jq -r '.uploadUrl')
UPLOAD_DOCUMENT_ID=$(printf '%s' "$PREPARED" | jq -r '.documentId')
curl --fail-with-body -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --upload-file contrato.pdf
request POST "/api/v1/documentos/$DOCUMENT_ID/arquivos" "{\"documentId\":\"$UPLOAD_DOCUMENT_ID\"}"
SIGNER=$(request POST "/api/v1/documentos/$DOCUMENT_ID/signatarios" '{"nome":"Marina Costa","email":"marina@example.com","nivel":"l1","enviarConvite":false}')
SIGNER_ID=$(printf '%s' "$SIGNER" | jq -r '.id')
request POST "/api/v1/documentos/$DOCUMENT_ID/enviar"
# O OTP chega ao e-mail do signat\u00E1rio. Leia-o no modal do sistema integrador.
request POST "/api/v1/documentos/$DOCUMENT_ID/signatarios/$SIGNER_ID/assinar/iniciar"
read -r OTP
request POST "/api/v1/documentos/$DOCUMENT_ID/signatarios/$SIGNER_ID/assinar/confirmar" "{\"codigo\":\"$OTP\",\"nome\":\"Marina Costa\",\"email\":\"marina@example.com\",\"consentimentoTexto\":\"Li e concordo com o documento.\",\"consentimentoVersao\":\"2026-09-01\"}"
for attempt in $(seq 1 30); do
  STATUS=$(request GET "/api/v1/documentos/$DOCUMENT_ID"); [ "$(printf '%s' "$STATUS" | jq -r '.status')" = "completed" ] && break
  sleep 1
done
DOSSIER=$(request GET "/api/v1/documentos/$DOCUMENT_ID/dossie")
DOSSIER_URL=$(printf '%s' "$DOSSIER" | jq -r '.url')
curl --fail-with-body -L "$DOSSIER_URL" -o dossie.pdf

O último evento de assinatura completa o envelope, gera o dossiê e dispara o webhook correspondente. Se a aplicação usar a opção delegada, substitua o fluxo de OTP pormodo: "delegada" somente depois de habilitar essa capability no portal; o evento será rotulado comoauth_level: "delegado_app".