Guias da API
Como integrar a emissão de NF-e, NFC-e e NFS-e do CertFiscal. A referência completa de cada endpoint está no OpenAPI. Base da API: https://api.certfiscal.com.br/v1.
Autenticação & ambientes
Toda requisição é autenticada por API key no header Authorization, no formato Bearer:
# confirma a chave e o ambiente curl https://api.certfiscal.com.br/v1/me \ -H "Authorization: Bearer SUA_CHAVE"
O prefixo da chave indica o ambiente:
| Prefixo | Ambiente | Valor fiscal |
|---|---|---|
cf_test_… | Sandbox (homologação) | Não — notas de teste |
cf_live_… | Produção | Sim — nota fiscal real |
Crie e gerencie chaves no painel (API / Desenvolvedores). A chave aparece uma única vez na criação — guarde em variável de ambiente, nunca no código versionado.
Escopos
Cada chave carrega escopos que limitam o que ela pode fazer. A emissão exige o escopo de escrita do tipo correspondente:
| Escopo | Permite |
|---|---|
nfce:write / nfce:read | Emitir / ler NFC-e |
nfe:write / nfe:read | Emitir / cancelar / ler NF-e (e DANFE, XML, busca) |
nfse:write / nfse:read | Emitir / ler NFS-e |
Quickstart: emitir uma NFC-e
A emissão parte de um pedido de venda. Dois passos:
1. Crie o pedido
curl -X POST https://api.certfiscal.com.br/v1/sales-orders \ -H "Authorization: Bearer SUA_CHAVE" \ -H "Content-Type: application/json" \ -d '{ "companyId": "comp_...", "items": [{ "description": "Produto 1", "unit": "UN", "quantity": 1, "unitPriceCents": 1000 }] }'
Itens usam unitPriceCents (centavos). Veja todos os campos em Emissão por tipo de nota.
2. Emita a NFC-e
curl -X POST https://api.certfiscal.com.br/v1/nfce \ -H "Authorization: Bearer SUA_CHAVE" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "salesOrderId": "so_...", "payments": [{ "tipo": "01", "valor": 10.00 }] }'
A resposta traz a chave de acesso, o protocolo de autorização e o status. Recupere o DANFE em PDF (GET /v1/nfe/{id}/danfe) e o XML autorizado (GET /v1/nfe/{id}/xml).
NF-e (modelo 55) e NFS-e seguem o mesmo padrão em POST /v1/nfe e POST /v1/nfse.
Idempotência
A numeração fiscal é sequencial: um retry de rede sem proteção duplicaria a nota. Por isso toda emissão exige o header Idempotency-Key (um valor único por operação, ex.: um UUID).
- Reenviar a mesma chave com o mesmo corpo retorna o resultado original, sem emitir de novo.
- A janela de idempotência é de 24 horas.
- Os SDKs oficiais geram a chave automaticamente se você não informar.
Erros
Erros vêm com status HTTP adequado e um corpo padronizado:
{ "error": { "code": "missing_scope", "message": "..." } }
| HTTP | code | Significado |
|---|---|---|
| 401 | api_key_required / api_key_invalid | Chave ausente, inválida ou revogada |
| 403 | public_api_not_enabled | Organização sem plano de API ativo |
| 403 | missing_scope | A chave não tem o escopo exigido pelo endpoint |
| 400 | idempotency_key_required | Emissão sem o header Idempotency-Key |
| 400 | validation_error | Corpo inválido (a mensagem detalha o campo) |
| 429 | rate_limit_exceeded | Excedeu o limite de requisições (veja Limites) |
Limites & planos
O rate limit é por chave e definido pelo plano da organização. As respostas trazem os headers:
X-RateLimit-Limit: 300 X-RateLimit-Remaining: 297 Retry-After: 12 # presente no 429
| Plano | Mensal | Documentos/mês | Excedente | Req/min |
|---|---|---|---|---|
| Starter | R$ 79 | 200 | R$ 0,12 | 120 |
| Growth | R$ 249 | 1.500 | R$ 0,10 | 300 |
| Scale | R$ 599 | 6.000 | R$ 0,08 | 600 |
CNPJs ilimitados em todos os planos. Acompanhe o consumo do mês (franquia, excedente) na aba Uso & Planos do painel ou em GET /v1/usage.
Webhooks
Registre uma URL https para receber um POST a cada evento de emissão. Eventos: nfce.issued, nfe.issued, nfse.issued.
Assinatura
Cada entrega traz o header X-CertFiscal-Signature no formato t=<timestamp>,v1=<hex>, onde v1 é o HMAC-SHA256 de timestamp + "." + corpo usando o secret retornado no cadastro. Valide antes de confiar no payload:
// Node.js import { createHmac, timingSafeEqual } from "node:crypto"; function valido(corpoRaw, header, secret) { const [t, v1] = header.split(",").map(p => p.split("=")[1]); const esperado = createHmac("sha256", secret).update(t + "." + corpoRaw).digest("hex"); return timingSafeEqual(Buffer.from(esperado), Buffer.from(v1)); }
Entrega & retry
- Responda
2xxrápido. Falhas são reentregues com backoff (até 6 tentativas). - O histórico de entregas fica em
GET /v1/webhooks/{id}/deliveriese no painel. - Use Testar no painel (ou
POST /v1/webhooks/{id}/test) para um disparo de exemplo.