Fluxos de uso
Guias ponta a ponta dos cenários mais comuns da API. Os exemplos usam curl e a
base https://api.conttrole.io; troque pela sua URL de integração se tiver um
domínio próprio. Toda chamada autenticada leva o header
Authorization: Bearer ck_live_sua_chave (ver Autenticação).
O detalhe de cada campo (tipos, enums, obrigatoriedade) está na Referência da API. Aqui o foco é a ordem das chamadas.
1. Emitir uma nota fiscal
A emissão tem dois momentos: criar o documento (rascunho, síncrono) e emitir (processamento assíncrono junto ao fisco). Você acompanha pelo status.
Passo 0 — pré-requisitos
- Cliente: identifique o destinatário de uma de duas formas —
clientId(um cliente que já existe na sua empresa; cadastre pelo painel ou pela API de clientes) ou um objetoclientinline no corpo da criação, que cria o cliente na hora (ou reusa um existente quando odocumentbate). Use um dos dois, nunca os dois juntos. - Empresa configurada: certificado digital e configurações fiscais válidas no painel — sem isso a emissão é rejeitada.
Passo 1 — criar o documento
Com um cliente já cadastrado, informe o clientId:
curl -X POST https://api.conttrole.io/v1/fiscal-documents \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"type": "NFE",
"clientId": "cli_xxx",
"operationNature": "Venda de mercadoria",
"items": [
{ "code": "P1", "description": "Produto 1", "cfop": "5102",
"ncm": "61091000", "unit": "UN", "quantity": 2, "unitValue": 50.0 }
],
"payments": [
{ "method": "PIX", "value": 100.0 }
]
}'Se preferir não cadastrar o cliente antes, mande um objeto client inline no
lugar do clientId — a API cria o cliente junto da nota (ou reusa o já
existente quando o document casa, tornando a chamada idempotente):
curl -X POST https://api.conttrole.io/v1/fiscal-documents \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"type": "NFE",
"client": {
"name": "Maria Silva",
"document": "123.456.789-09",
"email": "maria@exemplo.com",
"zipCode": "01001-000",
"street": "Praça da Sé", "number": "100",
"neighborhood": "Sé", "city": "São Paulo", "state": "SP",
"municipalityCode": "3550308"
},
"operationNature": "Venda de mercadoria",
"items": [
{ "code": "P1", "description": "Produto 1", "cfop": "5102",
"ncm": "61091000", "unit": "UN", "quantity": 2, "unitValue": 50.0 }
],
"payments": [
{ "method": "PIX", "value": 100.0 }
]
}'No objeto client só name é obrigatório. type (INDIVIDUAL/COMPANY/FOREIGN) e
documentType (CPF/CNPJ/FOREIGN/OTHER) são inferidos pelo documento quando
omitidos (14 dígitos = CNPJ/empresa, 11 = CPF/pessoa física). O document pode
até vir vazio (consumidor final) — a SEFAZ aceita NF-e sem identificação do
destinatário. Para NFS-e/NF-e com destinatário identificado, mande o endereço
completo, senão a emissão pode ser rejeitada.
Obrigatório: exatamente um de
clientIdouclient. A API valida isso antes de processar (validação de schema, não chega a criar nada):
Corpo enviado Resultado só clientId✅ usa o cliente existente (deve ser da sua empresa, senão 422 invalid_client)só client✅ cria/reusa o cliente inline nenhum dos dois ❌ 400 — “Informe clientId (cliente existente) ou client (inline).“ os dois juntos ❌ 400 — “Envie apenas um: clientId OU client, não os dois.”
Resposta 201 — o documento nasce em DRAFT (ainda sem número definitivo):
{ "id": "doc_abc", "type": "NFE", "status": "DRAFT", "number": 0, "runId": null }Passo 2 — emitir
curl -X POST https://api.conttrole.io/v1/fiscal-documents/doc_abc/emit \
-H "Authorization: Bearer ck_live_sua_chave"Resposta 202 com runId — o documento entra em PROCESSING e o trabalho roda
em segundo plano:
{ "documentId": "doc_abc", "status": "PROCESSING", "runId": "run_..." }Atalho: envie
"emit": trueno corpo do passo 1 para criar e emitir numa só chamada. A resposta 201 já volta comstatus: "PROCESSING"e orunId.
Reforma Tributária — notas de crédito/débito (ajuste de IBS/CBS). Para emitir um documento de ajuste (NT 2025.002), envie no corpo do passo 1
"rtcCreditType"(crédito, motivos01–05— finNFe 5) ou"rtcDebitType"(débito, motivos01–08— finNFe 6), opcionalmente com"referencedAccessKey"(chave de 44 dígitos da NF-e ajustada, virarefNFe). Só NF-e (modelo 55); a nota sai sem cobrança (tPag=90). Os motivos estão descritos na referência da API.
Passo 3 — acompanhar o status
Há duas formas de saber o desfecho da emissão. Prefira webhooks: cadastre um
endpoint uma vez e a API te avisa com um POST assinado (document.authorized /
document.rejected) assim que a nota muda de estado — sem ficar consultando.
Veja Receber eventos via webhook.
Quando webhooks não forem viáveis, use polling como alternativa: consulte o
documento até sair de PROCESSING.
curl https://api.conttrole.io/v1/fiscal-documents/doc_abc \
-H "Authorization: Bearer ck_live_sua_chave"status | Significado |
|---|---|
DRAFT | Rascunho, ainda não emitido |
PROCESSING | Em emissão (aguarde) |
AUTHORIZED | Autorizada pelo fisco — tem accessKey |
REJECTED | Rejeitada — veja rejectionReason; corrija e emita de novo |
CANCELLED | Cancelada |
Ambiente: produção × homologação
Todo documento tem um campo environment (PRODUCTION ou HOMOLOGATION).
Nota de homologação é teste — não tem valor fiscal — e sai da SEFAZ/prefeitura
pelo ambiente de testes. Qual ambiente a nota usa é definido nas configurações
fiscais da empresa, não pela API.
A listagem GET /v1/fiscal-documents devolve apenas produção por padrão. Para
ver as de teste, use o parâmetro environment:
# só produção (padrão — não precisa passar nada)
curl "https://api.conttrole.io/v1/fiscal-documents" \
-H "Authorization: Bearer ck_live_sua_chave"
# só homologação
curl "https://api.conttrole.io/v1/fiscal-documents?environment=HOMOLOGATION" \
-H "Authorization: Bearer ck_live_sua_chave"
# os dois misturados
curl "https://api.conttrole.io/v1/fiscal-documents?environment=ALL" \
-H "Authorization: Bearer ck_live_sua_chave"O detalhe (GET /v1/fiscal-documents/{id}) sempre devolve o documento pedido,
independente do ambiente — o campo environment na resposta diz qual é.
Tanto o detalhe (GET /v1/fiscal-documents/{id}) quanto a listagem
(GET /v1/fiscal-documents) trazem um objeto client com
{ id, name, document, documentType } — o id é o do cadastro (use em
GET /v1/clients/{id}) e nome/documento são o snapshot gravado na emissão (ficam
íntegros mesmo se o cliente for editado/removido depois).
Passo 4 — baixar XML e DANFE
Depois de AUTHORIZED:
# XML autorizado (application/xml)
curl https://api.conttrole.io/v1/fiscal-documents/doc_abc/xml \
-H "Authorization: Bearer ck_live_sua_chave" -o nota.xml
# DANFE/DANFSe em PDF (application/pdf)
curl https://api.conttrole.io/v1/fiscal-documents/doc_abc/danfe \
-H "Authorization: Bearer ck_live_sua_chave" -o nota.pdf2. Definir os impostos da nota
Há três formas de preencher os impostos dos itens, nesta prioridade:
-
Explícito no item — você manda os campos tributários direto:
{ "code": "P1", "description": "Produto 1", "cfop": "5102", "unit": "UN", "quantity": 1, "unitValue": 100, "icmsSituation": "00", "icmsRate": 18, "pisSituation": "01", "pisRate": 1.65 } -
Regra tributária — referencie uma regra por
taxRuleId, no documento (vale pra todos os itens) ou no item (sobrepõe a do documento):{ "type": "NFE", "clientId": "cli_xxx", "taxRuleId": "rule_abc", "items": [ { "code": "P1", "description": "...", "cfop": "5102", "unit": "UN", "quantity": 1, "unitValue": 100 } ] }A regra é casada por UF do destinatário + tipo de cliente. Crie/liste regras em
/v1/tax-rules(ver fluxo 3). -
Configuração da empresa — sem imposto no item e sem
taxRuleId, vale a configuração tributária padrão da empresa. NCM ausente também herda o da empresa.
O
taxRuleIdprecisa ser de uma regra da sua empresa — caso contrário a criação responde 422invalid_tax_rule.
3. Criar e usar regras tributárias
Regras tributárias evitam repetir impostos em cada nota. Requer escopo
tax-rules:write para criar e tax-rules:read para listar.
Criar uma regra (NF-e)
curl -X POST https://api.conttrole.io/v1/tax-rules \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"name": "Venda dentro de SP",
"model": "NFE",
"nfeTaxRules": [
{ "states": ["SP"], "clientTypes": ["CONTRIBUINTE"],
"icmsCst": "00", "icmsPIcms": 18,
"pisCst": "01", "pisPPis": 1.65,
"cofinsCst": "01", "cofinsPCofins": 7.6 }
]
}'Resposta 201 com o id da regra — use esse id como taxRuleId na criação
da nota (fluxo 2).
Listar regras
curl "https://api.conttrole.io/v1/tax-rules?model=NFE&isActive=true" \
-H "Authorization: Bearer ck_live_sua_chave"
PATCH /v1/tax-rules/{id}atualiza a regra — enviarnfeTaxRules/nfseTaxRulessubstitui integralmente as sub-regras daquele modelo.DELETE /v1/tax-rules/{id}faz soft delete.
4. Cancelar uma nota autorizada
Síncrono — a API chama o fisco e devolve o resultado. A justificativa tem de ter 15 a 255 caracteres (regra SEFAZ) e o cancelamento respeita o prazo legal.
curl -X POST https://api.conttrole.io/v1/fiscal-documents/doc_abc/cancel \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{ "justification": "Cancelamento a pedido do cliente." }'5. Corrigir uma nota (Carta de Correção)
Para NF-e/NFC-e autorizadas: evento 110110, síncrono. Texto de 15 a 1000 caracteres, prazo de 30 dias, máximo de 20 correções. Não corrige valores fiscais nem dados de emitente/destinatário, e não se aplica a NFS-e.
curl -X POST https://api.conttrole.io/v1/fiscal-documents/doc_abc/correction-letter \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{ "correction": "Correção do endereço de entrega do produto." }'6. Inutilizar uma faixa de numeração
Quando um intervalo de números de NF-e/NFC-e foi pulado e precisa ser declarado como inutilizado:
curl -X POST https://api.conttrole.io/v1/fiscal-inutilizations \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{ "series": 1, "startNumber": 10, "endNumber": 20,
"justification": "Numeração pulada por falha de sistema." }'7. Receber eventos via webhook (sem polling)
Em vez de consultar o status repetidamente, cadastre um endpoint e receba um
POST assinado quando a nota muda de estado. Requer escopo webhooks:write para
criar e webhooks:read para listar/consultar entregas.
Passo 1 — criar o endpoint
curl -X POST https://api.conttrole.io/v1/webhooks \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"url": "https://seu-app.com/webhooks/conttrole",
"description": "Eventos fiscais de produção",
"events": ["DOCUMENT_AUTHORIZED", "DOCUMENT_REJECTED"]
}'urldeve ser HTTPS (em produção) e pública — IPs privados/internos são bloqueados (proteção anti-SSRF).eventsfiltra o que você recebe; vazio = todos. Eventos disponíveis:DOCUMENT_AUTHORIZED,DOCUMENT_REJECTED,DOCUMENT_CANCELLED,DOCUMENT_INUTILIZED.
Resposta 201 — o secret (whsec_…) vem uma única vez; guarde-o para
validar as assinaturas:
{ "id": "ep_abc", "url": "https://seu-app.com/webhooks/conttrole",
"events": ["DOCUMENT_AUTHORIZED", "DOCUMENT_REJECTED"],
"isActive": true, "secret": "whsec_xxxxxxxx" }Perdeu o
secret? Gere outro comPOST /v1/webhooks/{id}/rotate-secret(o antigo deixa de valer).
Passo 2 — validar a assinatura no seu endpoint
Cada entrega traz o header X-Conttrole-Signature: t=<timestamp>,v1=<hmac>, onde
hmac é o HMAC-SHA256 de "<timestamp>.<corpo-cru>" usando o seu secret.
Recalcule e compare (comparação em tempo constante):
import crypto from "node:crypto";
function isValid(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((kv) => kv.split("=")),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
);
}Responda 2xx rapidamente. Em falha (timeout ou status ≠ 2xx) a entrega é reagendada automaticamente com backoff (até 20 tentativas / 24h).
Passo 3 — testar e inspecionar entregas
# Dispara um evento de teste para o endpoint
curl -X POST https://api.conttrole.io/v1/webhooks/ep_abc/test \
-H "Authorization: Bearer ck_live_sua_chave"
# Histórico de entregas (status, tentativas)
curl https://api.conttrole.io/v1/webhooks/ep_abc/deliveries \
-H "Authorization: Bearer ck_live_sua_chave"
# Reenviar manualmente uma entrega que falhou
curl -X POST https://api.conttrole.io/v1/webhooks/ep_abc/deliveries/del_xyz/redeliver \
-H "Authorization: Bearer ck_live_sua_chave"Para editar (PATCH /v1/webhooks/{id}), remover (DELETE) e os demais detalhes
de segurança e payload dos eventos, veja Webhooks.
8. Gerenciar clientes
CRUD completo dos clientes (tomadores/destinatários) da empresa. O id retornado
é o clientId que você usa ao criar uma nota. Exige
os escopos clients:read (leitura) e clients:write (escrita).
Criar um cliente
Só name é obrigatório. type (INDIVIDUAL/COMPANY/FOREIGN) e documentType
(CPF/CNPJ/FOREIGN/OTHER) são inferidos pelo documento quando omitidos.
curl -X POST https://api.conttrole.io/v1/clients \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"name": "ACME LTDA",
"document": "12.345.678/0001-99",
"email": "fiscal@acme.com",
"zipCode": "01001-000",
"street": "Praça da Sé", "number": "100",
"neighborhood": "Sé", "city": "São Paulo", "state": "SP",
"municipalityCode": "3550308"
}'Resposta 201 com o cliente criado (incl. id). Documento já cadastrado na
empresa retorna 422.
Listar e buscar
# Lista paginada (page, pageSize) com filtros opcionais
curl "https://api.conttrole.io/v1/clients?search=acme&state=SP&page=1&pageSize=20" \
-H "Authorization: Bearer ck_live_sua_chave"search casa por nome, documento, email ou nome fantasia. Também dá pra filtrar
por documentType e isActive.
Detalhar, atualizar e remover
# Detalhe
curl https://api.conttrole.io/v1/clients/cli_abc \
-H "Authorization: Bearer ck_live_sua_chave"
# Atualiza só os campos enviados
curl -X PATCH https://api.conttrole.io/v1/clients/cli_abc \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{ "email": "novo@acme.com" }'
# Remove (409 se o cliente já tiver notas vinculadas)
curl -X DELETE https://api.conttrole.io/v1/clients/cli_abc \
-H "Authorization: Bearer ck_live_sua_chave"Atalho: se você emite para um cliente novo e não quer um passo separado de cadastro, mande o objeto
clientinline direto noPOST /v1/fiscal-documents— ele cria/reusa o cliente junto da nota.
9. Templates fiscais
Um template é um modelo reutilizável que pré-preenche a natureza da operação,
o CFOP/ICMS (NF-e) ou o código de serviço (NFS-e) de uma nota. O id do template
é aplicado à venda pelo campo templateId em
POST /v1/fiscal-documents. Escopos
templates:read / templates:write.
Listar os templates disponíveis
A empresa enxerga três origens (campo source): os da própria empresa
(company), os do provedor/white-label (tenant) e os da Conttrole
(system).
curl "https://api.conttrole.io/v1/fiscal-templates?type=NFE" \
-H "Authorization: Bearer ck_live_sua_chave"Criar um template da empresa
Só name e type (NFE|NFSE) são obrigatórios; os demais campos dependem do
modelo (CFOP/ICMS para NF-e; código de serviço para NFS-e).
curl -X POST https://api.conttrole.io/v1/fiscal-templates \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"name": "Venda de mercadoria SP",
"type": "NFE",
"operationNature": "Venda de mercadoria",
"cfop": "5102",
"icmsOrigin": "0", "icmsCst": "00", "icmsRate": 18
}'
PATCH/DELETE /v1/fiscal-templates/{id}só funcionam nos templates da sua empresa (source: "company"). Os detenant/systemsão somente-leitura (retornam 404 na escrita).
Aplicar o template numa venda
Passe templateId ao criar o documento. O template preenche apenas os campos
que você não enviou (fill-empty) — valores explícitos no item/documento sempre
vencem:
curl -X POST https://api.conttrole.io/v1/fiscal-documents \
-H "Authorization: Bearer ck_live_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"type": "NFE",
"clientId": "cli_xxx",
"templateId": "tpl_abc",
"items": [
{ "code": "P1", "description": "Produto 1", "ncm": "61091000",
"unit": "UN", "quantity": 2, "unitValue": 50.0 }
]
}'Aqui o cfop, o ICMS e a natureza da operação vêm do template. Um templateId
inexistente/invisível retorna 422 invalid_template; um template de tipo
incompatível com o documento retorna 422 template_type_mismatch (NF-e/NFC-e
usam template NFE; NFS-e usa NFSE).