Skip to main content
Emita uma Nota Fiscal Eletrônica (NF-e, modelo 55) e transmita para a SEFAZ em uma única chamada REST. Endpoint: POST https://api.engineapi.com.br/v1/nfe

Situação indeterminada

Uma resposta perdida depois da transmissão não significa rejeição. Consulte GET /v1/nfe?situacao=indeterminada&page=1&limit=20 para listar NF-e com chave de acesso sem desfecho terminal. O parâmetro não pode ser combinado com status; cada item traz situacao, o status persistido, updatedAt, proximaVerificacaoEm e o motivo.
Quais campos enviar? Este guia cobre os campos mais usados. A lista completa, navegável por grupo (Identificação, Destinatário, Itens, Impostos, Transporte, Pagamento…) e gerada direto do contrato real, está no Catálogo de campos: NF-e. Para navegar por endpoint em vez de por documento, veja a Referência da API.
Este é o guia completo (todos os campos, regimes tributários, tratamento de erros). Se você só quer o checklist rápido antes da 1ª emissão, veja Primeira Emissão.
Como a emissão flui do cadastro à autorização (ou rejeição) da SEFAZ:

Pré-requisitos

1

Conta criada

Obtenha seu token via POST /v1/auth/login ou uma API Key (ek_live_/ek_test_) no Dashboard.
2

Empresa cadastrada

Cadastre o CNPJ emissor via POST /v1/companies. Guarde o id retornado.
3

Certificado digital enviado

Faça upload do .pfx via POST /v1/companies/{id}/certificate (campo multipart file). Sem certificado, a emissão falha.
4

Ambiente definido

Todo emissor nasce em homologação (ambienteFiscal: 2). Ver Ambiente de testes para o estado real de ir pra produção.
Notas em homologação (ambienteFiscal: 2) são transmitidas para o SEFAZ de teste e não têm validade fiscal. Use para testar sem risco.
Emissor (issuerId) é opcional no payload de NF-e/NFC-e. Com um único emissor cadastrado, pode omitir (a API usa o seu emissor). Com dois ou mais, informe issuerId (UUID) para escolher o CNPJ; sem ele a API responde 400. Ver Autenticação.

Exemplos de Request

Vendendo para outra empresa (contribuinte de ICMS)? Informe indicadorIE: 1 e a ie real do destinatário: a SEFAZ valida o vínculo IE×CNPJ no cadastro dela. Sem a IE a nota é rejeitada com cStat 728; com IE que não pertence ao CNPJ, cStat 234. Destinatário isento usa indicadorIE: 2 (sem ie); consumidor que não é contribuinte (o exemplo acima), indicadorIE: 9.

ICMS por Regime Tributário

O campo icms do item muda conforme o regime da empresa emissora:
Informe origem e csosn.
O emissor crt: 3 não informa cst à mão: icms.cst no payload é recusado com 422 CST_NAO_SUPORTADO_NFE (o leiaute exige a modalidade da base de cálculo, campo fora deste contrato). O caminho que autoriza é a emissão assistida: "resolverTributacao": true no corpo da requisição, com o item trazendo só icms.origem. O motor calcula CST, base, alíquota e valor a partir de NCM, CFOP, UF do emissor e origem da mercadoria:
Detalhe completo do cenário (pré-requisitos, resposta, XML, erros e limitações) em Regime Normal.
Com "resolverTributacao": true no corpo da requisição, itens sem tributação manual (sem icms.csosn e sem ibsCbs) recebem CSOSN e o grupo IBS/CBS (Reforma Tributária) resolvidos automaticamente. Requer feature de plano + Issuer.fiscalBrainEnabled; sem isso, 403. Item não-resolvível → 422 com {index, motivo} por item e nada é emitido. icms.origem nunca é opinado pelo Cérebro: sempre vem do seu payload (ausente = 0, nacional). Este 422 é diferente da rejeição SEFAZ (sempre 400); ver Erros e respostas.

PIS, COFINS e IPI

O motor é passthrough: o que você informa é o que vai no documento, nada é calculado aqui. Quando uma combinação não pode ser escrita no documento com segurança, a emissão recusa com 422 antes de consumir número fiscal (ver Erros e respostas). Campo aceito e ignorado em silêncio não existe nesta API.
Códigos fora dessas faixas (ex.: 57, 68, 76) não existem na tabela do leiaute 4.00 e são recusados com 422. CST de 1 dígito é normalizado ("1""01").Omitir pis/cofins mantém o comportamento padrão (CST 99 com valores zerados). Os valores informados também somam em vPIS/vCOFINS no total da nota.
cEnq (Código de Enquadramento Legal) é opcional: ausente, transmitimos "999" (demais casos), o mesmo default do leiaute.
O IPI compõe o total da nota. Com IPI informado, o documento sai com vNF = produtos + IPI, e a soma de pagamentos[].valor precisa fechar com esse total (senão 422 PAGAMENTO_DIVERGENTE). O amount devolvido pela API é sempre o vNF transmitido.
A NFC-e (modelo 65) não tem grupo de IPI no leiaute: enviar ipi numa NFC-e devolve 422 IPI_NAO_SUPORTADO em vez de emitir sem o imposto.
O leiaute da NT 2025.002 separa dois números por componente (IBS-UF, IBS-Município e CBS):
  • a alíquota nominal, que é a que o documento grava em pIBSUF/pIBSMun/pCBS;
  • a redução do cClassTrib mais a alíquota efetiva, que vão no grupo gRed do documento.
Se o produto tem redução de alíquota (alimento, medicamento, cesta básica e a maior parte dos NCMs com anexo), informe os três campos:
pNominal e pRedAliq andam juntos, e p precisa ser igual a pNominal × (1 − pRedAliq/100). Fora disso, a emissão recusa antes de consumir número fiscal.Produto sem redução continua exatamente como antes: só p (nominal e efetiva são o mesmo número), e o documento sai sem gRed.
Informar só a alíquota efetiva em p, num item que tem redução, faz a SEFAZ rejeitar com cStat 1026 (“Alíquota do IBS da UF inválida”): ela compara o campo contra a alíquota nominal vigente. Informar a nominal sem o gRed cai no outro lado: cStat 1033 (“o CST obriga informação de redução”). Os três campos juntos são o caminho que autoriza.
Com resolverTributacao: true você não precisa preencher nada disso: o Cérebro Fiscal resolve nominal, redução e efetiva do NCM sozinho.
cest tem 7 dígitos (ex.: "0100100"). Máscara é aceita e normalizada ("01.001.00"0100100); o que não fecha 7 dígitos → 422 CEST_INVALIDO.Os campos ANTIGOS de ICMS-ST (icms.baseCalculoST, aliquotaST, valorST) nunca chegaram ao documento e continuam sem efeito: informá-los com valor diferente de zero devolve 422 ICMS_ST_NAO_SUPORTADO (tudo zero continua emitindo, o documento sai igual). O vocabulário que vale é o do leiaute, e ele já emite na NF-e:
  • Simples Nacional pleno (crt: 1): icms.csosn "201", "202" ou "203", com modBCST, vBCST, pICMSST e vICMSST; no "201", também pCredSN e vCredICMSSN (o crédito do artigo 23 da LC 123/2006, que sai da sua apuração).
  • Regime Normal e Simples com excesso de sublimite (crt: 3/crt: 2): icms.cst "10", "30" ou "70" com os mesmos campos de ST, mais o ICMS próprio nos códigos "10" e "70".
Na NFC-e a substituição cobrada na operação continua fora, nos dois regimes: emita uma NF-e para documentar essa operação.No Regime Normal (resolverTributacao: true) a recusa acontece antes disso: basta o NCM do item estar arrolado no CEST (Convênio ICMS 142/2018) para o motor devolver 422 TRIBUTACAO_NAO_RESOLVIDA, mesmo sem nenhum campo de ST no payload. Ver Regime Normal para a lista de segmentos afetados.

Campos de Referência

Nomes de campo divergentes deste contrato são rejeitados com 400.

Raiz

Destinatário

Item

Documento referenciado

referenciadas é um array na raiz do corpo. Cada entrada tem chaveAcesso (44 dígitos, a chave de acesso da NF-e/NFC-e original), obrigatória em finNFe: 2 (complementar), 3 (ajuste) e 4 (devolução):
finNFe: 3 (ajuste) e 4 (devolução) exigem também a forma de pagamento "90" (Sem Pagamento): pagamentos: [{ "forma": "90", "valor": 0 }], única entrada e sem troco. A forma "90" descreve uma operação sem contraprestação (remessa, bonificação, comodato, devolução), e a conferência de soma dos pagamentos contra o total da nota não se aplica a ela. Outra forma junto de "90", ou troco maior que zero, recusa com 422 SEM_PAGAMENTO_INVALIDO; na NFC-e a forma "90" é vedada (422 SEM_PAGAMENTO_VEDADO_NFCE). Ver Erros e Rejeições.
Na devolução (finNFe: 4), cada item precisa informar documentoReferenciado.nItem (número do item na nota original, NT 2025.002-RTC VC03). A chave pode vir de referenciadas[0].chaveAcesso ou de documentoReferenciado.chaveAcesso. Uma devolução completa combina finNFe: 4, referenciadas apontando a nota original e a forma de pagamento "90":
Escopo de referenciadas hoje: só a chave de acesso (refNFe do leiaute), aceita para NF-e (55), NFC-e (65) e CF-e SAT (59). Nota de papel, produtor rural, CT-e e cupom de ECF ainda não são suportados. Ver Cobertura fiscal e Erros e Rejeições.

Ciclo de vida da NF-e

Os estados possíveis de uma nota e as transições entre eles:

Contingência SVC

Quando a SEFAZ do estado do emissor está fora do ar, a engineAPI reroteia automaticamente a transmissão para a SEFAZ Virtual de Contingência (SVC): você não aciona nada, não muda o payload, não escolhe rota. A mesma chamada POST /v1/nfe segue funcionando; só muda, por baixo, qual webservice recebe a nota.
Zero campo novo no payload. Não existe (nem precisa existir) um campo do tipo contingencia/forcarContingencia no corpo do POST /v1/nfe: a decisão é 100% automática, calculada a cada emissão a partir do status real da SEFAZ da UF do emissor.
Como a engineAPI decide, a cada POST /v1/nfe:
  1. Consulta o status da SEFAZ da UF do emissor (cache de 5 minutos).
  2. UP: transmite normal, nada muda.
  3. DOWN: reroteia para a SVC-AN ou a SVC-RS (o mapa por UF é definido pelo fisco; a engineAPI escolhe a rota certa automaticamente).
  4. Quando a SEFAZ da UF volta a ficar UP, a próxima emissão já transmite normal de novo, sem nenhuma ação sua.
A engineAPI implementa contingência via SVC (SVC-AN/SVC-RS), não via EPEC. Se algum dia você inspecionar o XML autorizado, o jeito de confirmar que uma nota saiu em contingência é o campo tpEmis da identificação: normal usa tpEmis = 1, SVC-AN usa tpEmis = 6, SVC-RS usa tpEmis = 7. A engineAPI não expõe um status separado tipo CONTINGENCY no Invoice: a nota chega a AUTHORIZED (ou REJECTED) do mesmo jeito, só que autorizada pela SVC.

Consultar o status da SEFAZ

Para monitorar disponibilidade antes de decidir se vale a pena reagendar um lote, use:
Esta rota pública não usa o certificado de nenhum emissor, por isso status sempre vem UNKNOWN nela (nunca UP/DOWN): é desenho deliberado, sem uma consulta real (cert-backed) a engineAPI nunca fabrica “no ar”/“fora do ar”. message e cStat continuam vindo do provider (ex.: cStat 107 = Serviço em Operação); só status fica UNKNOWN aqui. A decisão UP/DOWN que de fato aciona o reroteamento pra SVC roda por dentro, com o certificado do SEU emissor, no momento de cada emissão, e não é o que esta rota devolve. Use-a para inspecionar message/cStat, não para prever se a próxima emissão vai sair via SVC.
GET /v1/nfe/sefaz-status (sem UF) devolve o mesmo formato para as 27 UFs de uma vez, com o mesmo cache de 5 minutos. Ver também SEFAZ e Webservices.

Response de sucesso

Mesmo contrato de resposta nos dois caminhos de emissão (síncrono e fila), envelopado em { data, meta }:
Sem issuer/customer embutidos (o parceiro já conhece os dois: foi ele quem cadastrou o emissor e enviou o destinatário no payload) e sem xmlPath/pdfPath (caminho de arquivo interno do container). amount é string decimal ("119.8", o Decimal do banco serializa sem zero à direita, mesmo formato do webhook), nunca number cru (evita imprecisão de ponto flutuante) nem o formato serializado do decimal interno ({"s":1,"e":2,"d":[...]}", bug já corrigido). downloads.xml/downloads.pdf substituem os caminhos internos: são os endpoints reais de download (GET /v1/nfe/xml/{accessKey}, GET /v1/nfe/pdf/{accessKey}).
DANFE, mudança de contrato (30/07/2026): GET /v1/nfe/pdf/{accessKey} devolvia text/html. Agora devolve application/pdf: o DANFE oficial gerado a partir do XML autorizado da nota (ambiente, CST/CSOSN e informações complementares vêm do documento). Sem XML autorizado armazenado no ambiente, a resposta é 409 com code: DANFE_INDISPONIVEL, nunca um documento aproximado.

Emissão em lote

POST /v1/nfe/batch enfileira várias notas de uma vez. Cada item de notas[] segue exatamente o mesmo contrato de POST /v1/nfe: mesmo schema, mesmos campos obrigatórios, mesmas recusas. Não existe um “formato de lote” separado.
Resposta 201: confirma o enfileiramento, não a autorização:

Regras do lote

São dois limites independentes: o de notas e o de tamanho do corpo. O que morder primeiro depende de quantos itens suas notas têm.
Quantas notas cabem de verdade? Uma nota com 1 item ocupa ~1,3 kB de JSON; com 10 itens, ~3,5 kB. Na prática: 50 notas de 1 item cabem folgado (~64 kB), mas 50 notas de 10 itens somam ~175 kB e batem no 413. Se você emite notas com muitos itens, use lotes menores; o corpo do 413 diz o tamanho enviado e o limite.
Mudança de contrato (31/07/2026): até esta versão o lote não validava nada: um payload que o POST /v1/nfe recusaria era aceito e só quebrava lá na frente, dentro da fila. Agora o lote recusa na porta, com o mesmo 400/422 do endpoint singular. Se a sua integração de lote enviava algo que o singular já recusava, ela passa a receber a recusa; o corpo do erro diz exatamente qual nota e qual campo.

Consultando o desfecho

O 201 é só o aceite na fila. O desfecho fiscal de cada nota vem de GET /v1/nfe/queue (filtrável por status: PENDING, PROCESSING, DONE, FAILED):
Item que falhou traz lastError (texto) e resultData.error com o código estruturado (code): o mesmo código que o endpoint singular devolveria para o mesmo payload. Recusa determinística (ex.: CST_REGIME_INCOMPATIVEL, PAGAMENTO_DIVERGENTE, CADASTRO_EMISSOR_INCOMPLETO, EMISSOR_INEXISTENTE) vai direto para FAILED, sem retry: repetir não muda o desfecho, e nenhum número da sequência fiscal é consumido. Corrija o payload e reenvie.
EMISSOR_INEXISTENTE cobre o caso de o emissor ser removido entre o enfileiramento e o processamento: a nota falha na hora, sem gastar tentativas e sem consumir número. Reenvie o lote apontando para um emissor válido.

Cancelamento

Cancele uma NF-e autorizada em até 24 horas após a autorização. Prazo geral, também regulamentado por UF.
idOuChave aceita o id (UUID) da NF-e ou a chave de acesso (44 dígitos). O campo do corpo é justificativa (mínimo 15 caracteres); a rota aceita tanto JWT do painel quanto x-api-key de integração.
O prazo da NFC-e (modelo 65) é muito menor: 30 minutos, padrão nacional por UF. Não assuma o mesmo prazo para os dois modelos; veja o guia de NFC-e para os detalhes.
Se o cancelamento for solicitado fora do prazo, a SEFAZ rejeita (cStat 501) e a engineAPI repassa o desfecho verbatim no 400, mesmo formato descrito em Tratamento de Erros abaixo.
O DANFE de uma nota cancelada não traz carimbo de cancelamento. O PDF servido por GET /v1/nfe/pdf/{accessKey} é o documento gerado a partir do XML de autorização: ele não muda quando o cancelamento é homologado, porque o evento de cancelamento é um documento fiscal separado (o XML do evento, com o protocolo). Para provar que a nota foi cancelada, use o status do documento (CANCELED) ou o XML do evento, nunca a ausência de carimbo no DANFE.

Carta de Correção (CC-e)

Só permitida em NF-e com status AUTHORIZED; limite de 20 CC-e por nota. O corpo aceita correcao (mínimo 15 caracteres). Assim como o cancelamento, aceita JWT ou x-api-key.

Inutilização

Inutilize uma faixa de numeração que nunca será usada. O caso típico é uma nota rejeitada de forma definitiva, que consome o número mas nunca chega a AUTHORIZED: sem inutilizar, esse número fica um gap permanente na sequência fiscal.
O corpo é validado com mensagens de erro no padrão pt-BR do resto da API (ex.: “Justificativa deve ter no mínimo 15 caracteres (exigência SEFAZ)”). Os campos numéricos aceitam number ou string só-dígitos ("45"), mas rejeitam null, "", array e boolean com uma mensagem pt-BR acionável (nunca o “Invalid input” genérico) e nunca coagem silenciosamente pra 0. Quando a SEFAZ homologa a inutilização (cStat 102), a resposta vem HTTP 200 com success true:
O campo xml (XML de retorno da SEFAZ) é servido quando o motor fiscal o devolve: trate como opcional na sua integração, não como garantia contratual. protocol e message são os campos com prova de emissão real.
Quando a SEFAZ rejeita o pedido (qualquer cStat diferente de 102), a engineAPI NÃO traduz isso em 4xx: o desfecho vem no próprio corpo, ainda em HTTP 200, com success false:
Ramifique pelo campo success, não pelo status HTTP: rejeição da SEFAZ na inutilização não é 400. Ver também Erros e respostas.
Outros status possíveis (nenhum é “sempre 200”: só o desfecho da SEFAZ é):
O 500 de timeout é ambíguo de propósito (a SEFAZ pode ter homologado sem a resposta voltar a tempo). Nunca reenvie a mesma faixa sem cuidado: um reenvio comum pode colidir com uma inutilização que JÁ foi homologada (cStat 563, “já existe pedido”). Envie sempre com o header Idempotency-Key: <uuid> (suportado globalmente por todo POST/PUT/PATCH da API). Reenviar com a MESMA key repete o resultado já concluído (replay), sem reprocessar. Gere uma key nova só quando for de fato uma faixa diferente.
Mesmo motor por trás do POST /v1/nfce/inutilizar (modelo 65): o worker de emissão e o desfecho da SEFAZ são genéricos por modelo; só o endpoint muda.

Tratamento de Erros

Rejeição SEFAZ (400)

Quando a SEFAZ rejeita a nota (cStat de rejeição), a API responde HTTP 400 com o envelope de erro padrão (RFC 7807) carregando error.erros[], o cStat/xMotivo verbatim da SEFAZ, sem tradução:
Ramifique pelo error.erros[].codigo (o cStat da SEFAZ) e consulte a tabela oficial de rejeições da Fazenda. A nota fica com status REJECTED e o webhook invoice.rejected é disparado com o mesmo erros[]. Rejeição é um desfecho determinístico: reenviar com a mesma Idempotency-Key devolve a mesma rejeição (replay, sem retransmitir à SEFAZ). Corrija os dados e emita com uma key nova. Falhas de infraestrutura genuínas (timeout, indisponibilidade da SEFAZ) continuam respondendo 500 e podem ser retentadas com a mesma key. Certificado A1 ausente e cadastro do emissor incompleto (IE, endereço) não chegam a 500: a API valida ANTES de acionar o worker de emissão e responde 422 estruturado: ver Pré-voo do emissor.

Webhook após emissão

A engineAPI dispara automaticamente um evento invoice.authorized quando a SEFAZ aprova:
Configure seus webhooks em Dashboard → Configurações → Webhooks ou via guia de webhooks.

Veja também