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 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 clientId ou client. A API valida isso antes de processar (validação de schema, não chega a criar nada):

Corpo enviadoResultado
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": 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 01–05 — finNFe 5) ou "rtcDebitType" (débito, motivos 01–08 — 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

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

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

12. Notas recebidas (DF-e) e manifestação

As notas que os fornecedores emitem contra o CNPJ da sua empresa chegam sozinhas: a Conttrole consulta a Distribuição DF-e da SEFAZ (NF-e) e o Ambiente Nacional (NFS-e do Padrão Nacional) a cada hora, com o certificado A1 da empresa. Pela API você lista essas notas, baixa XML e DANFE e manifesta o destinatário (MD-e).

Os escopos são received-documents:read e received-documents:write, e precisam estar literalmente na chave — uma chave sem escopos não entra (ver Autenticação).

Listar o que chegou

# Tudo, do NSU mais novo para o mais antigo curl "https://api.conttrole.io/v1/received-documents?page=1&pageSize=50" \ -H "Authorization: Bearer ck_live_sua_chave" # Só a fila de manifestação: NF-e com chave e ainda sem manifestação curl "https://api.conttrole.io/v1/received-documents?pending=true" \ -H "Authorization: Bearer ck_live_sua_chave" # De um fornecedor, num período (datas em horário de Brasília) curl "https://api.conttrole.io/v1/received-documents?issuerCnpj=12345678000190&emittedFrom=2026-09-01&emittedTo=2026-09-30" \ -H "Authorization: Bearer ck_live_sua_chave"

Filtros: model (NFE/NFSE), schemaType, manifestation, accessKey, issuerCnpj, search (trecho da chave, do CNPJ ou do nome) e o período de emissão. O totalValue vem como string decimal ("1500.50").

Resumo × nota completa

A SEFAZ entrega a NF-e em duas etapas, e o campo schemaType diz em qual você está:

schemaTypeO que éXMLDANFE
RES_NFESó o resumo (emitente, valor, chave)o resumo❌ 409
PROC_NFEA nota completao nfeProc inteiro✅
RES_EVENTO / PROC_EVENTOUm evento da nota (ex.: cancelamento)o evento❌ 409
NFSE / EVENTO_NFSENFS-e do Padrão Nacional e seus eventoscompleto✅ só a NFSE

Para o resumo virar nota completa, manifeste a Ciência da Operação: a SEFAZ libera o XML e ele chega na próxima distribuição, na mesma linha (mesmo id), com hasFullXml: true. O detalhe (GET /v1/received-documents/{id}) traz um bloco pdf dizendo se a DANFE já pode ser gerada — e, se não, por quê.

curl https://api.conttrole.io/v1/received-documents/rd_xxx/xml \ -H "Authorization: Bearer ck_live_sua_chave" -o nota.xml curl https://api.conttrole.io/v1/received-documents/rd_xxx/danfe \ -H "Authorization: Bearer ck_live_sua_chave" -o danfe.pdf

Baixar o mês inteiro (lote)

Todas as notas recebidas de um mês num ZIP só, sem paginar nota a nota — é o pacote que vai para a escrituração:

curl "https://api.conttrole.io/v1/received-documents/export?year=2026&month=8" \ -H "Authorization: Bearer ck_live_sua_chave" -o notas-recebidas-2026-08.zip

O ZIP tem notas/ (NF-e completas e NFS-e) e eventos/ (cancelamentos, CC-e, manifestações), um XML por documento, com o nome <chave>-nsu<NSU>.xml. O mês é o de emissão, no horário de Brasília.

ParâmetroDefaultO que muda
year, month—Obrigatórios. month vai de 1 a 12.
notestrueNF-e completas e NFS-e
eventstrueEventos das notas
summariesfalseOs resumos das NF-e ainda não manifestadas, em resumos/

Os headers chegam antes do arquivo: X-Document-Count (quantos documentos entram) e X-Pending-Summaries — NF-e das quais só existe o resumo. Se for maior que zero, faltam notas completas no pacote: manifeste a CIENCIA e sincronize antes de fechar o mês. Mês sem nenhum documento responde 404 no_documents.

O ZIP é montado enquanto é baixado, então não há limite de notas — mas também não dá para voltar atrás no meio: se algo falhar durante o envio, a conexão é cortada (nunca entregamos um ZIP fechado faltando notas). Trate download interrompido como falha e repita.

Quando quem vai baixar é uma pessoa (ou um agente de IA, que não tem onde pôr um ZIP), peça um link: ele abre direto no navegador, sem header de autenticação.

curl -X POST https://api.conttrole.io/v1/received-documents/export/link \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "year": 2026, "month": 8 }'
{ "url": "https://api.conttrole.io/v1/received-documents/export/download?token=…", "fileName": "notas-recebidas-2026-08.zip", "expiresAt": "2026-09-24T18:41:16.982Z", "documentCount": 250, "companyCount": 1, "pendingSummaries": 2 }

O link vale 15 minutos e pode ser aberto mais de uma vez nesse prazo. Ele leva junto a credencial que o pediu, e para de funcionar na hora se a chave for revogada (403 link_revoked) — trate-o como a própria chave: não publique. É o que o MCP usa na ferramenta get_received_documents_export_link.

Manifestar

curl -X POST https://api.conttrole.io/v1/received-documents/rd_xxx/manifestation \ -H "Authorization: Bearer ck_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "type": "CIENCIA" }'
typeEventoO que declara
CIENCIA210210Que a empresa tomou conhecimento da nota. Libera o XML completo.
CONFIRMADA210200Que a operação aconteceu como descrita.
DESCONHECIDA210220Que a empresa não reconhece a operação.
NAO_REALIZADA210240Que a operação não se concretizou. Exige justification com 15+ caracteres.

⚠️ A manifestação é enviada à SEFAZ em nome da empresa e não tem desfazer. Automatize a CIENCIA à vontade; DESCONHECIDA e NAO_REALIZADA recusam a nota do fornecedor e, se o módulo Financeiro gerou uma conta a pagar para ela, a conta é cancelada (ou vira pendência, se já tiver baixa).

NFS-e não tem manifestação (422). Rejeição da SEFAZ volta como 422 manifestation_failed, com o motivo em message.

Sincronizar agora

A distribuição já roda sozinha a cada hora. Para quando não dá para esperar:

# Estado da distribuição: NSU, último cStat e quando o próximo sync é permitido curl https://api.conttrole.io/v1/received-documents/distribution \ -H "Authorization: Bearer ck_live_sua_chave" # Dispara (assíncrono — 202; as notas aparecem na listagem em instantes) curl -X POST https://api.conttrole.io/v1/received-documents/sync \ -H "Authorization: Bearer ck_live_sua_chave"

Não faça polling com o sync. Depois de uma consulta sem novidades (cStat 137) a SEFAZ exige 1 hora de espera, e quem insiste recebe o cStat 656 e fica bloqueado por mais uma hora — inclusive a distribuição automática. Por isso, dentro da janela, a API responde 429 sefaz_wait com o header Retry-After em vez de disparar. O nextSyncAllowedAt de /distribution diz quando a janela fecha. Sem certificado A1 a resposta é 422 certificate_missing.

13. Modo contador: a carteira do escritório

Escritórios de contabilidade operam várias empresas-cliente com uma chave só: a chave do escritório (ok_...), criada no Painel do Contador → Configurações. As rotas ficam em /v1/accounting e não aceitam a chave de uma empresa (401 office_key_required).

Duas condições valem em toda chamada:

  • o escritório está ativo — suspenso pela plataforma, tudo responde 403 accounting_mode_inactive;
  • nas rotas por empresa, a empresa tem vínculo ativo com o escritório — o vínculo que ela aceitou no app e pode encerrar quando quiser. Sem ele, 403 company_not_in_portfolio.

Conferir a conexão e listar a carteira

curl https://api.conttrole.io/v1/accounting/office \ -H "Authorization: Bearer ok_live_sua_chave" curl "https://api.conttrole.io/v1/accounting/companies?search=padaria" \ -H "Authorization: Bearer ok_live_sua_chave"

O id de cada empresa é o companyId das rotas abaixo. Cada empresa traz também officeIssuingAllowed — se ela permite que o escritório emita pela integração (ver Emitir notas pela carteira).

Fechamento fiscal do mês

O fechamento gera um ZIP por empresa com o XML e/ou a DANFE das notas de produção autorizadas e canceladas no mês (horário de Brasília). O mês é 1–12.

# Quem tem movimento em agosto/2026 curl "https://api.conttrole.io/v1/accounting/closings/eligible?year=2026&month=8" \ -H "Authorization: Bearer ok_live_sua_chave" # Pede o fechamento — sem companyIds, vale para a carteira inteira curl -X POST https://api.conttrole.io/v1/accounting/closings \ -H "Authorization: Bearer ok_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "year": 2026, "month": 8, "includeXml": true, "includePdf": true }'

A resposta é 202 com created (um closingId por empresa) e skipped (com o motivo: already_running, no_documents, dispatch_failed ou not_in_portfolio). O ZIP é montado em segundo plano; acompanhe e baixe:

curl "https://api.conttrole.io/v1/accounting/closings?status=COMPLETED&year=2026&month=8" \ -H "Authorization: Bearer ok_live_sua_chave" # Link assinado, válido por 1 hora (409 closing_not_ready enquanto não termina) curl https://api.conttrole.io/v1/accounting/closings/cls_xxx/download \ -H "Authorization: Bearer ok_live_sua_chave"

Notas recebidas de cada cliente

São as mesmas rotas do fluxo 12, com a empresa no caminho — listagem, detalhe, XML, DANFE, estado da distribuição, sync (com a mesma janela da SEFAZ) e manifestação:

curl "https://api.conttrole.io/v1/accounting/companies/cmp_xxx/received-documents?pending=true" \ -H "Authorization: Bearer ok_live_sua_chave" curl -X POST https://api.conttrole.io/v1/accounting/companies/cmp_xxx/received-documents/rd_xxx/manifestation \ -H "Authorization: Bearer ok_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "type": "CIENCIA" }'

A manifestação vai à SEFAZ em nome da empresa-cliente e não tem desfazer — exige o escopo received-documents:write na chave do escritório.

O mês da carteira inteira num ZIP

O download em lote do fluxo 12 também existe por empresa (/v1/accounting/companies/{companyId}/received-documents/export) e, para fechar o mês de todos os clientes de uma vez, para a carteira:

# Carteira inteira (até 200 empresas) — uma pasta por empresa curl "https://api.conttrole.io/v1/accounting/received-documents/export?year=2026&month=8" \ -H "Authorization: Bearer ok_live_sua_chave" -o carteira-2026-08.zip # Só algumas empresas curl "https://api.conttrole.io/v1/accounting/received-documents/export?year=2026&month=8&companyIds=cmp_a,cmp_b" \ -H "Authorization: Bearer ok_live_sua_chave" -o lote-2026-08.zip

Em forma de link, é POST /v1/accounting/received-documents/export/link, com companyIds como array no corpo (ou omitido, para a carteira inteira); o link deixa de valer se alguma daquelas empresas sair da carteira.

Cada empresa vira a pasta <CNPJ> - <razão social>/, com notas/ e eventos/ dentro. Empresa sem nota no mês não ganha pasta; o header X-Company-Count diz quantas vieram. Um companyIds com empresa fora da carteira é recusado inteiro (403 company_not_in_portfolio, nomeando-a) — um ZIP “sem” aquela empresa se leria como “ela não recebeu nota no mês”. Exige received-documents:read na chave do escritório.

Emitir notas pela carteira

As rotas de emissão existem também por empresa da carteira, em /v1/accounting/companies/{companyId}/fiscal-documents — criar, emitir, detalhar, cancelar, carta de correção, comprovante de entrega, XML e DANFE —, com os escopos documents:read/documents:write na chave do escritório. A nota é da empresa: plano, cota, certificado e as configurações fiscais são os dela, e a nota registra a chave do escritório como emissora. Os templates dela ficam em /v1/accounting/companies/{companyId}/fiscal-templates (só leitura).

A empresa precisa permitir. Estar na carteira dá acesso à LEITURA das notas; criar, emitir, cancelar, corrigir e registrar comprovante de entrega exigem que a empresa tenha ligado “Permitir que o escritório emita notas pela integração” (no app dela, Configurações → Contador). Vínculo novo nasce com a permissão desligada. Sem ela:

{ "statusCode": 403, "error": "Forbidden", "message": "A empresa não permitiu que o escritório emita ou cancele notas pela integração. …", "code": "office_issuing_not_allowed" }

Quando a empresa liga ou desliga, o webhook link.updated avisa na hora (com officeIssuingAllowed no data), e a carteira (GET /v1/accounting/companies) mostra o valor atual.

Antes de emitir, confira a prontidão — sem criar rascunho:

curl https://api.conttrole.io/v1/accounting/companies/cmp_xxx/nfse-readiness \ -H "Authorization: Bearer ok_live_sua_chave"
{ "ready": false, "issues": [ { "code": "certificate_expired", "message": "Certificado digital expirado. Faça upload de um novo certificado em Configurações > Certificado." } ], "certificate": { "present": true, "expiresAt": "2026-09-01T03:00:00.000Z" }, "templates": 2, "defaultTemplateId": "tpl_xxx", "officeIssuingAllowed": true }
codeO que falta
certificate_missing / certificate_expiredcertificado A1 da empresa
nfse_not_configuredconfiguração de NFS-e da empresa
municipality_unsupportedo webservice do município não aceita a emissão (sem integração, sistema municipal encerrado, CGSN 191)
no_nfse_templatenenhum template de NFS-e
quota_exhausteda cota de emissões do plano da empresa
office_issuing_not_alloweda empresa não permitiu a emissão pelo escritório

ready é true quando issues vem vazia. A prontidão olha a empresa; os dados da nota (serviço, tomador, valores) são conferidos na emissão. Escopo documents:read.

O cliente da nota pode vir do cadastro da empresa: com clients:read, GET /v1/accounting/companies/{companyId}/clients?search= devolve a mesma lista (mesma forma e paginação) de GET /v1/clients, e o id de cada cliente é o clientId da criação. Só leitura.

curl -X POST https://api.conttrole.io/v1/accounting/companies/cmp_xxx/fiscal-documents \ -H "Authorization: Bearer ok_live_sua_chave" \ -H "Idempotency-Key: pedido-8842" \ -H "Content-Type: application/json" \ -d '{ "type": "NFSE", "clientId": "cli_xxx", "externalId": "pedido-8842", "items": [ ... ], "emit": true }'

Idempotency-Key (1–100 caracteres entre letras, números e _ . : -, única por empresa) torna a criação segura para repetir — vale também em POST /v1/fiscal-documents:

SituaçãoResposta
chave nova201 com a nota criada
mesma chave, mesmo corpo200 com a nota já criada, header Idempotent-Replayed: true — nada é criado nem emitido de novo
mesma chave, outro corpo422 idempotency_key_mismatch
chave fora do formato400 invalid_idempotency_key

Para espelhar as notas no seu sistema, a lista tem o mesmo modo sincronização das notas recebidas: changedSince (ISO 8601) na primeira rodada, depois siga meta.nextCursor enquanto meta.hasMore for verdadeiro e guarde o último nextCursor — ele é o marco da próxima rodada. externalId filtra pelo id da venda no seu sistema.

curl "https://api.conttrole.io/v1/accounting/companies/cmp_xxx/fiscal-documents?changedSince=2026-09-01T00:00:00-03:00&pageSize=100" \ -H "Authorization: Bearer ok_live_sua_chave"

Webhooks do escritório

Um endpoint do escritório recebe os eventos de todas as empresas da carteira (vínculo ativo no momento do evento). Escopo webhooks:write; até 5 endpoints; URL em https.

curl -X POST https://api.conttrole.io/v1/accounting/webhooks \ -H "Authorization: Bearer ok_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "url": "https://seu-sistema.com/webhooks/conttrole", "events": ["document.authorized", "document.rejected", "link.updated"] }'

A resposta (201) traz o secret (whsec_…) uma única vez — gire-o com POST /v1/accounting/webhooks/{id}/rotate-secret. Cada entrega:

{ "id": "evt_…", "type": "document.authorized", "createdAt": "2026-09-24T12:00:00.000Z", "officeId": "off_…", "companyId": "cmp_…", "data": { "id": "doc_…", "type": "NFE", "status": "AUTHORIZED", "externalId": "pedido-8842", "…": "…" } }
EventoQuandodata
document.authorized / document.rejected / document.cancellednota de uma empresa da carteira mudou de desfechoo mesmo corpo do webhook da empresa, mais id e status (e rejectionReason na rejeição)
document.inutilizedfaixa de numeração inutilizadaa faixa (inutilizationId, series, startNumber, endNumber…)
received_document.created / received_document.updatednota recebida chegou, ganhou o XML completo ou foi manifestada{ id, model, schemaType, accessKey, manifestation, updatedAt }
link.updatedvínculo do escritório criado, com status novo ou com a permissão de emissão trocada pela empresa{ linkId, companyId, taxId, companyName, status, requestedBy, officeIssuingAllowed }

A assinatura é a mesma dos webhooks da empresa: X-Webhook-Signature: t=<unix>,v1=<hex>, com v1 = HMAC-SHA256 do secret sobre <t>.<corpo cru>. Deduplique pelo id do evento. Não há POST de verificação no cadastro: o primeiro evento já é real. Cinco entregas seguidas com falha desativam o endpoint (isActive: false) — remova e cadastre de novo.

Pedir vínculo a uma empresa

Com portfolio:write, o seu sistema faz o mesmo “Convidar empresa” do painel:

curl -X POST https://api.conttrole.io/v1/accounting/links \ -H "Authorization: Bearer ok_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "taxId": "11.222.333/0001-81" }'
RespostaSignificado
201 status: PENDINGpedido criado; os administradores da empresa recebem o convite por e-mail e aceitam no app
200 status: PENDINGjá havia pedido pendente (sem novo e-mail)
200 status: ACTIVEa empresa já está na carteira
404 company_not_foundnenhuma empresa com esse CNPJ/CPF usa o app
409 company_already_linkeda empresa tem vínculo ativo com outro escritório

Acompanhe por GET /v1/accounting/links?status=PENDING ou pelo webhook link.updated. Quando o vínculo vira ACTIVE, o companyId passa a valer nas rotas por empresa.

Last updated on