nature_operationAtualizado 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.
https://fiscal.contora.com.br/api/v1/nfe/drafts
(Global — recomendado)
X-Company-Document: CNPJ_SEM_MASCARA. A rota hierárquica identifica a empresa pelo UUID da URL.
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
}
]
}
}
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.
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.
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.
operation_typesaida ou entrada.id_destide/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.recipientdest/email.items[]items[].discountdet/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_expensesdet/prod/vOutro, fora de qualquer base de cálculo. Compõe ICMSTot/vOutro e o vNF.items[].additional_charges[]items[].freight / items[].insurancedet/prod/vFrete e vSeg. Integram a base de cálculo dos tributos e compõem o vNF. Veja frete e seguro.items[].taxespayments[]99 (Outros) exige description.billingitems[].purchase_order / purchase_order_itemdet/prod/xPed e nItemPed.items[].taxes.icms.exemptionvICMSDeson, motDesICMS, indDeduzDeson). Veja ICMS desonerado.items[].taxes.ipi.quantity / unit_amountqUnid × vUnid, o modo de bebidas e cigarros. Exclusivo com aliquot — o leiaute traz um choice.items[].taxes.pis.code = 03PISQtde/COFINSQtde). Exige quantity e unit_amount em vez de aliquot.items[].taxes.icms.st.exemptionvICMSSTDeson, motDesICMSST), nos CST 10, 70 e 90. Independente da desoneração do ICMS próprio.exit_at / delivery_forecastdhSaiEnt) 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.contingencymode (tpEmis), entered_at (dhCont) e reason (xJust, mínimo 15 caracteres). Os três andam juntos.fiscal_infoinfAdic/infAdFisco: texto de interesse do Fisco, separado do additional_info, que é do contribuinte.items[].approximate_taxesvTotTrib, 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_barcodecBarra e cBarraTrib: código de barras fora do padrão GTIN.items[].relevant_scale / manufacturer_documentindEscala 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_codesEXTIPI, nFCI, indBemMovelUsado e até 8 NVE.recipient.suframadest/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 / servicesitems[].taxes.pis.code = 05items[].taxes.icms.deferral / items[].taxes.ibscbs.deferralgDif). Grupos distintos, com estruturas distintas. Veja diferimento.items[].taxes.ibscbspurchasepurposeide/finNFe): 1 normal, 2 complementar, 3 ajuste, 4 devolução. Veja complementar e ajuste.recipient.foreign_document / export / items[].export_details[]referenced_documents[]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_ipidet/impostoDevol (pDevol + IPI/vIPIDevol); a soma compõe ICMSTot/vIPIDevol e o vNF. Somente NF-e modelo 55 com purpose: 4.items[].referenced_documentdet/DFeReferenciado), quando a ordem dos itens não espelha a nota original.shippingtransp). Sem este campo, a nota sai com modalidade 9 (sem ocorrência de transporte), como antes.items[].tracking[] / items[].medicineitems[].agrochemicalinfNFe/agropecuario/defensivo), NT 2024.003. Veja defensivos agrícolas.delivery_address / pickup_addressentrega) e local de retirada (retirada), quando diferentes dos endereços do destinatário e do emitente. Veja entrega e retirada.marketplaceindIntermed e infIntermed), para operações via marketplace ou plataforma de delivery.additional_infocode 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,70e90. Para emitentes emsimples_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
900quandoaliquot > 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 (CSOSN101ou900), usetaxes.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,50a67,70a75,98e99); para03calcula por quantidade (quantityeunit_amount); para04a09gera o grupo não tributado (PISNT/COFINSNT). - IPI: calcula para os CSTs
50e99; setaxes.ipinão for enviado, a tag IPI não é gerada.
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_orderdet/prod/xPed — até 15 caracteres.items[].purchase_order_itemdet/prod/nItemPed — de 1 a 6 dígitos.purchase.commitment_notecompra/xNEmp — até 22 caracteres.purchase.ordercompra/xPed — até 60 caracteres.purchase.contractcompra/xCont — até 60 caracteres.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_expensesitems[].additional_charges[]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 é.
amountdescriptiondet/infAdProd — o campo que aparece no DANFE. Várias despesas no mesmo item são unidas por |, até 500 caracteres.in_icms_basein_pis_cofins_basein_ipi_base{
"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.
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.
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).
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_keypurpose: 4 vai para det/DFeReferenciado (por item); nas demais finalidades, para ide/NFref/refNFe.items[].other_expensesdet/prod/vOutro — valor por item, duas casas decimais. Legado, fora de qualquer base.items[].additional_charges[].amountdet/prod/vOutro, e nas bases marcadas.items[].additional_charges[].descriptiondet/infAdProd — descrições do item unidas por |.total/ICMSTot/vOutro — calculado pela API.IBS e CBS (reforma tributária)
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.
cst000 (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_codecClassTrib, 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_ratepIBSUF). Na redução, a efetiva é calculada pela API.municipal_ratepIBSMun).cbs_ratepCBS).base_amount{
"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 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"
}
}
}
}
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"
}
}
}
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.
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.
lotnLote), 1 a 20 caracteres. Obrigatório.quantityqLote), até 3 casas decimais. Obrigatório, maior que zero.manufactured_atdFab), AAAA-MM-DD. Obrigatória.expires_atdVal), AAAA-MM-DD. Obrigatória. Se a validade não especifica o dia, informe o último dia do mês.aggregation_codecAgreg). 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" }
]
}
]
}
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.
Medicamentos — items[].medicine
anvisa_codecProdANVISA), ou o literal ISENTO. Obrigatório.exemption_reasonxMotivoIsencao), como o número da RDC. Obrigatório com ISENTO e recusado quando há registro real.max_consumer_pricevPMC). Obrigatório.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_numbernReceituario), 1 a 30 caracteres do conjunto do XSD: letras, dígitos e @ # , - _ . ; : $ % /. Obrigatório.technical_responsible_cpfCPFRespTec). 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"
}
}
]
}
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_addressentrega: onde a mercadoria será entregue. Típico de e-commerce e de venda à ordem.pickup_addressretirada: 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.
documentstreet, number, district, city_code, city_name, state_codexLgr, nro, xBairro, cMun (código IBGE), xMun e UF.namexNome). Opcional.complement, postal_code, phone, email, state_registrationxCpl, 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"
}
}
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[].freightvFrete).items[].insurancevSeg).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_amountshipping.insurance_amountshipping.modalitymodFrete: 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.
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".
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.
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_modemodBCST): 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.aliquotpICMSST). Obrigatório.mvapMVAST). Obrigatória quando base_mode = 4.basevBCST). Sem ela, é derivada do mva sobre a base do ICMS.base_reductionpRedBCST). Opcional.fcp_aliquot, fcp_basepFCPST, 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, valuevBCSTRet, pST e vICMSSTRet. Os três são obrigatórios juntos.substitute_valuevICMSSubstituto). 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_baseeffective_base, effective_aliquot, effective_base_reductionST 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.
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.
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.
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_aliquotfcp_basebase_reductionpRedBC). 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.
00vBCFCP aqui: a base é a própria vBC, então fcp_base não é aceito.20base_reduction obrigatório.90base_reduction opcional.10, 70items[].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.
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_documentidEstrangeiro. Exclusivo com document — o destinatário está no Brasil ou fora dele.recipient.address.country_codecPais, na tabela do BACEN. Obrigatório no exterior.recipient.address.country_namexPais. Obrigatório no exterior.export.exit_stateUFSaidaPais: a UF de embarque ou de transposição de fronteira.export.exit_locationxLocExporta: o porto, aeroporto ou ponto de fronteira.export.dispatch_locationxLocDespacho, 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 }
]
}
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
}
}
]
}
]
}
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.
purpose1 normalreferenced_documents sem virar outra finalidade.2 complementar3 ajuste4 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
unit_price: 0 com o destaque do tributo sobre a base informada. O vNF fecha em 0,00.unit_price com a diferença que faltou. O vNF é essa diferença.quantity com a diferença e unit_price: 0.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.
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 / aliquotvBC e vAliq. O valor (vISSQN) é calculado.city_codecMunFG: código IBGE do município do fato gerador.service_list_itemcListServ, da LC 116/03. Aceito com ou sem ponto; o schema exige NN.NN e a API normaliza.exigibilityindISS: 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_amountvISSRet, quando o ISS é retido pelo tomador.services.competence_dateISSQNtot/dCompet. Sem ele a API usa a data de emissão.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.
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.
recebe a mercadoria com o tributo já retido
taxes.pis.code e taxes.cofins.code = 05retém e declara o valor
PISST / COFINSST{
"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.
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.
gDifvICMSOp, campo explícitovICMSDifvDif, um por componentevICMS, campo explícitovIBSUF/vCBS, já líquidoICMSTot/vICMS recebe o devido; o diferido não tem totalIBSCBSTot tem vDif próprio, separado do devidoind_gDif do CSTICMS 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.percentagepDif. Obrigatório no grupo, de 0 a 100.deferral.fcp_percentagepFCPDif: o FCP tem diferimento próprio, com percentual independente. Exige fcp_aliquot.base_reductionbase_reduction_benefit_codecBenefRBC, o código de benefício da redução — campo próprio do CST 51, distinto do tax_benefit_code do item.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.
valuevICMSDeson). Obrigatório, maior que zero.reasonmotDesICMS). Obrigatório, e a lista aceita depende do CST — ver tabela abaixo.deducts_from_totalindDeduzDeson). 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
}
}
}
}
]
}
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.
reason)20 redução de base30 isenta com ST40, 41, 5070, 90
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.
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:
aliquotinterstate_aliquot4, 7 ou 12. Obrigatório — outros valores são recusados no build.fcp_aliquotbasefcp_base{
"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.
icms_destination não vier, a nota é gerada sem o grupo e a resposta traz um aviso em
build_warnings — nada é inventado silenciosamente.
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:
valuepercentage{
"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.percentagedet/impostoDevol/pDevol — percentual com duas casas, de 0 a 100.items[].returned_ipi.valuedet/impostoDevol/IPI/vIPIDevol — valor por item.items[].returned_ipi.valuetotal/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).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.
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 }
}
]
}
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_rateICMSSN101/pCredSN ou ICMSSN900/pCredSN — alíquota do crédito, com 4 casas decimais.credit_rateICMSSN101/vCredICMSSN ou ICMSSN900/vCredICMSSN — valor do crédito, calculado pela API.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
billing.invoice.numbercobr/fat/nFatbilling.invoice.original_amountcobr/fat/vOrigbilling.invoice.discount_amountcobr/fat/vDescbilling.invoice.net_amountcobr/fat/vLiqbilling.installments[].numbercobr/dup/nDupbilling.installments[].due_datecobr/dup/dVencbilling.installments[].amountcobr/dup/vDuppayments[].indicatorpag/detPag/indPagpayments[].methodpag/detPag/tPagpayments[].paid_atpag/detPag/dPag, data do pagamento.payments[].processed_document / processed_stateCNPJPag e UFPag: onde o pagamento foi processado, quando diferente do emitente. Andam em par — o leiaute traz os dois no mesmo grupo.payments[].cardintegration (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[].descriptionpag/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[].amountpag/detPag/vPagExemplo 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
}
]
}
}
}'
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.
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.modalitytransp/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.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.
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.integrationtpIntegra, 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.authorizationcAut, 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_documentCNPJ — os 14 dígitos da credenciadora ou do PSP.card.brandtBand, 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_documentCNPJReceb — os 14 dígitos do estabelecimento que recebeu o pagamento, quando diferente do emitente.card.terminal_ididTermPag, 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.
vTroco não aparece (ou aparece como 0.00 quando já havia algum meio de pagamento diferente de 90).pag/vTroco automaticamente.O troco (...) precisa estar coberto por pagamento(s) em dinheiro (method 01).A soma de payments e menor que o valor total da NF-e.payload.billing presentenet_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.
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
marketplace e presence_indicator em 2, 3, 4 ou 9ide/indIntermed = 1 mais o grupo infIntermed.marketplace e presence_indicator em 2, 3, 4 ou 9ide/indIntermed = 0 (operação sem intermediador), sem infIntermed.marketplace e presence_indicator em 0, 1 ou 5indIntermed não é gravada.marketplace e presence_indicator em 0, 1 ou 5marketplace.intermediary_documentinfIntermed/CNPJ — CNPJ do intermediador; obrigatório e validado (14 dígitos).marketplace.intermediary_idinfIntermed/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.
https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/build
(Global - Recomendado)
Build opcional
{"sign": false}
sign=falsexml_unsigned, sem certificado e sem contato com a SEFAZ. É o padrão quando sign é omitido.sign=truexml_signed usando o A1 ativo, mas ainda não transmite.action=authorize monta e assina automaticamente antes da transmissão.built_signeddispatch.https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/build
(Hierárquico)
https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/dispatch
(Global - Recomendado)
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"
}
}
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.
https://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/cancel
(Global - Recomendado)
https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/cancel
(Hierárquico)
{
"reason": "Cancelamento por erro operacional na emissao."
}
reasonhttps://fiscal.contora.com.br/api/v1/nfe/drafts/{document}/corrections
(Global - Recomendado)
https://fiscal.contora.com.br/api/v1/companies/{company}/nfe/drafts/{document}/corrections
(Hierárquico)
{
"text": "Corrigir informacao complementar sem alterar valores fiscais."
}
textGET /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
- Criar o rascunho com o payload completo.
- Executar o build assinado se quiser revisar XML antes do envio.
- Disparar
action=authorize. - Acompanhar por
/statusou webhook. - Quando autorizado, baixar XML e DANFE.
- Se houver rejeição, revisar o retorno traduzido antes de reenviar.
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.Disponibilidade atual da v1
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/synchas_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/receivedhas_full_xml e manifest_type=none (o que ainda falta manifestar).GET /nfe/received/{id}GET /nfe/received/{id}/xmlprocNFe). Se ainda só houver resumo, a API tenta buscar na hora; quando ele depende da manifestação, responde 409.POST /nfe/received/{id}/manifestciencia (210210), confirmacao (210200), desconhecimento (210220) e nao_realizada (210240, com justificativa de no mínimo 15 caracteres).nfe.received, o gancho natural para um
monitor de entradas sem polling constante.