Aceitou — API Reference

API REST para integração com a plataforma de assinatura eletrônica Aceitou.

Base URL produção: https://api.aceitou.com.br Base URL demo: https://api-demo.aceitou.com.br

Sumário


Autenticação

JWT (Bearer token)

Para usuários humanos (portal). Obtenha o token via POST /auth/login:

curl -X POST https://api.aceitou.com.br/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"voce@empresa.com","password":"sua-senha"}'

Resposta:

{
  "accessToken": "eyJhbGc...",
  "refreshToken": "eyJhbGc..."
}

Use em requisições subsequentes:

curl https://api.aceitou.com.br/api/v1/documents \
  -H "Authorization: Bearer eyJhbGc..."

O accessToken expira em 5 minutos. Renove com POST /auth/refresh:

curl -X POST https://api.aceitou.com.br/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refreshToken":"eyJhbGc..."}'

API Key

Para integrações server-to-server. Gere uma chave em Configurações → Chaves de API no portal, ou via:

curl -X POST https://api.aceitou.com.br/api/v1/api-keys \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Integração Backoffice","scopes":["documents:read","documents:write"]}'

A chave (ak_live_...) é exibida uma única vez na resposta. Guarde com cuidado.

Use em requisições com o header X-Api-Key:

curl https://api.aceitou.com.br/api/v1/documents \
  -H "X-Api-Key: ak_live_xxxxxxxxxxx"

Escopos

Cada API key tem escopos que limitam o que ela pode fazer. Tente sempre dar o mínimo necessário.

Escopo Permite
documents:read Listar e ler documentos
documents:write Criar, enviar, cancelar documentos
templates:read Listar e ler templates
templates:write Criar, atualizar, excluir templates
webhooks:read Listar webhooks
webhooks:write Criar, atualizar, excluir, rotacionar segredo
api_keys:read Listar chaves de API
api_keys:write Gerar e revogar chaves
* Todos (use com cautela)

Chaves sem o escopo apropriado recebem 403 Forbidden com:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.4",
  "title": "Forbidden",
  "status": 403,
  "detail": "A chave de API não possui o escopo 'documents:write' necessário para este recurso.",
  "requiredScope": "documents:write"
}

Documentos

Em todos os payloads, documentId é o identificador interno do envelope (não confunda com o arquivo PDF anexado).

Criar documento

curl -X POST https://api.aceitou.com.br/api/v1/documents \
  -H "X-Api-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Contrato de prestação de serviços"}'

Resposta 201:

{ "id": "12345", "status": "Draft" }

Anexar PDF original

curl -X POST https://api.aceitou.com.br/api/v1/documents/12345/documents \
  -H "X-Api-Key: $KEY" \
  -F "file=@contrato.pdf;type=application/pdf"

Adicionar signatário

curl -X POST https://api.aceitou.com.br/api/v1/documents/12345/signers \
  -H "X-Api-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Maria Silva",
    "email": "maria@cliente.com",
    "verificationMethod": "simple",
    "signingOrder": 1
  }'

verificationMethod aceita: simple (clique apenas), email_token (OTP por e-mail), sms_token, email_and_sms.

signingOrder define a ordem sequencial: signatários com mesmo número assinam em paralelo; números maiores só recebem o convite após todos os menores assinarem.

Enviar para assinatura

curl -X POST https://api.aceitou.com.br/api/v1/documents/12345/send \
  -H "X-Api-Key: $KEY"

Dispara os e-mails de convite e o webhook document_sent.

Baixar versões

# PDF original
curl -O -J https://api.aceitou.com.br/api/v1/documents/12345/download?version=original \
  -H "X-Api-Key: $KEY"

# PDF assinado (gerado automaticamente quando todos assinam)
curl -O -J https://api.aceitou.com.br/api/v1/documents/12345/download?version=signed \
  -H "X-Api-Key: $KEY"

Trilha de auditoria

curl https://api.aceitou.com.br/api/v1/documents/12345/audit-trail \
  -H "X-Api-Key: $KEY"

Retorna eventos: document_sent, document_signed, document_rejected, document_completed, etc.


Templates

Cada tenant nasce com 4 templates pré-feitos (CLT, Locação, NDA, Prestação de Serviços). Você pode criar os seus:

curl -X POST https://api.aceitou.com.br/api/v1/templates \
  -H "X-Api-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Contrato XPTO",
    "htmlContent": "<h1>Contrato com {{nome}}</h1><p>CPF: {{cpf}}</p>",
    "variables": ["nome","cpf"]
  }'

Gerar documento a partir de template

curl -X POST https://api.aceitou.com.br/api/v1/documents/12345/documents/from-template \
  -H "X-Api-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "9",
    "variables": { "nome": "Maria Silva", "cpf": "111.222.333-44" }
  }'

O backend gera o PDF substituindo as variáveis e o anexa ao envelope.


Webhooks

Cadastrar

curl -X POST https://api.aceitou.com.br/api/v1/webhooks \
  -H "X-Api-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://meu-app.com/webhooks/aceitou",
    "events": ["document_sent","document_completed","signer_signed"]
  }'

Resposta 201:

{
  "id": 7,
  "secret": "abc123def456..."
}

O secret é exibido apenas uma vez. Guarde-o — você precisará dele para validar o HMAC. Para gerar um novo (rotação), use POST /api/v1/webhooks/{id}/rotate-secret.

Verificação HMAC

Toda entrega traz dois headers:

X-Aceitou-Signature: sha256=<hex>
X-Aceitou-Event: document_sent

A signature é HMAC-SHA256(secret, body) em hex lowercase. Sempre valide antes de processar:

Node.js

import crypto from 'node:crypto';

function verify(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');
  // constant-time compare
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express
app.post('/webhooks/aceitou', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.header('X-Aceitou-Signature');
  if (!sig || !verify(req.body, sig, process.env.ACEITOU_WEBHOOK_SECRET)) {
    return res.status(401).send('invalid signature');
  }
  const event = req.header('X-Aceitou-Event');
  const payload = JSON.parse(req.body.toString('utf8'));
  // ... processe
  res.status(200).send();
});

Python

import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature_header, expected)

PHP

function verify($raw_body, $signature_header, $secret) {
    $expected = 'sha256=' . hash_hmac('sha256', $raw_body, $secret);
    return hash_equals($signature_header, $expected);
}

Eventos

Evento Quando dispara Payload (campos chave)
document_sent Envelope sai do status Draft para Sent documentId, title, status, signers[], sentAt
signer_signed Cada signatário assina documentId, title, signer{id,name,email}, signedAt
document_completed Todos os signatários assinaram documentId, title, status, completedAt
document_rejected Um signatário recusa documentId, signer, rejectionReason
document_cancelled Dono cancela o envelope documentId, cancelledAt
document_expired ExpiresAt passa antes de todos assinarem documentId, expiredAt

Retry e dead-letter

A entrega tenta 1, 5, 15, 60, 360 minutos antes de marcar como Failed. Você pode replay manualmente em Configurações → Webhooks → Entregas.


Códigos de erro

Todos os erros seguem o formato RFC 7807 Problem Details:

{
  "type": "https://tools.ietf.org/html/rfc4918#section-11.2",
  "title": "auth.invalid_credentials",
  "status": 422,
  "detail": "Invalid email or password.",
  "traceId": "00-abc...-01"
}

Códigos comuns:

Código Significado
auth.invalid_credentials Email ou senha errados
auth.invalid_refresh_token Refresh expirado/inválido
auth.tenant_required Token sem tenant_id
document.not_found Documento não existe ou não pertence a este tenant
document.invalid_status Operação inválida no status atual (ex.: cancelar um Completed)
signing.token_expired Link de assinatura expirou
signing.invalid_status Documento já assinado ou rejeitado
webhook.not_found Webhook não existe ou não pertence ao tenant
subscription.not_found Tenant sem assinatura ativa (raro — fail-safe)
trial_expired Trial encerrou e não há plano ativo (HTTP 402)

Limites de plano

Cada plano define limites máximos que o tenant pode atingir:

Limite Onde aplica
MaxDocumentsPerMonth Criação de envelopes (POST /documents)
MaxTemplates Criação de templates (POST /templates)
MaxWebhooks Cadastro de webhooks
MaxUsers Convite de membros adicionais

Ao atingir, a API retorna 400 Bad Request com title: "plan.limit_reached" (erro de regra de negócio — não confundir com o 402 Payment Required do trial/assinatura expirada, acima). Durante o trial, o limite (Billing:TrialDocumentLimit) é sinalizado com title: "plan.trial_limit_reached", também 400.