Skip to Content
API de integração — v1
Fluxos de uso

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 objeto client inline no corpo da criação, que cria o cliente na hora (ou reusa um existente quando o document bate). 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 clientname é 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 clientId ou client. A API valida isso antes de processar (validação de schema, não chega a criar nada):

Corpo enviadoResultado
clientId✅ usa o cliente existente (deve ser da sua empresa, senão 422 invalid_client)
client✅ cria/reusa o cliente inline
nenhum dos dois400“Informe clientId (cliente existente) ou client (inline).“
os dois juntos400“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": true no corpo do passo 1 para criar e emitir numa só chamada. A resposta 201 já volta com status: "PROCESSING" e o runId.

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, motivos 0105 — finNFe 5) ou "rtcDebitType" (débito, motivos 0108 — finNFe 6), opcionalmente com "referencedAccessKey" (chave de 44 dígitos da NF-e ajustada, vira refNFe). 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"
statusSignificado
DRAFTRascunho, ainda não emitido
PROCESSINGEm emissão (aguarde)
AUTHORIZEDAutorizada pelo fisco — tem accessKey
REJECTEDRejeitada — veja rejectionReason; corrija e emita de novo
CANCELLEDCancelada

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.pdf

2. Definir os impostos da nota

Há três formas de preencher os impostos dos itens, nesta prioridade:

  1. 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 }
  2. 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).

  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 taxRuleId precisa ser de uma regra da sua empresa — caso contrário a criação responde 422 invalid_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 — enviar nfeTaxRules/ nfseTaxRules substitui 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"] }'
  • url deve ser HTTPS (em produção) e pública — IPs privados/internos são bloqueados (proteção anti-SSRF).
  • events filtra 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 com POST /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

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 client inline direto no POST /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

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 de tenant/system sã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).

Last updated on