# AssinAPI — contexto para agentes de programação

AssinAPI é uma API REST de assinatura eletrônica. Você cria o sistema; a AssinAPI cuida de documentos, signatários,
autenticação (CPF + OTP), consentimento, trilha de auditoria, evidências e webhooks.

## Regras obrigatórias
- Base URL: https://sandbox.api.assinapi.com.br/v1  (Sandbox: https://sandbox.api.assinapi.com.br/v1 · Produção: https://api.assinapi.com.br/v1)
- Autenticação: `Authorization: Bearer ${ASSINAPI_SECRET_KEY}` — leia SEMPRE de variável de ambiente no servidor.
- NUNCA exponha a secret key no frontend, navegador, app mobile, logs ou repositório.
- Envie `Idempotency-Key: <uuid>` em todo POST; em retry reutilize a mesma chave.
- Chaves: `ask_test_…` (Sandbox, OTP de teste 123456, CPF de teste 529.982.247-25) e `ask_live_…` (Produção).
- Concentre as chamadas em uma camada server-side (ex.: `assinapi.service`).

## Caminho mais curto (1 chamada)
POST /signature-requests
```json
{
  "title": "Contrato de prestação",
  "document": { "filename": "contrato.pdf", "contentBase64": "<PDF em base64>" },
  "signer": { "name": "João Silva", "email": "joao@email.com", "cpf": "529.982.247-25" },
  "authentication": "EMAIL_OTP",
  "externalId": "id-no-seu-sistema"
}
```
201 → `{ "id": "sigreq_…", "status": "SENT", "envelopeId": "env_…", "signingUrl": "https://…/s/…", "signers": [...] }`
GET /signature-requests/{id} → status atual (DRAFT | SENT | IN_PROGRESS | COMPLETED | DECLINED | EXPIRED | CANCELLED).
Opcional: "signers": [...] (vários), "templateId", "signingOrder": true, "expiresInDays", "notify": false (sem e-mail; use signingUrl).

## API granular
- POST /envelopes { title, message?, signatureLevel?: "SIMPLE"|"ADVANCED", expirationDays?, externalId?, templateId? } → 201 envelope (DRAFT)
- POST /envelopes/{id}/documents — multipart "file" (PDF) ou JSON { contentBase64, filename } ou { documentId }
- POST /envelopes/{id}/signers { name, email, cpf (obrigatório em ADVANCED), phone?, signingOrder?, authenticationMethod?: "EMAIL_OTP"|"SMS_OTP" }
- POST /envelopes/{id}/send { notify?: boolean } → 200 { status: "SENT", signingLinks: [{ signerId, name, signingUrl }] } (links só nesta resposta)
- GET /envelopes/{id} → status + signers[].status (PENDING|INVITED|VIEWED|AUTHENTICATED|SIGNED|DECLINED|EXPIRED)
- POST /envelopes/{id}/cancel { reason? }
- GET /envelopes/{id}/audit → eventos encadeados por hash
- GET /envelopes/{id}/evidence (após COMPLETED) → { downloads: { documents: [{ final, original }], certificate, manifest }, verifyUrl } — URLs expiram em minutos: baixe e armazene.
- POST /public/verify — multipart "file" ou { code } → { result: VALID|ALTERED|UNKNOWN|REVOKED|INVALID_EVIDENCE }

## Webhooks
- POST /webhooks { url, events: ["envelope.completed","signer.signed",...] } → { secret: "whsec_…" } (exibido uma vez; guarde em ASSINAPI_WEBHOOK_SECRET)
- Eventos: envelope.created, envelope.sent, envelope.viewed, signer.authenticated, signer.signed, signer.declined, envelope.completed, envelope.cancelled, envelope.expired
- Headers: X-Signature (v1=<hex>), X-Event-Id, X-Timestamp
- Validação: HMAC_SHA256(secret, `${X-Event-Id}.${X-Timestamp}.${corpo_bruto}`) == X-Signature (tempo constante); rejeite timestamps > 5 min; deduplique por X-Event-Id; responda 2xx rápido.
- Payload envelope.completed: { id, type, data: { envelope: { id, status, externalId }, evidence: { manifestHash, verifyUrl }, documents: [...] } }

## Erros
Formato: { "error": { "code", "message", "cause", "hint", "docs", "requestId" } }. Trate pelo code (estável).
Comuns: VALIDATION_FAILED(422), INVALID_API_KEY(401), INSUFFICIENT_SCOPE(403), NOT_FOUND(404), CPF_REQUIRED(422), INVALID_CPF(422),
ENVELOPE_INCOMPLETE(422), INVALID_STATE_TRANSITION(409), IDEMPOTENCY_KEY_REUSED(422), RATE_LIMITED(429, respeite Retry-After).

## SDK (Node/TypeScript)
```ts
import { AssinAPI } from '@assinapi/sdk';
const assinapi = new AssinAPI({ apiKey: process.env.ASSINAPI_SECRET_KEY });
const req = await assinapi.signatureRequests.create({ title: 'Contrato', file: pdfBuffer, signer: { name, email, cpf } });
const event = assinapi.webhooks.constructEvent({ payload: rawBody, headers, secret: process.env.ASSINAPI_WEBHOOK_SECRET! });
```
