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.

Venda a prazo — parcelas da cobrança. Mande "duplicates" no corpo do passo 1 e a nota sai com o grupo <cobr> (fatura + duplicatas): o XML leva indPag=1 (a prazo) e a DANFE ganha o bloco FATURA / DUPLICATAS, que é o que o comprador usa para lançar no contas a pagar dele.

"duplicates": [ { "dueDate": "2026-09-30", "value": 333.33 }, { "dueDate": "2026-10-30", "value": 333.33 }, { "number": "003", "dueDate": "2026-11-29", "value": 333.34 } ]

A soma das parcelas precisa fechar exatamente com o total da nota — diferença de um centavo volta 400 com code: "invalid_duplicates" e a mensagem dizendo quanto falta ou excede. Ao dividir um valor que não é divisível, jogue o resto na última parcela (R$ 1.000,00 em 3× → 333,33 / 333,33 / 333,34). O number (<nDup>) é opcional: sem ele a API numera 001, 002… Omita duplicates em venda à vista — a nota sai sem o grupo, como antes.

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.

Destaque de IBS/CBS por linha. Depois de autorizada, o detalhe (GET /v1/fiscal-documents/{id}) devolve em cada item ibsCbs.amounts — base, alíquotas e valores de IBS UF, IBS municipal e CBS calculados na emissão —, e a soma das linhas fecha com taxTotals.rtcIbsTotal / rtcCbsTotal. Linha sem destaque vem com amounts: null, nunca com zeros. Os grupos que você declarou no POST (ajuste, estorno, crédito presumido da ZFM, item referenciado) voltam em ibsCbs.adjustment, creditReversal, zfmPresumedCredit e referencedItem.

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

Cada imposto de cada item é resolvido campo a campo, do mais específico ao mais genérico — o primeiro nível que tiver o campo preenchido vence:

item da nota → regra do produto → tributação própria do produto → template fiscal → regras padrão da empresa
  1. O item da nota — o que você manda no item sempre prevalece:

    { "code": "P1", "description": "Produto 1", "cfop": "5102", "unit": "UN", "quantity": 1, "unitValue": 100, "icmsSituation": "00", "icmsRate": 18, "pisSituation": "01", "pisRate": 1.65 }

    Além do CST e das alíquotas, o item aceita os parâmetros do ICMS (icmsRedBc, icmsStModBc, icmsStMva, icmsStRedBc, icmsStRate, icmsSnCreditRate) e o cest — preenchidos, vencem tudo abaixo.

  2. A regra do produto — a Regra Fiscal indicada por taxRuleId no item (ou no documento) ou, sem ela, a vinculada ao produto do item. É casada por UF do destinatário + tipo de cliente + CFOP da operação:

    { "type": "NFE", "clientId": "cli_xxx", "taxRuleId": "rule_abc", "items": [ { "code": "P1", "description": "...", "cfop": "5102", "unit": "UN", "quantity": 1, "unitValue": 100 } ] }

    Crie/liste regras em /v1/tax-rules (ver fluxo 3).

  3. A tributação própria do produto — ligue o item a um produto com productId (ver fluxo 11). O que estiver preenchido no produto é o padrão dele, em qualquer UF; o CEST do produto também. NCM e CFOP ausentes no item vêm do produto.

    { "code": "CAM-01", "description": "Camiseta", "productId": "prod_abc", "quantity": 1, "unitValue": 89.90 }
  4. Template fiscal (templateId, ver fluxo 9) — o ICMS do template é a intenção para aquela operação (bonificação, remessa) e vence as regras padrão da empresa.

  5. Regras padrão da empresa — a configuração tributária da empresa, o nível mais genérico. NCM ausente (no item e no produto) 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. O mesmo vale para o productId (422 invalid_product) e para um cest que não tenha 7 dígitos (422 invalid_cest).

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

ICMS-ST e crédito do Simples

Empresa do Simples Nacional usa CSOSN; as demais usam CST — a sub-regra precisa usar a tabela do regime da empresa. Na substituição tributária (CSOSN 201/202/203, CST 10/30/70) informe a modalidade da base (icmsStModBc: "4" = MVA, "6" = valor da operação), a MVA (na modalidade 4) e a alíquota do ST; CSOSN 101 e 201 exigem a alíquota de crédito do Simples; CST 20 e 70, a redução da base (icmsRedBc):

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 com ST para SP (Simples)", "model": "NFE", "nfeTaxRules": [ { "states": ["SP"], "clientTypes": ["CONTRIBUINTE"], "icmsCst": "201", "icmsStModBc": "4", "icmsStMva": 40, "icmsStRate": 18, "icmsSnCreditRate": 1.25, "pisCst": "49", "cofinsCst": "49" } ] }'

Sub-regra que a emissão recusaria volta 422 invalid_taxation com o motivo na mensagem — por exemplo, "icmsCst": "00" numa empresa do Simples, ou CSOSN 201 sem a MVA. Parâmetro que o código não usa (uma MVA num CSOSN 102) é gravado nulo. O item com ST também precisa de CEST na emissão — no item ou no produto (fluxo 11).

Listar regras

curl "https://api.conttrole.io/v1/tax-rules?model=NFE&isActive=true" \ -H "Authorization: Bearer ck_live_sua_chave"

Operações especiais (bonificação, remessa, demonstração): dê à sub-regra a condição cfops — por exemplo ["5910"]. Ela passa a valer só para itens dessa operação, comparada pelos 3 últimos dígitos (5910 casa com 6910), e tem prioridade sobre as sub-regras sem cfops da mesma regra. CFOP que não tenha 4 dígitos responde 422 invalid_taxation.

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." }'

Comprovante de entrega (canhoto eletrônico)

Para NF-e (modelo 55) autorizada: registra no Ambiente Nacional quem recebeu a mercadoria e quando (evento 110130), com a imagem do comprovante em base64 (JPEG, PNG ou PDF, até 3 MB). A SEFAZ guarda só o hash; a imagem fica guardada pela plataforma.

curl -X POST https://api.conttrole.io/v1/fiscal-documents/doc_abc/delivery-receipt \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "deliveredAt": "2026-09-10T14:30:00-03:00", "receiverName": "Maria da Silva", "receiverDocument": "12345678", "proof": { "contentType": "image/jpeg", "base64": "/9j/4AAQ..." } }'

Com o comprovante registrado, a NF-e não pode ser cancelada e não aceita outro comprovante. Para cancelar a nota, ou trocar o comprovante, cancele-o antes com DELETE /v1/fiscal-documents/{id}/delivery-receipt (evento 110131). O detalhe da nota (GET /v1/fiscal-documents/{id}) lista os eventos em events, com os dados da entrega.

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-Webhook-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).

Até 01/02/2027 o mesmo valor também vai no header legado X-Conttrole-Signature. Migre para o X-Webhook-Signature antes dessa data.

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


10. Transportadoras, frota e transporte na nota

Se a sua operação despacha mercadoria, a nota precisa declarar quem levou e o que foi embarcado — é o grupo <transp> da NF-e, que preenche o quadro TRANSPORTADOR / VOLUMES TRANSPORTADOS da DANFE. Esse quadro é obrigatório no leiaute e por isso sempre impresso: sem os dados, ele sai em branco.

Exige os escopos carriers:read / carriers:write para o cadastro, e documents:write para usar o transporte na nota.

Cadastrar uma transportadora

curl -X POST https://api.conttrole.io/v1/carriers \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Transportadora Rápida Express LTDA", "document": "11.222.333/0001-81", "stateRegistration": "111222333", "anttCode": "12345678", "street": "Av. das Nações", "number": "2000", "city": "São Paulo", "state": "SP" }'

Resposta 201 com a transportadora (incl. id). Documento já cadastrado na empresa retorna 422. Use documentType: "CPF" para transportador autônomo.

Cadastrar os veículos (frota)

Uma transportadora tem frota — o caminhão muda de uma nota para a outra.

curl -X POST https://api.conttrole.io/v1/carriers/carr_abc/vehicles \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "plate": "ABC-1234", "state": "SP", "rntc": "12345678", "description": "Truck baú" }'

Três coisas que valem saber:

  • A placa é normalizada para o formato do XSD ([A-Z0-9], até 7): ABC-1234 vira ABC1234. Formato implausível retorna 422.
  • state é obrigatório. O <UF> do <veicTransp> é enumerado no schema da SEFAZ, então placa sem UF seria rejeição 215 na emissão — recusamos no cadastro, onde ainda dá para corrigir.
  • O primeiro veículo vira o padrão automaticamente. O padrão é o que a nota usa quando você não informa placa nenhuma. Para trocar, mande isDefault: true em outro veículo (o anterior é rebaixado).

Placa repetida na mesma frota retorna 422. Remover o veículo padrão promove outro — a frota nunca fica sem padrão.

# Frota da transportadora (o padrão vem primeiro) curl https://api.conttrole.io/v1/carriers/carr_abc/vehicles \ -H "Authorization: Bearer ck_live_sua_chave"

Emitir a nota com transporte

Mande o objeto transport no POST /v1/fiscal-documents:

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", "items": [ { "code": "P1", "description": "Produto 1", "ncm": "61091000", "unit": "UN", "quantity": 2, "unitValue": 745.0 } ], "transport": { "freightType": "THIRD_PARTY", "freightValue": 250.0, "carrierId": "carr_abc", "volumeQuantity": 12, "volumeSpecies": "CAIXA", "volumeGrossWeight": 340.5, "volumeNetWeight": 320.25 }, "emit": true }'

freightType diz quem paga o frete: SENDER (0, emitente), RECIPIENT (1, destinatário), THIRD_PARTY (2, terceiros), OWN_SENDER (3) e OWN_RECIPIENT (4) para transporte próprio, NO_FREIGHT (9, default).

Cada pedaço é independente. Dá para mandar só a modalidade e os volumes, sem transportadora — é o caso comum de quem despacha por conta do cliente.

Escolhendo o veículo

Sem vehiclePlate, a nota usa o veículo padrão da frota. Para usar outro, informe a placa e a UF na mesma requisição:

"transport": { "carrierId": "carr_abc", "vehiclePlate": "DEF2G34", "vehicleState": "SP" }

Placa sem vehicleState retorna 400 apontando o campo.

Erros que valem antecipar

SituaçãoResposta
carrierId de outra empresa422 — recusado, nunca ignorado em silêncio
vehiclePlate sem vehicleState400 validation_error
transport numa NFS-e400 — serviço não tem <transp> no leiaute
Peso ou quantidade negativos400

Conferindo o que ficou gravado

GET /v1/fiscal-documents/{id} devolve o bloco transportnull quando a nota não tem transporte:

{ "id": "doc_abc", "transport": { "freightType": "THIRD_PARTY", "freightValue": "250.00", "carrierId": "carr_abc", "carrierName": "Transportadora Rápida Express LTDA", "carrierDocument": "11222333000181", "vehiclePlate": "ABC1D23", "vehicleState": "SP", "volumeQuantity": 12, "volumeSpecies": "CAIXA", "volumeGrossWeight": "340.500", "volumeNetWeight": "320.250" } }

carrierName e carrierDocument são snapshot: se a transportadora for excluída depois, a nota continua dizendo de quem era o frete. Pelo mesmo motivo, remover um veículo da frota não altera notas já emitidas — elas guardam a placa que foi usada.


11. Produtos e tributação do produto

O produto guarda a tributação própria: CST/CSOSN e alíquota do ICMS, ICMS-ST, crédito do Simples, PIS/COFINS/IPI e o CEST. Ela vale em toda venda do produto, em qualquer UF e para qualquer cliente. Campo em branco no produto cai na regra tributária — e o que a nota mandar no item sempre prevalece (ver fluxo 2).

Exige os escopos products:read / products:write, e documents:write para usar o produto na nota.

Cadastrar um produto com ST (Simples Nacional)

Exemplo ilustrativo — confirme NCM, CEST, MVA e alíquotas com o seu contador:

curl -X POST https://api.conttrole.io/v1/products \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Refrigerante lata 350 ml", "code": "REF-350", "salePrice": 4.50, "nfeConfig": { "ncm": "22021000", "cest": "03.007.00", "internalCfop": "5403", "icmsCst": "201", "icmsStModBc": "4", "icmsStMva": 40, "icmsStRate": 18, "icmsSnCreditRate": 1.25, "pisCst": "49", "cofinsCst": "49" } }'

Resposta 201 com o produto. O que vale conferir nela:

  • Os CFOPs interestaduais que você não mandou são sugeridos a partir do interno ("interstateContributor": "6403", "interstateNonContributor": "6403" — para 5102, seriam 6102 e 6108). Troque-os com um PATCH se a sua operação pedir outro; null explícito deixa em branco.
  • O CEST sai só com dígitos ("0300700").
  • Os percentuais saem como string decimal ("icmsStMva": "40.0000").
  • Parâmetro que o código não usa volta nulo: aqui, icmsRedBc.

Sem commercialUnit/taxUnit/taxUnitExport, o produto nasce com UN - UNIDADE / UN - UNIDADE / KG - QUILOGRAMA.

SituaçãoResposta
CEST sem 7 dígitos422 invalid_cest
CFOP no campo errado (interno 5xxx, interestadual 6xxx, exportação 7xxx)422 invalid_cfop
taxRuleId de outra empresa, removida, ou de NFS-e em nfeConfig422 invalid_tax_rule
CST da tabela errada para o regime, ST sem MVA/alíquota, CSOSN 101/201 sem crédito, percentual fora de 0–100422 invalid_taxation
Código já usado por outro produto da empresa422 duplicate_code

Alterar parte da tributação (PATCH)

Campo não enviado mantém o valor gravado; null explícito limpa. Na tributação, o que você manda é mesclado com o que está gravado, e o resultado é validado — este PATCH troca o PIS sem apagar o CSOSN nem o ST:

curl -X PATCH https://api.conttrole.io/v1/products/prod_abc \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "nfeConfig": { "pisCst": "01", "pisRate": 0.65 } }'

Trocar o icmsCst de "201" para "102" descarta a MVA/ST, que o 102 não usa. Um PATCH sem nenhum campo de imposto (só ncm, por exemplo) não mexe na tributação.

Usar o produto na nota

Ligue o item ao produto com productId. NCM e CFOP ausentes no item vêm do produto; a tributação própria e o CEST dele valem na emissão:

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", "items": [ { "productId": "prod_abc", "code": "REF-350", "description": "Refrigerante lata 350 ml", "quantity": 24, "unitValue": 4.50 } ] }'

Qualquer campo de imposto mandado no item vence o produto — inclusive os parâmetros do ST (icmsStMva, icmsStRate…) e o cest. Produto de outra empresa responde 422 invalid_product.

Last updated on