Documentação / NF-e v1
API REST fiscal

NF-e modelo 55: monte, valide e autorize com um contrato fiscal verificável.

Quickstart operacional da NF-e com empresa, certificado, payload, build opcional, autorização assíncrona, estados e artefatos. A referência campo a campo permanece no OpenAPI.

Atualizado em .

Onde esse fluxo começa na prática

O onboarding mais rápido hoje passa pelo console: gerar a chave de homologação, cadastrar a empresa no mesmo ambiente, subir o certificado A1, configurar o webhook e depois criar o primeiro rascunho fiscal.

Criar o rascunho da NF-e

O fluxo da plataforma parte de um rascunho. Isso permite validar, versionar e emitir sem prender o ERP em uma chamada longa para a SEFAZ.

POST https://fiscal.contora.com.br/api/v1/nfe/drafts (Global — recomendado)
Como a rota global escolhe a empresa: com uma única empresa vinculada à chave, a seleção é automática. Com duas ou mais, envie X-Company-Document: CNPJ_SEM_MASCARA. A rota hierárquica identifica a empresa pelo UUID da URL.
POST https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts (Hierárquico - Multi-empresas)
{
  "document_type": "nfe",
  "series": 1,
  "number": 214,
  "payload": {
    "nature_operation": "Venda de mercadoria para revenda",
    "operation_type": "saida",
    "consumer_final": true,
    "presence_indicator": 1,
    "recipient": {
      "name": "Cliente Fictício Atacadista Ltda",
      "document": "98765432000110",
      "state_registration_indicator": 1,
      "email": "[email protected]",
      "address": {
        "street": "Avenida Paraná",
        "number": "1000",
        "district": "Batel",
        "city_name": "Curitiba",
        "city_code": "4106902",
        "state_code": "PR",
        "postal_code": "80000000"
      }
    },
    "items": [
      {
        "code": "SKU-100",
        "name": "Produto de demonstração",
        "ncm": "85176259",
        "cfop": "5102",
        "unit": "UN",
        "quantity": 1,
        "unit_price": 1500.00,
        "discount": 50.00,
        "taxes": {
          "icms": {"code": "00", "origin": 0, "aliquot": 18},
          "pis": {"code": "01", "aliquot": 1.65},
          "cofins": {"code": "01", "aliquot": 7.6}
        }
      }
    ],
    "payments": [
      {
        "method": "01",
        "amount": 1500.00
      }
    ]
  }
}
Tributação do exemplo: o ICMS 00 abaixo é um CST para emitente em presumido, real ou simples_excesso (CRT 2). Para mei e simples (CRT 1), informe um CSOSN válido — por exemplo, 102 — em vez de CST. Empresas em simples_excesso emitem com CRT 2 no XML e utilizam CST normal (ex.: 00, 10, 20, 70, 90). Mantenha ainda o CFOP coerente com a UF do destinatário: use 5102 somente se emitente e destinatário estiverem na mesma UF; para outra UF, valide o CFOP interestadual aplicável antes do envio.
Identidade fiscal preservada: a API não altera série, número, modelo ou tipo de emissão. Para emissão normal, séries de 890 a 899 (NF-e avulsa) e de 900 a 999 (reservadas para contingência) são rejeitadas. Para emitir em contingência, envie o bloco contingency (com mode=6/7 para NF-e SVC ou mode=9 para NFC-e off-line). O campo model, quando enviado, deve ser 55 para NF-e ou 65 para NFC-e.
Responsável Técnico (infRespTec) automático: você não precisa informar o grupo infRespTec nem calcular o hashCSRT no payload da nota. A plataforma herda as credenciais cadastradas na sua conta (PUT /technical-responsible) e injeta o grupo e o hash automaticamente no XML em todas as emissões. Veja Responsável técnico e CSRT e o Guia para SaaS Multi-CNPJ.

Contrato de payload suportado

A referência OpenAPI descreve tipos, limites e obrigatoriedade. Hoje o motor NF-e suporta oficialmente os grupos abaixo.

nature_operation
Obrigatório, até 60 caracteres no XML.
operation_type
saida ou entrada.
id_dest
Opcional. Declara em ide/idDest a operação como 1 interna, 2 interestadual ou 3 com o exterior. Sem o campo, o valor é calculado pela UF do destinatário (destinatário estrangeiro força 3). Use o override quando a UF do comprador não é a do destino real da mercadoria — a SEFAZ confere idDest com o CFOP, e um CFOP interno com idDest 2 volta com "CFOP de operação interna e idDest <> 1". A coerência com o exterior não se sobrescreve: 3 exige recipient.foreign_document, e vice-versa.
recipient
Nome, CPF/CNPJ, indicador de IE, IE, e-mail e endereço. Nome, documento e endereço são obrigatórios na NF-e. E-mail, quando informado, é gravado em dest/email.
items[]
Produto, NCM, CEST opcional, CFOP, unidade, quantidade, preço, desconto, outras despesas e tributos.
items[].discount
Desconto do item em det/prod/vDesc; entra na base de cálculo do ICMS (base = unit_price × quantity − discount) e no vNF. Deve ser maior ou igual a zero.
items[].other_expenses
Legado: despesa acessória em det/prod/vOutro, fora de qualquer base de cálculo. Compõe ICMSTot/vOutro e o vNF.
items[].additional_charges[]
Despesas acessórias com efeito tributário declarado base a base. Veja despesas acessórias.
items[].freight / items[].insurance
Frete e seguro do item em det/prod/vFrete e vSeg. Integram a base de cálculo dos tributos e compõem o vNF. Veja frete e seguro.
items[].taxes
ICMS por CST/CSOSN, PIS, COFINS e IPI dentro do subconjunto descrito no OpenAPI. Veja abaixo os códigos com destaque calculado.
payments[]
Meio, indicador à vista/a prazo e valor; a soma precisa fechar com o total calculado. O meio 99 (Outros) exige description.
billing
Fatura e até 120 duplicatas, somente para NF-e modelo 55.
items[].purchase_order / purchase_order_item
Pedido e item do pedido em det/prod/xPed e nItemPed.
items[].taxes.icms.exemption
ICMS desonerado: valor, motivo e se abate o total (vICMSDeson, motDesICMS, indDeduzDeson). Veja ICMS desonerado.
items[].taxes.ipi.quantity / unit_amount
IPI de pauta: qUnid × vUnid, o modo de bebidas e cigarros. Exclusivo com aliquot — o leiaute traz um choice.
items[].taxes.pis.code = 03
PIS/COFINS por quantidade, com grupo próprio no leiaute (PISQtde/COFINSQtde). Exige quantity e unit_amount em vez de aliquot.
items[].taxes.icms.st.exemption
ST desonerado (vICMSSTDeson, motDesICMSST), nos CST 10, 70 e 90. Independente da desoneração do ICMS próprio.
exit_at / delivery_forecast
Saída ou entrada da mercadoria (dhSaiEnt) e previsão de entrega (dPrevEntrega). A previsão exige modalidade de frete real em shipping.modality — com a 9 a SEFAZ devolve a Rejeição 1157.
contingency
Emissão em contingência: mode (tpEmis), entered_at (dhCont) e reason (xJust, mínimo 15 caracteres). Os três andam juntos.
fiscal_info
infAdic/infAdFisco: texto de interesse do Fisco, separado do additional_info, que é do contribuinte.
items[].approximate_taxes
vTotTrib, o valor aproximado dos tributos da Lei 12.741. É por item; o total da nota é somatório. Informá-lo só no total devolve a Rejeição 685.
items[].barcode / taxable_barcode
cBarra e cBarraTrib: código de barras fora do padrão GTIN.
items[].relevant_scale / manufacturer_document
indEscala e CNPJFab. Vivem no grupo do CEST e exigem items[].cest; em escala não relevante o fabricante é obrigatório.
items[].tipi_exception / import_content_sheet / used_movable_asset / aduaneiro_codes
EXTIPI, nFCI, indBemMovelUsado e até 8 NVE.
recipient.suframa
dest/ISUF. Fora de AM, AC, AP, RO e RR a SEFAZ devolve a Rejeição 251 — a API avisa sem bloquear, porque a delimitação real é por município.
items[].taxes.issqn / services
Serviço sujeito ao ISSQN e os totais dele. Veja ISSQN.
items[].taxes.pis.code = 05
PIS/COFINS retidos por substituição, lado do substituído. Veja PIS e COFINS por substituição.
items[].taxes.icms.deferral / items[].taxes.ibscbs.deferral
Diferimento do ICMS (CST 51) e do IBS/CBS (gDif). Grupos distintos, com estruturas distintas. Veja diferimento.
items[].taxes.ibscbs
CST, cClassTrib e alíquotas do IBS/CBS por item; os valores são calculados pela API. Veja IBS e CBS.
purchase
Grupo de compra da nota: empenho, pedido e contrato.
purpose
Finalidade da nota (ide/finNFe): 1 normal, 2 complementar, 3 ajuste, 4 devolução. Veja complementar e ajuste.
recipient.foreign_document / export / items[].export_details[]
Destinatário no exterior, local de saída do país e detalhes de drawback ou exportação indireta. Veja exportação.
referenced_documents[]
Chaves de acesso de NF-e referenciadas. Obrigatório na complementar (purpose: 2, rejeição 254) e na devolução (purpose: 4, rejeição 321). Na devolução a chave é gravada por item em det/DFeReferenciado; nas demais finalidades, em ide/NFref/refNFe.
items[].returned_ipi
IPI devolvido do item em det/impostoDevol (pDevol + IPI/vIPIDevol); a soma compõe ICMSTot/vIPIDevol e o vNF. Somente NF-e modelo 55 com purpose: 4.
items[].referenced_document
Chave e item da nota de origem daquela linha (det/DFeReferenciado), quando a ordem dos itens não espelha a nota original.
shipping
Modalidade, valores de frete e seguro, transportadora, veículo e volumes (transp). Sem este campo, a nota sai com modalidade 9 (sem ocorrência de transporte), como antes.
items[].tracking[] / items[].medicine
Rastreabilidade por lote (rastro) e dados de medicamento (med). Veja rastreabilidade.
items[].agrochemical
Receituário agronômico do defensivo agrícola (infNFe/agropecuario/defensivo), NT 2024.003. Veja defensivos agrícolas.
delivery_address / pickup_address
Local de entrega (entrega) e local de retirada (retirada), quando diferentes dos endereços do destinatário e do emitente. Veja entrega e retirada.
marketplace
Identificação do intermediador da venda (indIntermed e infIntermed), para operações via marketplace ou plataforma de delivery.
additional_info
Informações complementares; quebras de linha são normalizadas com segurança e preservadas visualmente no DANFE.
Códigos de tributação com destaque calculado: a API sempre grava a tag do imposto com o code enviado, mas só calcula vBC/valor/alíquota para um subconjunto de códigos — os demais são gravados sem destaque (base e valor zerados), o que é o comportamento fiscal correto para a maioria dos casos de isenção/não incidência, mas pode surpreender se você esperava um cálculo automático:
  • ICMS (regime presumido, real ou simples_excesso, CST): calcula para 00, 10, 20, 70 e 90. Para emitentes em simples_excesso, a nota é gerada com CRT 2 e utiliza CST normal em vez de CSOSN. Demais CSTs (ex.: 40, 41, 60) ficam sem destaque, como esperado para isenção/substituição.
  • ICMS (MEI/Simples, CSOSN): calcula apenas para 900 quando aliquot > 0. Os demais CSOSN (102, 103, 300, 400, 500 etc.) não têm destaque, o que é o padrão do Simples Nacional. Para o crédito de ICMS do art. 23 da LC 123/2006 (CSOSN 101 ou 900), use taxes.icms.credit_rate — veja a seção dedicada abaixo.
  • PIS/COFINS: calcula para os códigos tributados e outras operações de entrada/saída (01, 02, 49, 50 a 67, 70 a 75, 98 e 99); para 03 calcula por quantidade (quantity e unit_amount); para 04 a 09 gera o grupo não tributado (PISNT/COFINSNT).
  • IPI: calcula para os CSTs 50 e 99; se taxes.ipi não for enviado, a tag IPI não é gerada.
Indicador de IE do destinatário (recipient.state_registration_indicator): 1 = contribuinte com inscrição estadual (envie também state_registration); 2 = contribuinte isento (grava IE = ISENTO); 9 = não contribuinte (padrão quando omitido). Um destinatário CNPJ configurado como 9 frequentemente é rejeitado pela SEFAZ (código 232) — confirme com o cliente se ele é ou não contribuinte do ICMS antes de emitir.

Pedido de compra e informações complementares

Use os campos do item quando o cliente precisar relacionar cada produto a uma linha da ordem de compra. Quando um único pedido representar a nota inteira, use também ou apenas o grupo purchase.

{
  "items": [
    {
      "code": "SKU-001",
      "name": "Produto de demonstração",
      "ncm": "85369090",
      "cfop": "5102",
      "unit": "UN",
      "quantity": 15,
      "unit_price": 270,
      "purchase_order": "OC-0001",
      "purchase_order_item": "000001",
      "taxes": { "icms": { "code": "102", "origin": 2, "aliquot": 0 } }
    }
  ],
  "purchase": {
    "commitment_note": "EMP-2026-1",
    "order": "OC-0001",
    "contract": "CONT-88"
  },
  "additional_info": "Pedido OC-0001\nObservações comerciais\nOptante pelo Simples Nacional."
}

Mapeamento XML

items[].purchase_order
det/prod/xPed — até 15 caracteres.
items[].purchase_order_item
det/prod/nItemPed — de 1 a 6 dígitos.
purchase.commitment_note
compra/xNEmp — até 22 caracteres.
purchase.order
compra/xPed — até 60 caracteres.
purchase.contract
compra/xCont — até 60 caracteres.
DANFE: as tags de pedido permanecem no XML e não são acrescentadas automaticamente à descrição dos produtos no DANFE padrão da Contora. Para organizar infCpl, envie quebras \n, \r\n ou TAB; a API grava separadores permitidos no XML e o renderizador os converte em linhas. O marcador | não é convertido.

Outras despesas acessórias (vOutro) e devolução ao fornecedor

Há dois campos, com semânticas diferentes e deliberadamente separadas. Os dois gravam em det/prod/vOutro, somam em total/ICMSTot/vOutro e entram no vNF. O que muda é o efeito na base de cálculo.

items[].other_expenses
Legado. Valor único por item que nunca entra em base de cálculo. Continua funcionando exatamente como sempre funcionou.
items[].additional_charges[]
Lista de despesas em que você declara, despesa a despesa, em quais bases ela entra.

Despesas com efeito tributário declarado

Uma despesa acessória cobrada do destinatário normalmente integra a base do ICMS. Mas nem toda linha de vOutro é isso — muita gente usa o campo para fechar o total de uma devolução com o da nota de origem, onde não há o que tributar. Como não existe uma resposta única, a API não adivinha: quem informa diz o que a despesa é.

amount
Valor da despesa. Obrigatório, maior ou igual a zero.
description
Descrição, gravada em det/infAdProd — o campo que aparece no DANFE. Várias despesas no mesmo item são unidas por |, até 500 caracteres.
in_icms_base
Entra na base do ICMS. Reflete automaticamente no FCP, no ICMS-ST e no DIFAL, que derivam dessa base.
in_pis_cofins_base
Entra na base de PIS e COFINS.
in_ipi_base
Entra na base do IPI.
{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "additional_charges": [
        {
          "amount": 100.00,
          "description": "Embalagem especial cobrada do destinatario",
          "in_icms_base": true,
          "in_pis_cofins_base": true
        }
      ]
    }
  ]
}

A base do ICMS passa a 1.100,00 e o vNF também. Sem as marcações, o mesmo valor apareceria em vOutro e no vNF, mas a base continuaria 1.000,00.

Omitir uma flag é dizer "não". Cada base recebe apenas o que estiver explicitamente marcado, e as três são independentes: dá para uma despesa entrar só na base do IPI. Não existe regra geral dizendo que todo vOutro entra ou nunca entra na base.
in_icms_base move mais do que o ICMS. O FCP, o ICMS-ST e o DIFAL são todos calculados a partir da base do ICMS, então uma despesa marcada assim altera junto o vBCFCP, a base do ST (inclusive quando ela vem de MVA) e o vBCUFDest. É por isso que a marcação é por despesa, e não uma chave única da nota.
other_expenses é legado e continua como está. Ele nunca entrou em base de cálculo, há integração em produção que depende disso, e não houve migração automática. Os dois campos convivem no mesmo item e somam juntos em vOutro. Em código novo, prefira additional_charges: a intenção fica explícita no payload.

Frete e seguro têm campos próprios e regra própria — sempre integram a base, porque é o que a LC 87/96 determina. Veja frete e seguro.

IPI da nota de compra na devolução: use returned_ipi, não other_expenses. O vOutro faz o total da devolução fechar com o da compra, mas não gera crédito de IPI ao destinatário — para a SEFAZ são coisas diferentes. Desde 01/08/2017 (NT 2016.002) o caminho correto é o grupo impostoDevol, descrito na seção IPI devolvido na devolução. Reserve other_expenses para despesas acessórias de verdade (frete cobrado à parte, embalagem, seguro por conta do destinatário).
Nota original referenciada (obrigatório na devolução): a devolução precisa referenciar a chave de acesso da NF-e original em payload.referenced_documents. Sem isso, a SEFAZ rejeita com cStat 321 — "NF-e de devolução de mercadoria não possui documento fiscal referenciado". Na devolução a API grava essa chave por item, em det/DFeReferenciado — ver IPI devolvido na devolução.
{
  "purpose": 4,
  "nature_operation": "Devolução de compra",
  "operation_type": "saida",
  "referenced_documents": [
    { "access_key": "32250612345678000199550010000001231000001234" }
  ],
  "items": [
    {
      "code": "SKU-001",
      "name": "Produto de demonstração",
      "ncm": "85369090",
      "cfop": "5202",
      "unit": "UN",
      "quantity": 10,
      "unit_price": 100,
      "other_expenses": 50.00,
      "taxes": { "icms": { "code": "102", "origin": 2, "aliquot": 0 } }
    }
  ]
}

No exemplo, vProd = 1000,00 e vOutro = 50,00, resultando em vNF = 1050,00. Quando houver vários itens, informe other_expenses em cada linha; havendo pagamentos, a soma de payments precisa fechar com o vNF já acrescido do vOutro.

Mapeamento XML

referenced_documents[].access_key
Chave de acesso de 44 dígitos da nota de origem. Pontuação é normalizada e a chave é validada antes do envio. Em purpose: 4 vai para det/DFeReferenciado (por item); nas demais finalidades, para ide/NFref/refNFe.
items[].other_expenses
det/prod/vOutro — valor por item, duas casas decimais. Legado, fora de qualquer base.
items[].additional_charges[].amount
Soma no mesmo det/prod/vOutro, e nas bases marcadas.
items[].additional_charges[].description
det/infAdProd — descrições do item unidas por |.
soma dos dois campos
total/ICMSTot/vOutro — calculado pela API.

IBS e CBS (reforma tributária)

Escopo desta versão: regime regular com alíquota padrão da transição, mais as famílias de redução de alíquota, tributação regular, imunidade / não incidência e diferimento. Na tabela oficial instalada isso libera 79 cClassTrib em NF-e e 37 em NFC-e; dezenove foram autorizados nos cenários testados na homologação da SEFAZ-SP. Não é suporte completo ao RTC — a lista do que ainda não existe está no fim desta seção.

Grupo IBSCBS por item. Hoje é opcional: nota sem ele continua sendo autorizada. E é paralelo — não move ICMS, DIFAL, FCP, ICMS-ST, PIS, COFINS, IPI nem o vNF.

Referências: NT 2025.002-RTC v1.50, publicada em 03/06/2026, e IT 2025.002 v1.60 com tabelas CST/cClassTrib publicadas em 23/06/2026. A NT substitui a RT NT 2024.002 - IBS/CBS v1.10.

Guia prático e resolução de rejeições (1026, 1036 e 1037): Para entender a parametrização das alíquotas nominais de transição (0,1% / 0% / 0,9%) e a tabela oficial de correlação CST x cClassTrib, consulte o Guia Prático da Reforma Tributária (IBS e CBS) na API.
cst
CST do IBS/CBS, validado na tabela oficial v1.60. Aceitos 000 (tributação integral), 200 (alíquota reduzida), 410 (imunidade/não incidência), 510 e 515 (diferimento) e 550 (suspensão, inclusive exportação). Veja as seções abaixo para o payload de cada um.
classification_code
cClassTrib, validado junto ao CST, à vigência, ao tipo de alíquota e ao modelo do documento. Não há lista fixa no código: a tabela oficial decide, e o mesmo código pode valer em NF-e e não em NFC-e.
state_rate
Alíquota nominal do IBS estadual (pIBSUF). Na redução, a efetiva é calculada pela API.
municipal_rate
Alíquota nominal do IBS municipal (pIBSMun).
cbs_rate
Alíquota nominal da CBS (pCBS).
base_amount
Base opcional. Omitida, usa a base do item: produto − desconto + frete + seguro.
{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "taxes": {
        "ibscbs": {
          "cst": "000",
          "classification_code": "000001",
          "state_rate": 0.1,
          "municipal_rate": 0,
          "cbs_rate": 0.9
        }
      }
    }
  ]
}

Sobre 1.000,00: vIBSUF 1,00 e vCBS 9,00. O vIBS do item e todo o totalizador IBSCBSTot são somados automaticamente — não há campo de total para informar.

A SEFAZ valida alíquota por tabela, não por faixa. Qualquer outro valor — inclusive zero, que o XSD aceita — é recusado, e cada componente tem código próprio: 1026 (IBS da UF, regra UB18-10), 1036 (IBS do Município, UB37-10) e 1037 (CBS, UB56-10). O build avisa antes do envio.
Ano de emissão
IBS UF / IBS Município / CBS
2025 e 2026
0,1% / 0% / 0,9% — LC 214/2025, arts. 343 e 346
2027 e 2028
0,05% / 0,05% / alíquota a publicar — LC 214/2025, art. 344
a partir de 2029
alíquota de referência ou específica, a publicar

A API consulta a tabela instalada pela data de emissão. Quando o ano não tem as três alíquotas publicadas — como 2027 — gera aviso, não recusa: uma data sem cobertura integral não vira bloqueio rígido.

Redução de alíquota

Quando o CST informado tem o indicador de redução na tabela oficial — hoje o 200 —, a API monta o grupo gRed sozinha. O percentual não vai no payload: ele é do cClassTrib, e a SEFAZ o confere contra a tabela (regra UB27-10, Rejeição 1034). Informá-lo só criaria uma forma a mais de errar.

{
  "taxes": {
    "ibscbs": {
      "cst": "200",
      "classification_code": "200030",
      "state_rate": 0.1,
      "municipal_rate": 0,
      "cbs_rate": 0.9
    }
  }
}

O 200030 — dispositivos médicos do Anexo IV, LC 214/2025 art. 131 — reduz 60%. As alíquotas informadas continuam sendo as cheias; a API calcula gRed/pAliqEfet = 0,04 e 0,36 (regra UB28-10) e aplica sobre a base: vIBSUF 0,40 e vCBS 3,60 sobre 1.000,00. O IBSCBSTot soma o valor reduzido, e uma nota pode misturar itens reduzidos e integrais. Redução de 100% zera o imposto mas mantém o grupo: some o valor, não a declaração.

Se o cClassTrib exigir um grupo que ainda não existe, a resposta é 422 nomeando o que falta — “exige o grupo de diferimento”, “exige o grupo de crédito presumido da operação” — em vez de uma recusa genérica.

Tributação regular (operação suspensa ou condicionada)

Alguns regimes suspendem o imposto sob condição — depósito, permanência temporária, industrialização para exportação, Zona Franca. Nesses casos o cClassTrib traz o indicador de tributação regular, e a nota precisa declarar como seria tributada se a condição não fosse cumprida. Vai em taxes.ibscbs.regular_taxation.

{
  "taxes": {
    "ibscbs": {
      "cst": "550",
      "classification_code": "550003",
      "state_rate": 0.1,
      "municipal_rate": 0,
      "cbs_rate": 0.9,
      "regular_taxation": {
        "cst": "000",
        "classification_code": "000001"
      }
    }
  }
}
Aqui a alíquota real é zero — e isso é a regra, não um erro. Quando o cClassTrib exige tributação regular, a NT manda zerar pIBSUF, pIBSMun e pCBS (exceção 1 das regras UB18-10, UB37-10 e UB56-10). A API força esse zero mesmo que você informe as alíquotas do ano, e o imposto que seria devido vai para gTribRegular. Sobre 1.000,00: vIBSUF e vCBS zerados no grupo real, vTribRegIBSUF 1,00 e vTribRegCBS 9,00 na hipótese. O IBSCBSTot acompanha o imposto real, não o hipotético.

Informe apenas o par hipotético; alíquotas e valores são calculados. Se esse par tiver redução — por exemplo 200/200030, que reduz 60% —, a hipótese herda a redução. O par é conferido contra a mesma tabela (rejeições 1066 e 1067), e uma classificação que ela própria exija tributação regular é recusada: a declaração não fecharia.

Informar o grupo onde ele não é permitido devolve 422 citando a Rejeição 1114; omiti-lo onde é exigido, a 1065.

Imunidade e não incidência

No CST 410 — imunidade e não incidência — não existe grupo de imposto. Não é alíquota zero: a tabela oficial marca esse CST como incompatível com o grupo (ind_gIBSCBS = 0), e o item sai só com o código e a classificação.

{
  "taxes": {
    "ibscbs": {
      "cst": "410",
      "classification_code": "410008"
    }
  }
}
Aqui informar zero é pior do que não informar. Mandar base_amount ou qualquer alíquota nesse CST devolve 422 no build. Se chegasse à SEFAZ, seria a Rejeição 1021 — "Grupo IBS/CBS informado indevidamente" (regra UB13-20), confirmada em sonda. O item também não entra no IBSCBSTot: numa nota mista, o totalizador conta apenas os itens tributados.

O 410008 do exemplo é a imunidade de livros, jornais e periódicos. A mesma família cobre doações, transferências entre estabelecimentos do mesmo titular, operações acobertadas somente pelo ICMS, exportações e outras situações sem incidência — 19 classificações em NF-e e 13 em NFC-e.

O CST 400, de isenção, existe na tabela mas nenhuma das suas classificações está habilitada para NF-e ou NFC-e. Na prática, quem cobre as situações sem incidência nestes modelos é o 410.

Entrega em etapas. Estão implementados o item no regime regular, os totalizadores, a redução de alíquota, a tributação regular, a imunidade e o diferimento. O vínculo CST/cClassTrib não é uma lista fixa: vem do IT v1.60. Continuam não implementados — devolução de tributo, compra governamental, monofásicos, crédito presumido, transferência de crédito, ajuste de competência, estorno de crédito e Imposto Seletivo.
A base de cálculo ainda não é a definitiva. A regra oficial (UB16-10) manda somar produto, serviço, frete, seguro, vOutro e II, e subtrair ICMS, DIFAL, FCP, PIS, COFINS e ISSQN — o IBS e a CBS são tributos "por fora". Hoje a API usa produto − desconto + frete + seguro, e a SEFAZ aceita porque a própria NT marca a UB16-10 como implementação futura. Quando ela entrar em vigor, a base muda. Note que vOutro entra inteiro na base do IBS/CBS, independentemente das marcações de additional_charges, que são do ICMS e dos tributos federais antigos. A fórmula futura já está coberta por teste e preparada atrás de chave operacional desligada.

Rastreabilidade por lote e medicamentos

São dois grupos separados, e um não implica o outro. O rastro vale para qualquer produto sob controle sanitário — medicamento, alimento, bebida, água envasada, embalagem, produto veterinário. O med é só de medicamento e matéria-prima farmacêutica.

Lotes — items[].tracking[]

O grupo se repete por lote, até 500 por item, e não move nenhum valor da nota: é informação de rastreio, não de cálculo.

lot
Número do lote (nLote), 1 a 20 caracteres. Obrigatório.
quantity
Quantidade neste lote (qLote), até 3 casas decimais. Obrigatório, maior que zero.
manufactured_at
Data de fabricação (dFab), AAAA-MM-DD. Obrigatória.
expires_at
Data de validade (dVal), AAAA-MM-DD. Obrigatória. Se a validade não especifica o dia, informe o último dia do mês.
aggregation_code
Código de agregação (cAgreg). Opcional.
{
  "items": [
    {
      "quantity": 10,
      "unit": "CX",
      "tracking": [
        { "lot": "L2026-001", "quantity": 6, "manufactured_at": "2026-01-15",
          "expires_at": "2028-01-14", "aggregation_code": "AGREG-001" },
        { "lot": "L2026-002", "quantity": 4, "manufactured_at": "2026-02-01",
          "expires_at": "2028-02-29" }
      ]
    }
  ]
}
O que é recusado no build. Lote sem qualquer campo obrigatório, validade anterior à fabricação, data fora do formato AAAA-MM-DD, data que não existe no calendário (2027-02-30 passa no XSD e a SEFAZ recusa), quantidade zero ou negativa, mais de 500 lotes, e campo desconhecido dentro do lote.
Soma dos lotes diferente da quantidade do item: aviso, não erro. O esperado é que fechem, e o build avisa quando não fecham. Mas não recusa: a unidade comercial pode ser outra — item em caixas, lote em unidades — e aí divergir é legítimo. Confirmado na SEFAZ-SP: uma nota com 3 unidades em lote e 10 de quantidade comercial foi autorizada.

Medicamentos — items[].medicine

anvisa_code
Registro na ANVISA com 11 ou 13 dígitos (cProdANVISA), ou o literal ISENTO. Obrigatório.
exemption_reason
Motivo da isenção (xMotivoIsencao), como o número da RDC. Obrigatório com ISENTO e recusado quando há registro real.
max_consumer_price
Preço máximo ao consumidor (vPMC). Obrigatório.
Medicamento exige lote. O XSD aceita medicine sozinho, mas a SEFAZ devolve a Rejeição 873 ("operação com medicamentos e não informado o grupo de rastreabilidade"). Levantamos o gatilho nos dois sentidos: é o grupo medicine que exige o tracking, não o NCM — um item com NCM de medicamento e sem o grupo autoriza normalmente. O build avisa antes do envio.

Defensivos agrícolas — items[].agrochemical

Defensivo agrícola (agrotóxico) com NCM da lista da NT 2024.003 exige receituário agronômico. Em nota normal (purpose: 1) para consumidor final, a SEFAZ devolve a Rejeição 835 quando falta o grupo infNFe/agropecuario/defensivo. Você declara por item; a API junta tudo em um grupo só por nota — até 20 defensivos, qualquer que seja o número de itens — e o posiciona logo após infRespTec, como manda o XSD.

prescription_number
Número do receituário agronômico (nReceituario), 1 a 30 caracteres do conjunto do XSD: letras, dígitos e @ # , - _ . ; : $ % /. Obrigatório.
technical_responsible_cpf
CPF de 11 dígitos do responsável técnico que emitiu o receituário (CPFRespTec). Precisa ter os dígitos verificadores válidos — inválido dá Rejeição 308. Obrigatório.
{
  "items": [
    {
      "ncm": "38085200",
      "...": "...",
      "agrochemical": {
        "prescription_number": "RC-2026/001",
        "technical_responsible_cpf": "52998224725"
      }
    }
  ]
}
Rejeição nos dois sentidos. Item de NCM defensivo sem o grupo → Rejeição 835; grupo sem nenhum item de NCM defensivo → Rejeição 309. Nos dois casos o build gera aviso em build_warnings antes do envio. Não precisam do grupo os produtos biológicos e saneantes das Exceções 4 e 5 da NT 2024.003, nem CFOPs de retorno, de transferência (X.151/152/155/156) e de entrega futura (5922/6922). Passando de 20 defensivos na mesma nota, o build recusa com mensagem própria — o limite é da nota, não do item.

Local de entrega e local de retirada

O endereço do destinatário fica em recipient.address e continua sendo o dest/enderDest. Quando a mercadoria é entregue em outro endereço, ou sai de um endereço diferente do emitente, o leiaute tem dois grupos próprios: entrega e retirada.

delivery_address
Grupo entrega: onde a mercadoria será entregue. Típico de e-commerce e de venda à ordem.
pickup_address
Grupo retirada: de onde a mercadoria sai, quando não é o endereço do emitente.

Os dois são opcionais, independentes e têm exatamente os mesmos campos.

document
CNPJ (14 dígitos) ou CPF (11). Obrigatório. A API escolhe a tag certa pelo tamanho.
street, number, district, city_code, city_name, state_code
Obrigatórios. xLgr, nro, xBairro, cMun (código IBGE), xMun e UF.
name
Razão social ou nome do local (xNome). Opcional.
complement, postal_code, phone, email, state_registration
Opcionais: xCpl, CEP, fone, email e IE. Ausentes, não viram tag vazia.
{
  "recipient": { "...": "endereco do cliente, continua no dest/enderDest" },
  "delivery_address": {
    "name": "Deposito Central LTDA",
    "document": "11222333000181",
    "street": "Avenida do Deposito",
    "number": "1500",
    "complement": "Galpao 3",
    "district": "Distrito Industrial",
    "city_code": "3550308",
    "city_name": "Sao Paulo",
    "state_code": "SP",
    "postal_code": "01001000"
  }
}
Endereço completo ou nada. Os grupos não admitem endereço parcial no leiaute: faltando qualquer obrigatório, o build recusa com o campo pelo nome, em vez de gerar um XML que a SEFAZ rejeita. Campo desconhecido dentro do grupo também é recusado — nunca aceito e ignorado.
Não confunda com a entrega de webhook. Em webhooks "entrega" é a tentativa de envio de um evento. Aqui é o local físico da mercadoria.

Frete e seguro

O leiaute da NF-e não tem campo de frete da nota: existem det/prod/vFrete e det/prod/vSeg por item, e o total ICMSTot/vFrete é o somatório deles. Por isso há dois caminhos, e você escolhe um.

Valor por item

items[].freight
Frete cobrado do destinatário naquele item (vFrete).
items[].insurance
Seguro cobrado do destinatário naquele item (vSeg).

Valor único da nota, rateado pela API

Quem recebe um frete só da transportadora informa em shipping e a API rateia entre os itens, proporcionalmente ao valor de cada um.

shipping.freight_amount
Frete total da nota, rateado entre os itens.
shipping.insurance_amount
Seguro total da nota, mesmo critério.
shipping.modality
modFrete: 0 por conta do emitente, 1 do destinatário, 2 de terceiros, 3 do remetente, 4 do destinatário, 9 sem ocorrência de transporte.
{
  "shipping": {
    "modality": 0,
    "freight_amount": 150.00,
    "insurance_amount": 30.00
  },
  "items": [
    { "code": "SKU-1", "unit_price": 1000.00, "quantity": 1, "...": "..." },
    { "code": "SKU-2", "unit_price": 500.00, "quantity": 1, "...": "..." }
  ]
}

Na proporção 1.000/500, o frete vira 100,00 e 50,00; o seguro, 20,00 e 10,00.

O resíduo do arredondamento não se perde. Dividir 100,00 por três itens iguais dá 33,333… por item: arredondado ingenuamente, a soma fecha 99,99 e a SEFAZ devolve total divergente do somatório. A API soma o centavo que sobra ao item de maior valor, então ICMSTot/vFrete bate exatamente com o valor que você informou.

Efeito na base de cálculo

Frete e seguro cobrados do destinatário integram a base de cálculo do ICMS, PIS, COFINS e IPI (LC 87/96, art. 13, §1º, II). Num item de 1.000,00 com 100,00 de frete e 50,00 de seguro, a base passa a 1.150,00 — e não 1.000,00. Os dois valores também entram na composição do vNF.

É a diferença em relação ao other_expenses, que compõe o vNF mas fica fora da base. Se a despesa deve entrar na base, ela é frete ou seguro, não "outras despesas".

Informe um caminho, não os dois. items[].freight junto com shipping.freight_amount é recusado no build: a API teria de escolher qual vale, e a escolha é sua. O mesmo para o seguro.
Duas armadilhas de cadastro. Frete ou seguro com modality 9 ("sem ocorrência de transporte") é contradição que o XSD aceita — o build avisa. E a IE da transportadora é validada contra o cadastro da UF: um número apenas bem formado devolve a Rejeição 544 ("IE do Transportador inválida"). Se não tiver a IE real, omita o campo.

Substituição tributária (ICMS-ST)

São duas situações diferentes, com campos diferentes: o ST que você recolhe na operação (CST 10, 30, 70 e 90; CSOSN 201, 202, 203 e 900) e o ST que o fornecedor já reteve antes (CST 60; CSOSN 500).

ST a recolher — items[].taxes.icms.st

base_mode
Modalidade da base (modBCST): 0 preço tabelado, 1 lista negativa, 2 positiva, 3 neutra, 4 margem de valor agregado, 5 pauta, 6 valor da operação. Obrigatório.
aliquot
Alíquota do ST (pICMSST). Obrigatório.
mva
Margem de valor agregado (pMVAST). Obrigatória quando base_mode = 4.
base
Base do ST (vBCST). Sem ela, é derivada do mva sobre a base do ICMS.
base_reduction
Redução da base do ST (pRedBCST). Opcional.
fcp_aliquot, fcp_base
FCP sobre o ST (pFCPST, vBCFCPST). Opcionais.
{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "taxes": {
        "icms": {
          "code": "10",
          "origin": 0,
          "aliquot": 18,
          "st": {
            "base_mode": 4,
            "mva": 40,
            "aliquot": 18,
            "fcp_aliquot": 2
          }
        }
      }
    }
  ]
}

O MVA de 40% sobre 1.000,00 dá base de ST de 1.400,00. O ST a recolher é 18% disso (252,00) menos o ICMS próprio de 180,00, ou seja 72,00. O FCP-ST sai como 28,00. Ambos entram na composição do vNF, junto com os totalizadores ICMSTot/vBCST, vST e vFCPST.

ST retido anteriormente — items[].taxes.icms.st_withheld

Só no CST 60 (regime normal) e no CSOSN 500 (Simples). Não entra no vNF: o imposto já foi recolhido pelo fornecedor. Numa nota com itens de famílias diferentes, só os itens de ST a recolher somam ao total.

base, aliquot, value
vBCSTRet, pST e vICMSSTRet. Os três são obrigatórios juntos.
substitute_value
ICMS próprio do substituto (vICMSSubstituto). Opcional no leiaute, mas exigido pela SEFAZ quando o destinatário é contribuinte — sem ele a resposta é a Rejeição 938. O build avisa.
fcp_aliquot, fcp_base
FCP retido por ST. Opcionais, mas viram um trio no XML.
effective_base, effective_aliquot, effective_base_reduction
Valores efetivos. Informar um exige os demais.

ST no Simples Nacional — CSOSN 201, 202, 203 e 500

Os campos são os mesmos, no mesmo st e st_withheld. O que muda é que nenhum CSOSN de ST tem ICMS próprio: não há o que descontar, e o ST vai integral. O grupo st é obrigatório nos CSOSN 201, 202 e 203 — é a razão de existirem — e o st_withheld só cabe no 500.

{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "taxes": {
        "icms": {
          "code": "201",
          "origin": 0,
          "credit_rate": 2.5,
          "st": {
            "base_mode": 4,
            "mva": 40,
            "aliquot": 18,
            "fcp_aliquot": 2
          }
        }
      }
    }
  ]
}

Base de ST de 1.400,00, ST de 252,00 (integral, sem desconto de ICMS próprio) e FCP-ST de 28,00, fechando vNF em 1.280,00. O credit_rate gera o crédito do art. 23 da LC 123 e vale nos CSOSN 101, 201 e 900. No 201 o par pCredSN/vCredICMSSN é obrigatório pelo leiaute: sem credit_rate ele sai zerado.

O único CSOSN com destaque próprio é o 900: nele o aliquot gera o ICMS do item e esse valor é descontado do ST, como no regime normal. Nenhum CSOSN tem FCP próprio — informar taxes.icms.fcp_aliquot com emitente do Simples é recusado, com a indicação de usar st.fcp_aliquot ou st_withheld.fcp_aliquot.

Dois limites que só a SEFAZ cobra. Os CSOSN 101, 201, 202, 203 e 900 pressupõem revenda e exigem destinatário contribuinte (recipient.state_registration_indicator = 1); com não contribuinte a resposta é a Rejeição 600. E o emitente MEI tem lista própria: só 102, 300 e 400 são aceitos, os demais devolvem a Rejeição 782. O XSD aceita os dois casos, então o build avisa em vez de deixar você descobrir no envio.
Nada é aceito pela metade. Cada bloco do leiaute é tudo-ou-nada, e o build recusa um bloco incompleto em vez de gravar XML inválido. Da mesma forma, st num código que não comporta ST, ou st_withheld fora do CST 60 e do CSOSN 500, é recusado — nunca ignorado em silêncio.
Ainda fora: o CST 30 monta, mas por ser CST de benefício exige tax_benefit_code válido da sua UF.

Fundo de Combate à Pobreza (FCP)

Vários estados cobram um adicional de ICMS destinado ao FCP. Informe a alíquota em items[].taxes.icms.fcp_aliquot e a API calcula o valor e soma o totalizador ICMSTot/vFCP.

fcp_aliquot
Alíquota do FCP em percentual. Sem ela o grupo não é gerado.
fcp_base
Base do FCP. Opcional; assume a base do ICMS (já reduzida, quando houver redução).
base_reduction
Percentual de redução da base (pRedBC). Obrigatório no CST 20, opcional no 90.
{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "tax_benefit_code": "SP800001",
      "taxes": {
        "icms": {
          "code": "20",
          "origin": 0,
          "aliquot": 18,
          "base_reduction": 30,
          "fcp_aliquot": 2
        }
      }
    }
  ]
}

Com redução de 30% sobre 1.000,00, a base passa a 700,00, e é sobre ela que incidem tanto o ICMS (126,00) quanto o FCP (14,00). A base do FCP acompanha a base reduzida do ICMS, não o valor bruto do item.

CST
FCP próprio
00
Sim. O leiaute não tem vBCFCP aqui: a base é a própria vBC, então fcp_base não é aceito.
20
Sim, com base_reduction obrigatório.
90
Sim, com base_reduction opcional.
10, 70
Sim, junto com o grupo de substituição tributária.
CSOSN (Simples/MEI)
Não. Nenhum CSOSN tem FCP próprio no leiaute — só FCP-ST.
Código de benefício fiscal. O CST 20 é benefício fiscal e a maioria das UFs exige o código correspondente da tabela estadual em items[].tax_benefit_code (prod/cBenef). Sem ele a nota passa no schema e é recusada pela SEFAZ com a Rejeição 930 — por isso a API avisa em build_warnings quando o campo falta. O código precisa existir na tabela da sua UF: um valor inventado gera a Rejeição 946.
O FCP da substituição tributária mora dentro do grupo de ST. Vai em taxes.icms.st.fcp_aliquot (ST a recolher) ou taxes.icms.st_withheld.fcp_aliquot (ST retido). Os nomes achatados fcp_st_aliquot e afins são recusados no build em vez de aceitos em silêncio, para você não achar que informou algo que não foi para o XML.

Exportação

Três coisas caracterizam uma nota de exportação, e a SEFAZ confere as três juntas: o destinatário no exterior, o CFOP da série 7000 em todos os itens e o grupo export, que diz por onde a mercadoria deixa o país. Faltar qualquer uma devolve 422 antes do envio.

recipient.foreign_document
idEstrangeiro. Exclusivo com document — o destinatário está no Brasil ou fora dele.
recipient.address.country_code
cPais, na tabela do BACEN. Obrigatório no exterior.
recipient.address.country_name
xPais. Obrigatório no exterior.
export.exit_state
UFSaidaPais: a UF de embarque ou de transposição de fronteira.
export.exit_location
xLocExporta: o porto, aeroporto ou ponto de fronteira.
export.dispatch_location
xLocDespacho, opcional, quando o despacho é em outro local.
{
  "recipient": {
    "name": "IMPORTADORA INTERNACIONAL LLC",
    "foreign_document": "US987654321",
    "address": {
      "street": "100 Biscayne Boulevard",
      "number": "2500",
      "district": "Downtown",
      "country_code": 2496,
      "country_name": "Estados Unidos"
    }
  },
  "export": {
    "exit_state": "SP",
    "exit_location": "Porto de Santos"
  },
  "items": [
    { "cfop": "7102", "quantity": 10, "unit_price": 100 }
  ]
}
O endereço no exterior tem valores fixos. Não informe cidade, UF ou CEP: o leiaute manda gravar cMun 9999999, xMun "EXTERIOR" e UF "EX", e a API grava. Quem identifica o destino é o país. O destinatário também sai sempre como não contribuinte (indIEDest 9), e a nota vira idDest 3.

Drawback e exportação indireta

O grupo items[].export_details[] cobre dois casos que o leiaute reuniu: o ato concessório de drawback e a exportação indireta, em que a mercadoria sai por uma comercial exportadora e o item aponta a NF-e recebida.

{
  "referenced_documents": [
    { "access_key": "35260767882130000176550990010002271021181451" }
  ],
  "items": [
    {
      "cfop": "7102",
      "quantity": 10,
      "export_details": [
        { "drawback_number": "20260000001" },
        {
          "indirect": {
            "export_registry": "123456789012",
            "access_key": "35260767882130000176550990010002271021181451",
            "quantity": 10
          }
        }
      ]
    }
  ]
}
Duas regras que só apareceriam no envio. A chave citada na exportação indireta precisa constar também em referenced_documents — a SEFAZ cruza as duas listas e devolve a Rejeição 345 quando a chave só aparece no item. E a soma das quantidades exportadas do item tem de fechar com a quantidade comercial, sob pena de Rejeição 346. A API recusa os dois casos no build, com a mensagem dizendo qual é.

No IBS/CBS, a exportação tem classificações próprias: 550001 (suspensão para exportação de bens materiais) e 410004 (imunidade nas exportações de bens e serviços). Ambas só fazem sentido numa nota que realmente sai do país, e as duas foram autorizadas nesse formato nos cenários testados na homologação da SEFAZ-SP. Veja IBS e CBS.

Nota complementar e nota de ajuste

A finalidade da nota vai em purpose (ide/finNFe). As quatro existem e todas emitem; o que muda entre elas é a exigência de referenciar a nota de origem.

purpose
Referência à nota de origem
1 normal
Opcional. Aceita referenced_documents sem virar outra finalidade.
2 complementar
Obrigatória. Sem ela a SEFAZ devolve a Rejeição 254 — e a API recusa antes do envio.
3 ajuste
Opcional. Medido na homologação da SEFAZ-SP: a mesma nota autoriza com e sem referência.
4 devolução
Obrigatória (rejeição 321). Veja IPI devolvido na devolução.
{
  "purpose": 2,
  "nature_operation": "Complemento de imposto",
  "referenced_documents": [
    { "access_key": "35260767882130000176550990010002271021181451" }
  ],
  "items": [
    {
      "unit_price": 0,
      "quantity": 1,
      "taxes": {
        "icms": { "code": "00", "origin": 0, "aliquot": 18 }
      }
    }
  ]
}

O que dá para complementar

Complemento de
Como fica o item
imposto
unit_price: 0 com o destaque do tributo sobre a base informada. O vNF fecha em 0,00.
valor
unit_price com a diferença que faltou. O vNF é essa diferença.
quantidade
quantity com a diferença e unit_price: 0.
Quantidade zero é aceita — e só aqui. Na complementar e no ajuste o item pode ir com quantity: 0: há complemento que não tem quantidade nova a declarar, o leiaute aceita e a SEFAZ-SP autorizou nas duas finalidades. Nas demais finalidades a API continua recusando, porque numa venda quantidade zero é quase sempre defeito da integração.

referenced_documents não indica a finalidade: uma nota normal com referência também é autorizada. Se você quer complementar, é o purpose que diz isso, não a presença da chave.

Os cenários acima — complemento de imposto, de valor e de quantidade, com e sem quantidade zero, com pagamento declarado e com item misto — foram autorizados nos cenários testados na homologação da SEFAZ-SP. Nenhum foi testado em produção nem em outra UF.

ISSQN — serviço na NF-e

A NF-e pode levar serviço sujeito ao imposto municipal, sozinho ou junto com mercadoria. Duas regras governam isso, e as duas foram medidas contra a SEFAZ.

ICMS e ISSQN são exclusivos no mesmo item. Aqui a regra é do leiaute, não de biblioteca: o grupo imposto traz um choice entre os dois. Numa nota mista, mercadoria e serviço vão em itens separados — nunca no mesmo.
{
  "services": { "competence_date": "2026-07-01" },
  "items": [
    {
      "code": "SRV-1",
      "name": "Suporte tecnico",
      "ncm": "00",
      "cfop": "5933",
      "unit": "UN",
      "quantity": 1,
      "unit_price": 1000.00,
      "taxes": {
        "issqn": {
          "base": 1000.00,
          "aliquot": 5,
          "city_code": "3550308",
          "service_list_item": "01.07",
          "exigibility": 1
        }
      }
    }
  ]
}
base / aliquot
vBC e vAliq. O valor (vISSQN) é calculado.
city_code
cMunFG: código IBGE do município do fato gerador.
service_list_item
cListServ, da LC 116/03. Aceito com ou sem ponto; o schema exige NN.NN e a API normaliza.
exigibility
indISS: 1 exigível, 2 não incidente, 3 isenção, 4 exportação, 5 imunidade, 6 suspensa por decisão judicial, 7 por processo administrativo. Nos casos 6 e 7, process_number é obrigatório.
withheld_amount
vISSRet, quando o ISS é retido pelo tomador.
services.competence_date
ISSQNtot/dCompet. Sem ele a API usa a data de emissão.
O serviço não entra no vProd. O valor do item de serviço vai para ISSQNtot/vServ e é de lá que compõe o vNF. Somá-lo ao ICMSTot/vProd devolve a Rejeição 564 — testado. Numa nota mista de 500,00 em mercadoria e 1.000,00 em serviço, o XML sai com vProd 500,00, vServ 1.000,00 e vNF 1.500,00.
Exige Inscrição Municipal no cadastro da empresa. Sem municipal_registration, a SEFAZ devolve a Rejeição 530 e a API recusa antes do envio. É dado cadastral do emitente, como a Inscrição Estadual — não vai na nota.

PIS e COFINS por substituição tributária

Tem dois lados, e só um está disponível.

Lado
Como se declara
Estado
Substituído
recebe a mercadoria com o tributo já retido
taxes.pis.code e taxes.cofins.code = 05
Disponível e comprovado na homologação da SEFAZ-SP
Substituto
retém e declara o valor
grupos PISST / COFINSST
Indisponível nesta versão
{
  "taxes": {
    "pis":    { "code": "05" },
    "cofins": { "code": "05" }
  }
}

O CST 05 é "Operação Tributável por Substituição Tributária". O item sai com PISNT/COFINSNT, sem valor destacado, e não move o vNF — o tributo foi recolhido antes, por quem substituiu. É o caso mais comum na ponta: o varejista que revende mercadoria com PIS/COFINS já retidos.

Por que o lado do substituto não está disponível. Não é decisão de escopo: é que não existe payload que chegue à autorizadora. A SEFAZ exige o grupo PIS em todo item — Rejeição 745, medida — e a biblioteca que gera o XML não monta PIS e PISST no mesmo item, embora o leiaute permita os dois. Com um, o outro é descartado; sem o PIS, a nota é recusada. Por isso taxes.pis_st e taxes.cofins_st são recusados no build, com a explicação, em vez de aceitos e silenciosamente ignorados.

Diferimento

Diferir é adiar o recolhimento: o imposto incide, mas o pagamento passa para outro momento da cadeia. Os dois sistemas tributários fazem isso de formas diferentes, e os grupos não se misturam — uma nota pode ter os dois ao mesmo tempo.

ICMS — CST 51
IBS/CBS — gDif
Onde fica
grupo próprio do CST
dentro de cada componente: IBS-UF, IBS-Mun e CBS
Imposto antes do diferimento
vICMSOp, campo explícito
não existe campo — é base × alíquota
Valor diferido
vICMSDif
vDif, um por componente
Valor devido
vICMS, campo explícito
o próprio vIBSUF/vCBS, já líquido
Totalizador
ICMSTot/vICMS recebe o devido; o diferido não tem total
IBSCBSTot tem vDif próprio, separado do devido
Quando é exigido
a critério de cada UF
pelo indicador ind_gDif do CST

ICMS diferido (CST 51)

{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "tax_benefit_code": "SP052600",
      "taxes": {
        "icms": {
          "code": "51",
          "origin": 0,
          "aliquot": 18,
          "deferral": { "percentage": 100 }
        }
      }
    }
  ]
}

Sobre 1.000,00 a 18%: vICMSOp 180,00, vICMSDif 180,00 e vICMS 0,00. Com percentage: 50, metade de cada. O ICMSTot/vICMS recebe o devido, e o vNF não se move — o ICMS é imposto por dentro.

deferral.percentage
pDif. Obrigatório no grupo, de 0 a 100.
deferral.fcp_percentage
pFCPDif: o FCP tem diferimento próprio, com percentual independente. Exige fcp_aliquot.
base_reduction
A redução de base vem antes: primeiro reduz a base, depois difere o imposto que ela gerou.
base_reduction_benefit_code
cBenefRBC, o código de benefício da redução — campo próprio do CST 51, distinto do tax_benefit_code do item.
O detalhamento é opcional, mas alíquota sem diferimento é recusada. O leiaute anota que "a exigência do preenchimento das informações do ICMS diferido fica a critério de cada UF", e aceita o grupo só com origem e CST. A API mantém isso. O que ela não faz é aceitar aliquot sem o grupo deferral: a alíquota não teria onde entrar e seria descartada em silêncio.

IBS/CBS diferido (CST 510 e 515)

{
  "taxes": {
    "ibscbs": {
      "cst": "510",
      "classification_code": "510001",
      "state_rate": 0.1,
      "municipal_rate": 0,
      "cbs_rate": 0.9,
      "deferral": { "percentage": 100 }
    }
  }
}

A alíquota informada continua sendo a padrão da transição — quem retira o imposto é o diferimento. Sobre 1.000,00: vDif 1,00 no IBS da UF e 9,00 na CBS, com vIBSUF e vCBS zerados. Informe só o percentual; os valores dos três componentes são calculados (regras UB23-10, UB42-10 e UB61-10).

No 515 — diferimento com redução de alíquota — as duas coisas se compõem na ordem certa: a redução do cClassTrib primeiro, o diferimento sobre a alíquota já reduzida. O grupo é exigido pelo indicador do CST (Rejeições 1030, 1044 e 1061) e recusado sem ele (1029, 1083 e 1090).

ICMS desonerado (vICMSDeson)

Quando o ICMS deixa de ser cobrado por isenção, redução, suspensão ou decisão judicial, o valor que deixou de ser cobrado é declarado no item, junto com o motivo. Vai em items[].taxes.icms.exemption.

value
Valor desonerado (vICMSDeson). Obrigatório, maior que zero.
reason
Motivo (motDesICMS). Obrigatório, e a lista aceita depende do CST — ver tabela abaixo.
deducts_from_total
Se o valor desonerado abate o que é cobrado do destinatário (indDeduzDeson). Padrão false.
{
  "items": [
    {
      "unit_price": 1000.00,
      "quantity": 1,
      "tax_benefit_code": "SP010030",
      "taxes": {
        "icms": {
          "code": "40",
          "origin": 0,
          "exemption": {
            "value": 180.00,
            "reason": 9,
            "deducts_from_total": false
          }
        }
      }
    }
  ]
}
A diferença entre desonerar e descontar. Com deducts_from_total: false — o padrão — o imposto não é cobrado do Estado, mas o preço do item permanece: vNF continua 1.000,00 e o ICMSTot/vICMSDeson registra os 180,00 como informação. Com true, o valor abate o total e a nota fecha em 820,00 (regra W16-10). As duas leituras foram autorizadas na homologação da SEFAZ-SP — o campo é seu, não uma fórmula fixa. Numa nota com vários itens, cada um decide o seu.
CST
Motivos aceitos (reason)
20 redução de base
3, 9, 10, 11, 12
30 isenta com ST
6, 7, 9
40, 41, 50
1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 90
70, 90
3, 9, 12

Um motivo fora da lista do CST é recusado no build, com a lista do que vale — o XSD tem enumerações diferentes por grupo, e um valor errado só apareceria como erro de schema no envio. A desoneração é dos CST do regime normal: nenhum grupo ICMSSN do Simples Nacional tem vICMSDeson, e enviá-la com emitente do Simples é recusado.

Estes CST exigem código de benefício. Os CST 20, 30, 40, 41, 50, 70 e 90 são de benefício fiscal, e ES, PR e SP exigem o código da tabela estadual em items[].tax_benefit_code. Sem ele a SEFAZ devolve a Rejeição 930; com um código inexistente, a 946. A tabela de SP está em portal.fazenda.sp.gov.br; os demais estados não usam cBenef.

Partilha do ICMS na venda interestadual (ICMSUFDest / DIFAL)

Na venda interestadual para consumidor final não contribuinte, a diferença entre a alíquota interna da UF de destino e a interestadual pertence ao estado do comprador (EC 87/2015). Isso vai no grupo ICMSUFDest por item, e a API soma o totalizador ICMSTot/vICMSUFDest automaticamente.

Informe por item, em items[].taxes.icms_destination:

aliquot
Alíquota interna da UF de destino, em percentual. Obrigatório.
interstate_aliquot
Alíquota interestadual: 4, 7 ou 12. Obrigatório — outros valores são recusados no build.
fcp_aliquot
Alíquota do Fundo de Combate à Pobreza da UF de destino. Opcional; sem ela o FCP não é gerado.
base
Base de cálculo na UF de destino. Opcional; assume a base do item.
fcp_base
Base do FCP. Opcional; assume a mesma base acima.
{
  "consumer_final": true,
  "recipient": {
    "state_registration_indicator": 9,
    "address": { "state_code": "SP" }
  },
  "items": [
    {
      "cfop": "6108",
      "unit_price": 1000.00,
      "quantity": 1,
      "taxes": {
        "icms": { "code": "00", "origin": 0, "aliquot": 12 },
        "icms_destination": {
          "aliquot": 18,
          "interstate_aliquot": 12,
          "fcp_aliquot": 2
        }
      }
    }
  ]
}

O exemplo gera vICMSUFDest = 60,00 — (18 − 12)% sobre 1.000,00 — e vFCPUFDest = 20,00. Desde 2019 a partilha é integral para o destino, então pICMSInterPart é sempre 100 e vICMSUFRemet é sempre zero; a API preenche os dois, você não precisa informá-los.

A API não adivinha alíquota. Ela varia por UF de destino e por produto, e um palpite errado vira imposto errado com aparência de correto. Se a operação exigir a partilha e icms_destination não vier, a nota é gerada sem o grupo e a resposta traz um aviso em build_warnings — nada é inventado silenciosamente.
Simples Nacional e MEI estão fora da partilha. O STF suspendeu a cláusula nona do Convênio ICMS 93/2015 (ADI 5464) e a LC 190/2022 manteve a exclusão: o emitente optante não recolhe o DIFAL nessa operação. Por isso a API não cobra o grupo desses emitentes. Se você informá-lo mesmo assim, ele é emitido — com um aviso em build_warnings para você confirmar a regra da sua UF.

IPI devolvido na devolução (impostoDevol / vIPIDevol)

Quando um emitente que não é contribuinte do IPI (Simples Nacional, MEI) devolve mercadoria a um fornecedor do regime normal, o IPI que veio destacado na nota de compra precisa voltar no grupo impostoDevol. É ele que dá ao destinatário o direito ao crédito do IPI: o vOutro faz o total fechar, mas não produz esse efeito. O grupo existe no leiaute desde a versão 3.10; o totalizador no grupo de totais (ICMSTot/vIPIDevol) entrou com o leiaute 4.00, pela NT 2016.002.

Informe por item, em items[].returned_ipi:

value
Valor do IPI devolvido daquele item. Obrigatório quando o objeto é informado.
percentage
Percentual da mercadoria devolvida (quantidade devolvida ÷ quantidade da nota original). Opcional; assume 100, que é a devolução integral do item.
{
  "purpose": 4,
  "nature_operation": "Devolução de compra",
  "operation_type": "saida",
  "referenced_documents": [
    { "access_key": "32250612345678000199550010000001231000001234" }
  ],
  "items": [
    {
      "code": "SKU-001",
      "name": "Produto devolvido integralmente",
      "ncm": "85369090",
      "cfop": "5202",
      "unit": "UN",
      "quantity": 10,
      "unit_price": 100,
      "returned_ipi": { "value": 209.40 },
      "taxes": { "icms": { "code": "102", "origin": 2, "aliquot": 0 } }
    },
    {
      "code": "SKU-002",
      "name": "Produto devolvido pela metade",
      "ncm": "85369090",
      "cfop": "5202",
      "unit": "UN",
      "quantity": 4,
      "unit_price": 50,
      "returned_ipi": { "value": 15.60, "percentage": 50 },
      "taxes": { "icms": { "code": "102", "origin": 2, "aliquot": 0 } }
    }
  ]
}

No exemplo, vProd = 1200,00 e o total do IPI devolvido é 209,40 + 15,60 = 225,00, resultando em vNF = 1425,00. O totalizador e o vNF são calculados pela API: não existe campo de total independente.

Mapeamento XML

items[].returned_ipi.percentage
det/impostoDevol/pDevol — percentual com duas casas, de 0 a 100.
items[].returned_ipi.value
det/impostoDevol/IPI/vIPIDevol — valor por item.
soma de items[].returned_ipi.value
total/ICMSTot/vIPIDevol — calculado pela API (regra 863: o total tem de bater com a soma dos itens).
—
total/ICMSTot/vNF — o vIPIDevol entra na composição do total da nota (regra W16-10).
Referência por item, com data por ambiente. Pela regra VC02-14 da NT 2025.002-RTC v1.40, a devolução passa a apontar a nota de origem item a item, e não mais só no cabeçalho; sem isso vem a variante "por item" da Rejeição 321. A virada é 01/07/2026 em homologação e 01/09/2026 em produção. A API acompanha esse calendário sozinha: informe a chave em referenced_documents como sempre e, no ambiente em que a regra já vale, ela é replicada em det/DFeReferenciado de cada item — como os dois níveis são mutuamente exclusivos (Rejeição 1010), o ide/NFref deixa de ser gravado aí. Antes da virada, o ambiente continua recebendo a referência no cabeçalho, que é a que a SEFAZ aceita nele.
Atenção ao nItem inferido. Sem indicação explícita, a API assume que a devolução espelha a nota de origem linha a linha e usa a posição do item. A SEFAZ não confere essa correspondência — testamos apontar de propósito para o item errado e a nota foi autorizada mesmo assim. Ou seja, um mapeamento errado não volta como rejeição: vira dado fiscal errado em silêncio. Sempre que a ordem dos seus itens não for a mesma da nota do fornecedor, informe items[].referenced_document.item. Quando a API infere, ela devolve o aviso em build_warnings.

Quando a ordem dos itens não coincide com a da nota original, ou quando a devolução consolida mais de uma nota, informe a referência explicitamente em cada linha:

{
  "items": [
    {
      "code": "SKU-001",
      "referenced_document": {
        "access_key": "32250612345678000199550010000001231000001234",
        "item": 7
      },
      "returned_ipi": { "value": 209.40 }
    }
  ]
}
Onde o grupo não é aceito: returned_ipi só vale em NF-e (modelo 55) com purpose: 4. Em NFC-e, ou em qualquer outra finalidade, a API retorna erro de validação no build — a SEFAZ rejeitaria com cStat 390 — "Nota Fiscal com grupo de devolução de tributos".

Crédito de ICMS do Simples Nacional na devolução (CSOSN 101/900)

Empresas do Simples Nacional que devolvem mercadoria a um fornecedor do regime normal podem informar o crédito de ICMS a que o destinatário tem direito (art. 23 da LC 123/2006). Use taxes.icms.credit_rate com a alíquota do crédito — a API calcula o valor automaticamente sobre a base do item (unit_price × quantity − discount) e grava no grupo pCredSN/vCredICMSSN. O campo vale tanto para CSOSN 101 quanto 900.

{
  "purpose": 4,
  "nature_operation": "Devolução de compra",
  "operation_type": "saida",
  "referenced_documents": [
    { "access_key": "32250612345678000199550010000001231000001234" }
  ],
  "items": [
    {
      "code": "SKU-001",
      "name": "Produto de demonstração",
      "ncm": "85369090",
      "cfop": "5202",
      "unit": "UN",
      "quantity": 1,
      "unit_price": 1000.00,
      "other_expenses": 50.00,
      "taxes": { "icms": { "code": "900", "origin": 2, "aliquot": 0, "credit_rate": 4.00 } }
    }
  ]
}

No exemplo, a base do item é 1000,00 e o crédito de 4% resulta em vCredICMSSN = 40,00.

Mapeamento XML

items[].taxes.icms.credit_rate
ICMSSN101/pCredSN ou ICMSSN900/pCredSN — alíquota do crédito, com 4 casas decimais.
base do item × credit_rate
ICMSSN101/vCredICMSSN ou ICMSSN900/vCredICMSSN — valor do crédito, calculado pela API.
Sem credit_rate: o comportamento é o mesmo de antes — CSOSN 101 grava o grupo zerado (0.00), exigido pelo schema; CSOSN 900 não grava o grupo.

Venda a prazo com fatura e duplicatas

Para fazer o grupo fiscal cobr/fat/dup aparecer no XML e na DANFE da NF-e modelo 55, use payload.billing. O pagamento financiado deve usar method=14 (Duplicata Mercantil); indicator=1 informa pagamento a prazo e também é assumido automaticamente quando omitido nesse método.

Mapeamento para o XML/MOC

Campo da API
Destino no XML
billing.invoice.number
cobr/fat/nFat
billing.invoice.original_amount
cobr/fat/vOrig
billing.invoice.discount_amount
cobr/fat/vDesc
billing.invoice.net_amount
cobr/fat/vLiq
billing.installments[].number
cobr/dup/nDup
billing.installments[].due_date
cobr/dup/dVenc
billing.installments[].amount
cobr/dup/vDup
payments[].indicator
pag/detPag/indPag
payments[].method
pag/detPag/tPag
payments[].paid_at
pag/detPag/dPag, data do pagamento.
payments[].processed_document / processed_state
CNPJPag e UFPag: onde o pagamento foi processado, quando diferente do emitente. Andam em par — o leiaute traz os dois no mesmo grupo.
payments[].card
Cartão, PIX, boleto e demais pagamentos eletrônicos: integration (tpIntegra, obrigatório), brand, authorization, institution_document, receiver_document, terminal_id. Nos meios 03, 04 e 17 o grupo inteiro é obrigatório na NFC-e — sem ele vem a Rejeição 391; na NF-e a ausência vira alerta em build_warnings. A authorization é obrigatória no pagamento integrado: sem ela a SEFAZ devolve a Rejeição 392. Com integration: 2 ela é dispensada. Detalhes em Cartão e PIX.
payments[].description
pag/detPag/xPag. Exclusiva do meio 99 (Outros), e obrigatória nele: sem ela a SEFAZ devolve a Rejeição 441; com ela em qualquer outro meio, a 442. O schema apenas a declara opcional — a regra é da autorizadora.
payments[].amount
pag/detPag/vPag

Exemplo completo para copiar

Substitua {token}, os dados fiscais e as datas de vencimento antes de executar.

curl -X POST 'https://fiscal.contora.com.br/api/v1/nfe/drafts' \
  -H 'Authorization: Bearer {token}' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: pedido-214-nfe' \
  --data-raw '{
  "document_type": "nfe",
  "series": 1,
  "number": 214,
  "payload": {
    "nature_operation": "Venda de mercadoria a prazo",
    "operation_type": "saida",
    "consumer_final": false,
    "presence_indicator": 0,
    "recipient": {
      "name": "Cliente de Exemplo LTDA",
      "document": "98765432000110",
      "state_registration_indicator": 9,
      "address": {
        "street": "Avenida Parana",
        "number": "1000",
        "district": "Batel",
        "city_name": "Curitiba",
        "city_code": "4106902",
        "state_code": "PR",
        "postal_code": "80000000"
      }
    },
    "items": [{
      "code": "SKU-100",
      "name": "Produto de demonstracao",
      "ncm": "85176259",
      "cfop": "5102",
      "unit": "UN",
      "quantity": 1,
      "unit_price": 1500.00,
      "taxes": {
        "icms": {"code": "00", "origin": 0, "aliquot": 18},
        "pis": {"code": "01", "aliquot": 1.65},
        "cofins": {"code": "01", "aliquot": 7.6}
      }
    }],
    "payments": [
  {
    "method": "14",
    "indicator": 1,
    "amount": 1500.00
  }
],
"billing": {
  "invoice": {
    "number": "FAT-000214",
    "original_amount": 1500.00,
    "discount_amount": 0.00,
    "net_amount": 1500.00
  },
  "installments": [
    {
      "number": "001",
      "due_date": "2030-08-20",
      "amount": 500.00
    },
    {
      "number": "002",
      "due_date": "2030-09-20",
      "amount": 500.00
    },
    {
      "number": "003",
      "due_date": "2030-10-20",
      "amount": 500.00
    }
  ]
    }
  }
}'
Fechamento obrigatório: net_amount deve ser original_amount - discount_amount; a soma das duplicatas e a soma dos pagamentos method=14 devem ser iguais a net_amount; a soma de todos os pagamentos deve fechar com o total da NF-e. Use números de parcela 001, 002, 003, datas futuras em YYYY-MM-DD e no máximo 120 parcelas.
Somente NF-e: NFC-e modelo 65 não aceita fatura ou duplicatas. A API rejeita billing em NFC-e e também rejeita method=14 sem o grupo de cobrança, antes do envio à SEFAZ.

Pagamento parcialmente à vista e parcialmente a prazo

Em uma NF-e de 1500.00, os 300.00 pagos em dinheiro ficam separados dos 1200.00 financiados. Somente a parte com method=14 entra na fatura e nas duplicatas.

"payments": [
  {"method": "01", "indicator": 0, "amount": 300.00},
  {"method": "14", "indicator": 1, "amount": 1200.00}
],
"billing": {
  "invoice": {
    "number": "FAT-000215",
    "original_amount": 1200.00,
    "discount_amount": 0.00,
    "net_amount": 1200.00
  },
  "installments": [
    {"number": "001", "due_date": "2030-08-20", "amount": 600.00},
    {"number": "002", "due_date": "2030-09-20", "amount": 600.00}
  ]
}

Erros de validação

Quando as duplicatas não fecham com a fatura, a API responde HTTP 422 e aponta o campo exato:

{
  "ok": false,
  "message": "Falha de validacao no request.",
  "error": {
    "type": "validation_error",
    "details": {
      "fields": {
        "payload.billing.installments": [
          "A soma das duplicatas precisa ser exatamente igual ao net_amount da fatura."
        ]
      }
    }
  }
}

Se method=14 for enviado sem fatura e duplicatas, o retorno mantém o mesmo envelope:

{
  "ok": false,
  "message": "Falha de validacao no request.",
  "error": {
    "type": "validation_error",
    "details": {
      "fields": {
        "payload.payments": [
          "O meio de pagamento 14 (Duplicata Mercantil) exige payload.billing com fatura e duplicatas."
        ]
      }
    }
  }
}

Frete, transportadora e volumes (shipping)

Use payload.shipping quando a operação realmente envolver transporte. Sem esse campo, a nota continua sendo emitida com modalidade 9 (sem ocorrência de transporte), como antes — nada muda para quem não usa o recurso. carrier, vehicle e volumes são opcionais dentro de shipping e podem ser combinados livremente.

{
  "shipping": {
    "modality": 0,
    "carrier": {
      "name": "Transportadora Rápida LTDA",
      "document": "11222333000181",
      "state_registration": "1234567890",
      "address": "Rodovia BR-101, KM 50",
      "city_name": "Curitiba",
      "state_code": "PR"
    },
    "vehicle": {
      "plate": "ABC1D23",
      "state_code": "PR",
      "rntc": "99887766"
    },
    "volumes": [
      {
        "quantity": 2,
        "species": "CAIXA",
        "brand": "MARCA-X",
        "numbering": "001/002",
        "net_weight": 10.5,
        "gross_weight": 12.0
      }
    ]
  }
}

Mapeamento XML

shipping.modality
transp/modFrete — 0 CIF, 1 FOB, 2 por conta de terceiros, 3 transporte próprio do remetente, 4 transporte próprio do destinatário, 9 sem ocorrência de transporte (padrão quando shipping não é enviado).
shipping.carrier.*
transp/transporta — nome, CPF/CNPJ, IE, endereço, município e UF do transportador.
shipping.vehicle.*
transp/veicTransp — placa (normalizada para 7 caracteres alfanuméricos), UF e RNTC.
shipping.volumes[].*
transp/vol — quantidade, espécie, marca, numeração e pesos líquido/bruto (kg), um grupo por posição da lista.
CNPJ/CPF do transportador: assim como acontece com qualquer outro CNPJ na NF-e, a SEFAZ valida o dígito verificador do shipping.carrier.document. Um CNPJ com dígito inválido é rejeitado (cStat 542) antes de chegar à autorização — teste com um CNPJ real ou ao menos com dígito verificador válido.

Cartão e PIX: o grupo card

Para a SEFAZ, PIX é pagamento eletrônico e entra na mesma regra do cartão. Sempre que payments[].method for 03 (crédito), 04 (débito) ou 17 (PIX), envie o grupo payments[].card. Sem ele pode vir a Rejeição 391 — "Não informados os dados do cartão de crédito / débito nas Formas de Pagamento da Nota Fiscal".

O PIX é o caso que costuma surpreender: o nome do grupo fala em cartão, o pagamento não passa por maquininha nenhuma, e é natural enviar só o meio e o valor. A regra da autorizadora, porém, é a mesma.

A exigência depende do modelo. Na NFC-e (65) a rejeição é certa, e a API barra antes: o rascunho aceita o pagamento sem o grupo, e o build responde 422 com document_build_error, gravando a mensagem em build_errors. Como a nota não chega a ser assinada nem transmitida, o número não é consumido na SEFAZ — corrija o payload e reenvie com o mesmo número. Na NF-e (55) as UFs que medimos autorizam a nota sem o grupo, então a API transmite normalmente e devolve o alerta em build_warnings. Se a sua UF cobrar, a 391 vem na resposta da SEFAZ.

Enviar o grupo é sempre o mais seguro: vale nos dois modelos e não muda o resultado onde a SEFAZ não cobra.

O mínimo que faz uma venda em PIX passar é um campo:

"payments": [
  {
    "method": "17",
    "indicator": 0,
    "amount": 4400.00,
    "card": { "integration": 2 }
  }
]
card.integration
tpIntegra, obrigatório. 1 integrado ao sistema de automação (TEF, POS integrado, e-commerce, PSP que devolve a confirmação ao ERP); 2 não integrado — a maquininha avulsa, o PIX por chave ou QR Code estático em que a baixa é manual.
card.authorization
cAut, o número de autorização devolvido pela operadora, pelo PSP ou pelo banco. Obrigatório quando integration: 1: sem ele a SEFAZ devolve a Rejeição 392. Com integration: 2 é dispensado.
card.institution_document
CNPJ — os 14 dígitos da credenciadora ou do PSP.
card.brand
tBand, o código de 2 dígitos da bandeira: 01 Visa, 02 Mastercard, 03 American Express, 04 Sorocred, 05 Diners, 06 Elo, 07 Hipercard, 08 Aura, 09 Cabal, 99 outros.
card.receiver_document
CNPJReceb — os 14 dígitos do estabelecimento que recebeu o pagamento, quando diferente do emitente.
card.terminal_id
idTermPag, o identificador do terminal em que o pagamento foi processado.

Uma venda em cartão de crédito integrado ao PDV fica assim:

"payments": [
  {
    "method": "03",
    "indicator": 0,
    "amount": 1250.00,
    "card": {
      "integration": 1,
      "authorization": "884523",
      "institution_document": "01027058000191",
      "brand": "02"
    }
  }
]

Quando o grupo falta numa NFC-e, o build responde:

{
  "ok": false,
  "message": "O meio de pagamento 17 (PIX, pagamento instantâneo) exige o grupo payments[].card na NFC-e — sem ele a SEFAZ devolve a Rejeição 391. (...)",
  "error": {
    "type": "document_build_error",
    "details": {}
  }
}

Na NF-e, o mesmo payload constrói e o alerta vem junto:

{
  "ok": true,
  "data": {
    "build_warnings": [
      "O meio de pagamento 17 (PIX, pagamento instantâneo) foi informado sem o grupo payments[].card. A NF-e é transmitida assim, e as UFs que medimos autorizam, mas a UF que cobrar o grupo devolve a Rejeição 391. (...)"
    ]
  }
}

Pagamento em dinheiro com troco

Fora do fluxo de fatura/duplicata (payload.billing), a soma de payments[].amount precisa ser maior ou igual ao valor total do documento. Quando for maior, o excedente é gravado automaticamente como troco (pag/vTroco) — mas só quando houver pagamento(s) em dinheiro (method: "01") suficiente(s) para cobrir a diferença; caso contrário a API rejeita antes do envio, já que não faz sentido dar troco de um pagamento em cartão ou PIX.

{
  "payments": [
    { "method": "01", "amount": 200.00 }
  ]
}

Se o total do documento for 150,00, esse pagamento gera vTroco = 50,00. Quando a soma dos pagamentos é exatamente igual ao total (o caso mais comum), o comportamento é o de sempre: nenhuma mudança para quem já integra hoje.

Soma dos pagamentos = total
Comportamento inalterado; vTroco não aparece (ou aparece como 0.00 quando já havia algum meio de pagamento diferente de 90).
Soma dos pagamentos > total, com dinheiro suficiente
O excedente vira pag/vTroco automaticamente.
Soma dos pagamentos > total, sem dinheiro suficiente
HTTP 422 — O troco (...) precisa estar coberto por pagamento(s) em dinheiro (method 01).
Soma dos pagamentos < total
HTTP 422 — A soma de payments e menor que o valor total da NF-e.
payload.billing presente
Regra de troco não se aplica; a soma continua precisando ser exatamente igual ao net_amount da fatura (ver seção de duplicatas acima).

Venda por marketplace ou plataforma de intermediação

Use payload.marketplace quando a venda passar por um intermediador (marketplace, plataforma de delivery e similares). Esse campo grava ide/indIntermed = 1 e o grupo infIntermed.

O intermediador só existe em operação não presencial. A NT 2020.006 admite o grupo apenas quando presence_indicator é 2, 3, 4 ou 9; nos demais (0, 1 e 5) a regra 435_B25c-20 proíbe a tag e a SEFAZ devolve a Rejeição 435. Por isso, enviar marketplace junto de um presence_indicator presencial retorna erro de validação no build, antes de consumir uma tentativa de autorização — ajuste o indicador de presença para o valor que descreve a operação real.
{
  "marketplace": {
    "intermediary_document": "44455566000183",
    "intermediary_id": "VENDEDOR-XPTO-001"
  }
}

Operação sem intermediador (indIntermed = 0)

Não é preciso enviar nada além de presence_indicator. Quando a operação é direta — venda em site ou plataforma própria, atendimento B2G, televendas — basta omitir payload.marketplace: a API grava ide/indIntermed = 0 automaticamente sempre que presence_indicator for 2, 3, 4 ou 9, que são os indicadores de presença para os quais a NT 2020.006 exige a tag.

{
  "presence_indicator": 9
}
// resultado no XML: ide/indIntermed = 0, sem grupo infIntermed

Para presence_indicator igual a 0, 1 ou 5 a tag não se aplica e não é gravada, conforme a mesma nota técnica.

Mapeamento XML

Com marketplace e presence_indicator em 2, 3, 4 ou 9
ide/indIntermed = 1 mais o grupo infIntermed.
Sem marketplace e presence_indicator em 2, 3, 4 ou 9
ide/indIntermed = 0 (operação sem intermediador), sem infIntermed.
Sem marketplace e presence_indicator em 0, 1 ou 5
A tag indIntermed não é gravada.
Com marketplace e presence_indicator em 0, 1 ou 5
Erro de validação no build (combinação proibida pela NT 2020.006 — geraria a Rejeição 435).
marketplace.intermediary_document
infIntermed/CNPJ — CNPJ do intermediador; obrigatório e validado (14 dígitos).
marketplace.intermediary_id
infIntermed/idCadIntTran — identificador do vendedor cadastrado na plataforma; de 2 a 60 caracteres.

Exemplo pratico de resposta

Depois de criar o rascunho, a API devolve um identificador estavel do documento, o status inicial do ciclo de vida e os primeiros metadados para acompanhamento.

{
  "ok": true,
  "data": {
    "id": "18f017f7-a69c-4fa6-89c7-05f52fcc8be4",
    "document_type": "nfe",
    "environment": "homologacao",
    "series": 1,
    "number": 214,
    "status": "draft",
    "processing_status": "draft",
    "requested_action": "build_unsigned",
    "created_at": "2026-07-02T14:35:18-03:00"
  }
}

Build e emissão

Depois do rascunho salvo, o ERP pode pedir o build fiscal e em seguida disparar a emissão assíncrona.

POST https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/build (Global - Recomendado)

Build opcional

{"sign": false}
sign=false
Valida e monta xml_unsigned, sem certificado e sem contato com a SEFAZ. É o padrão quando sign é omitido.
sign=true
Monta e assina xml_signed usando o A1 ativo, mas ainda não transmite.
Sem build manual
É permitido. O dispatch com action=authorize monta e assina automaticamente antes da transmissão.
built_signed
Significa somente que o XML foi montado e assinado. A NF-e ainda não foi enviada nem autorizada pela SEFAZ; execute o dispatch.
POST https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/build (Hierárquico)
POST https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/dispatch (Global - Recomendado)
POST https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/dispatch (Hierárquico)
{
  "action": "authorize"
}

A resposta de dispatch confirma o enfileiramento. A autorização final deve ser acompanhada por consulta de status ou webhook.

HTTP/1.1 202 Accepted

{
  "ok": true,
  "message": "Documento enfileirado para processamento.",
  "data": {
    "id": "18f017f7-a69c-4fa6-89c7-05f52fcc8be4",
    "document_type": "nfe",
    "status": "draft",
    "processing_status": "queued",
    "requested_action": "authorize"
  }
}
Próximo passo: espere webhook ou consulte GET /nfe/drafts/{document}/status. Em polling, aplique backoff; não recrie nem redespache o documento enquanto estiver queued, processing ou authorize_pending.

Cancelamento e Carta de Correção

Só é possível cancelar ou corrigir uma NF-e já authorized. Os dois endpoints são assíncronos como o dispatch: enfileiram o evento e a confirmação vem por /status ou webhook.

POST https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/cancel (Global - Recomendado)
POST https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/cancel (Hierárquico)
{
  "reason": "Cancelamento por erro operacional na emissao."
}
reason
Obrigatório, entre 15 e 255 caracteres.
POST https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/corrections (Global - Recomendado)
POST https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/corrections (Hierárquico)
{
  "text": "Corrigir informacao complementar sem alterar valores fiscais."
}
text
Obrigatório, entre 15 e 1000 caracteres. CC-e não altera valores fiscais, apenas informações cadastrais e complementares.
Onde ver o resultado: o histórico de cancelamentos e CC-e fica em GET /nfe/drafts/{document}/fiscal-events, e o XML do evento (xml_cancelled, xml_correction) sai por GET /nfe/drafts/{document}/artifacts.

Sequência recomendada para a primeira emissão

  1. Criar o rascunho com o payload completo.
  2. Executar o build assinado se quiser revisar XML antes do envio.
  3. Disparar action=authorize.
  4. Acompanhar por /status ou webhook.
  5. Quando autorizado, baixar XML e DANFE.
  6. Se houver rejeição, revisar o retorno traduzido antes de reenviar.
Leitura prática: para onboarding, emita primeiro em homologação, confirme que webhook e artefatos estão corretos e só depois crie uma chave de produção para o mesmo fluxo.

Máquina de estados resumida

draft
  ├─ build(sign=false) ─→ built_unsigned
  ├─ build(sign=true)  ─→ built_signed
  └─ dispatch(authorize) → queued → processing
                                  ├─ authorized ─┬─ cancel      → cancelled
                                  │              └─ corrections → correction registrada
                                  ├─ authorize_pending → authorized | error
                                  └─ error / failed
statusEstado fiscal e material do documento: rascunho, montado, pendente, autorizado ou erro.
processing_statusEstado técnico da fila: draft, queued, processing, completed ou failed.
requested_actionA ação em curso ou mais recente: build, authorize, cancel ou correction.
Rejeição x falhaRejeição vem da regra fiscal/SEFAZ; failed indica falha técnica de processamento.

Disponibilidade atual da v1

Ja operacional Rascunho, build, dispatch, status, artefatos, cancelamento, CC-e, tentativas, eventos e webhook.
Escopo desta pagina Esta e hoje a familia documental mais madura da plataforma e o melhor ponto de entrada para pilotos e onboarding assistido.

NF-e recebidas — DFe do destinatário

Além de emitir, a API captura as NF-e emitidas contra o CNPJ da empresa no DFe do destinatário (Ambiente Nacional). A sincronização avança por NSU e importa o resumo primeiro; o XML completo só fica disponível depois que a empresa registra a ciência da operação, como manda a SEFAZ.

POST /nfe/received/sync
Busca o próximo bloco do DFe. Enquanto has_more for true, repita. A SEFAZ limita a uma consulta por minuto por CNPJ: em consumo indevido (cStat 656) a resposta é 422 com orientação para aguardar.
GET /nfe/received
Lista as notas capturadas, com filtros por ambiente, emitente, período, has_full_xml e manifest_type=none (o que ainda falta manifestar).
GET /nfe/received/{id}
Detalhe da nota recebida.
GET /nfe/received/{id}/xml
XML completo (procNFe). Se ainda só houver resumo, a API tenta buscar na hora; quando ele depende da manifestação, responde 409.
POST /nfe/received/{id}/manifest
Manifestação do destinatário: ciencia (210210), confirmacao (210200), desconhecimento (210220) e nao_realizada (210240, com justificativa de no mínimo 15 caracteres).
Leitura, não emissão. Sincronizar e listar não consome a franquia de eventos nem exige certificado para consultar — o A1 da empresa é usado para falar com a SEFAZ (DFe e eventos). Cada nota nova dispara o webhook nfe.received, o gancho natural para um monitor de entradas sem polling constante.