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.
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 levaindPag=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
400comcode: "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). Onumber(<nDup>) é opcional: sem ele a API numera001,002… Omitaduplicatesem 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, 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.
Destaque de IBS/CBS por linha. Depois de autorizada, o detalhe (
GET /v1/fiscal-documents/{id}) devolve em cada itemibsCbs.amounts— base, alíquotas e valores de IBS UF, IBS municipal e CBS calculados na emissão —, e a soma das linhas fecha comtaxTotals.rtcIbsTotal/rtcCbsTotal. Linha sem destaque vem comamounts: null, nunca com zeros. Os grupos que você declarou no POST (ajuste, estorno, crédito presumido da ZFM, item referenciado) voltam emibsCbs.adjustment,creditReversal,zfmPresumedCreditereferencedItem.
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
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-
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 ocest— preenchidos, vencem tudo abaixo. -
A regra do produto — a Regra Fiscal indicada por
taxRuleIdno 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). -
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 } -
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. -
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
taxRuleIdprecisa ser de uma regra da sua empresa — caso contrário a criação responde 422invalid_tax_rule. O mesmo vale para oproductId(422invalid_product) e para umcestque não tenha 7 dígitos (422invalid_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 — 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." }'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 emevents, 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"]
}'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-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 oX-Webhook-Signatureantes 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
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).
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-1234viraABC1234. 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: trueem 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ção | Resposta |
|---|---|
carrierId de outra empresa | 422 — recusado, nunca ignorado em silêncio |
vehiclePlate sem vehicleState | 400 validation_error |
transport numa NFS-e | 400 — serviço não tem <transp> no leiaute |
| Peso ou quantidade negativos | 400 |
Conferindo o que ficou gravado
GET /v1/fiscal-documents/{id} devolve o bloco transport — null 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 umPATCHse a sua operação pedir outro;nullexplí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ção | Resposta |
|---|---|
| CEST sem 7 dígitos | 422 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 nfeConfig | 422 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–100 | 422 invalid_taxation |
| Código já usado por outro produto da empresa | 422 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.