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.
| Header | Valor | Obrigatório |
|---|---|---|
| Authorization | Bearer sk_live_… | Sim |
| X-Timestamp | Unix time em segundos | Sim |
| X-Nonce | UUID único por requisição | Sim |
| X-Signature | HMAC-SHA256 em hexadecimal | Sim |
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."
}
}| HTTP | Código | Quando ocorre |
|---|---|---|
| 400 | validacao | Corpo, parâmetro ou header fora do contrato. |
| 401 | chave_invalida | Bearer ausente, inválido, revogado ou aplicação não aprovada. |
| 403 | capability_negada | A aplicação não tem a capability solicitada, como assinatura delegada. |
| 404 | nao_encontrado | Documento, signatário ou dossiê indisponível. |
| 409 | nonce_repetido | O nonce já foi usado; gere um UUID novo. |
| 409 | estado_invalido | A transição não é permitida no estado atual. |
| 429 | rate_limit | Mais 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 inclui
Retry-After. - Replay: timestamp com tolerância de ±300 segundos; cada nonce fica reservado por 10 minutos.
- Paginação:
porPaginaaceita 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.pdfO ú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".