engineAPIengineAPI
// guias

Guia: Emitir NFe

Guia completo para emitir uma Nota Fiscal Eletrônica (NFe) via engineAPI. Todos os campos, regimes tributários e tratamento de erros.

Emitir NFe

Emita uma Nota Fiscal Eletrônica (NFe, modelo 55) e transmita para a SEFAZ em uma única chamada REST.

Endpoint: POST https://api.engineapi.com.br/v1/nfe

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: NFe. Para navegar por endpoint em vez de por documento, veja a Referência por Endpoint.

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.

Sua Aplicaçãoseu backendengineAPImotor fiscalSEFAZórgão fiscalPOST /nfe/emitirpayload JSON da NF-eValida payloadAssina XML com cert. A1Transmite NF-eXML assinado via SOAPProtocolo de autorizaçãonProt + chave acesso200 OK { status: AUTHORIZED }accessKey + XML + PDFWebhook invoice.authorized123456

Pré-requisitos

·

Conta criada

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

·

Empresa cadastrada

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

·

Certificado digital enviado

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

·

Ambiente definido

Todo emissor nasce em homologação (ambienteFiscal: 2). Ver Sandbox 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 NFe/NFCe. 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

bash
curl -X POST https://api.engineapi.com.br/v1/nfe \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "naturezaOperacao": "VENDA DE MERCADORIA",
    "idDest": 1,
    "indFinal": 1,
    "destinatario": {
      "cnpjCpf": "99888777000100",
      "nome": "Cliente Exemplo SA",
      "endereco": {
        "logradouro": "Av Goiás",
        "numero": "500",
        "bairro": "Centro",
        "codigoMunicipio": "5208707",
        "municipio": "Goiânia",
        "uf": "GO",
        "cep": "74063010"
      },
      "indicadorIE": 9
    },
    "items": [{
      "codigo": "PROD001",
      "descricao": "Camiseta Algodão P",
      "ncm": "61091000",
      "cfop": "5102",
      "unidade": "UN",
      "quantidade": 2,
      "valorUnitario": 59.90,
      "icms": {
        "origem": 0,
        "csosn": "102"
      }
    }],
    "pagamentos": [{ "forma": "01", "valor": 119.80 }]
  }'

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:


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 Rejeições). Campo aceito e ignorado em silêncio não existe nesta API.


Campos de Referência

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

Raiz

CampoTipoObrigatórioDescrição
naturezaOperacaostringNãoEx: "VENDA DE MERCADORIA"
serie/numero/tpNF/idDest/indFinal/indPres/finNFenúmeroNãoCampos de identificação (ide), todos opcionais
destinatarioobjetoSimDados do destinatário
itemsarraySim (mín. 1)Não é itens
transporteobjetoNãoDados de transporte (modFrete obrigatório se enviado)
pagamentosarraySim (mín. 1)Não é pagamento singular
troconúmeroNãoTroco (NFCe/venda a consumidor)
informacoesComplementares/informacoesFiscostringNãoInformações adicionais
resolverTributacaobooleanNãoAtiva a emissão assistida (ver acima)

Destinatário

CampoTipoObrigatórioDescrição
cnpjCpfstring (11–14 dígitos)SimNão é cnpj/cpf separados
nomestringSimRazão social ou nome
iestringNãoNão é inscricaoEstadual. Obrigatória (e validada pela SEFAZ contra o CNPJ) quando indicadorIE: 1
indicadorIEnúmeroNão1 = contribuinte (exige ie) · 2 = isento · 9 = não contribuinte
emailstringNãoN/A
endereco.*objetoSimEndereço completo (todos os subcampos obrigatórios exceto complemento)

Item

CampoTipoObrigatórioDescrição
codigostringSimCódigo interno do produto
descricaostringSimDescrição do produto
ncmstring (8 dígitos exatos)SimNomenclatura Comum do Mercosul
cfopstringSimVer CFOP
unidadestringSimUN, KG, MT, CX, etc.
quantidadenúmero (min 0.0001)SimQuantidade
valorUnitarionúmero (min 0.01)SimValor por unidade em R$
valorTotalnúmeroNãoCalculado se ausente
ceststring (7 dígitos)NãoCódigo Especificador da ST, sem pontuação (422 CEST_INVALIDO se divergir)
icms/ibsCbsobjetoNãoDados tributários do item (obrigatórios de fato só sem resolverTributacao)
pis/cofinsobjetoNãocst + baseCalculo/aliquota/valor, transmitidos como informados (ver acima)
ipiobjetoNãoSó NF-e. cst + baseCalculo/aliquota/valor + cEnq opcional. Compõe o total da nota

Ciclo de vida da NFe

mermaid
stateDiagram-v2
    [*] --> PROCESSING : POST /v1/nfe
    PROCESSING --> AUTHORIZED : SEFAZ aprova
    PROCESSING --> REJECTED : SEFAZ rejeita (400 com erros[])
    AUTHORIZED --> CANCELED : POST /v1/nfe/{idOuChave}/cancelar (até 24h)
    CANCELED --> [*]
    AUTHORIZED --> [*] : XML + PDF disponíveis

    AUTHORIZED : ✅ AUTHORIZED
    REJECTED : ❌ REJECTED
    PROCESSING : ⏳ PROCESSING
    CANCELED : 🚫 CANCELED

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:

bash
curl https://api.engineapi.com.br/v1/nfe/sefaz-status/GO \
  -H "Authorization: Bearer SEU_TOKEN"
json
{
  "data": {
    "uf": "GO",
    "status": "UNKNOWN",
    "message": "Serviço em Operação",
    "responseTimeMs": 245,
    "checkedAt": "2026-07-20T18:00:00.000Z",
    "cStat": 107
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-07-20T18:00:00.000Z"
  }
}

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

json
{
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "status": "AUTHORIZED",
    "accessKey": "35260211222333000181550010000000011000000019",
    "protocol": "135260000001234",
    "number": 1,
    "series": 1,
    "model": "55",
    "amount": "119.8",
    "destCNPJ": "99888777000100",
    "destName": "Cliente Exemplo SA",
    "createdAt": "2026-07-06T12:00:00.000Z",
    "updatedAt": "2026-07-06T12:00:01.000Z",
    "downloads": {
      "xml": "/v1/nfe/xml/35260211222333000181550010000000011000000019",
      "pdf": "/v1/nfe/pdf/35260211222333000181550010000000011000000019"
    }
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-07-06T12:00:00.000Z"
  }
}

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 Decimal.js interno serializado ({"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.

Status persistido (Invoice.status)SignificadoAção
AUTHORIZEDAprovada pela SEFAZNenhuma. Nota válida
REJECTEDRejeitada (resposta HTTP é 400, ver abaixo)Corrija e reenvie com nova Idempotency-Key
CANCELEDCanceladaNota cancelada

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.

bash
curl -X POST https://api.engineapi.com.br/v1/nfe/batch \
  -H "x-api-key: ek_live_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "notas": [
      {
        "destinatario": {
          "cnpjCpf": "99888777000100",
          "nome": "Cliente Exemplo SA",
          "endereco": {
            "logradouro": "Av. Paulista",
            "numero": "1000",
            "bairro": "Bela Vista",
            "codigoMunicipio": "3550308",
            "municipio": "São Paulo",
            "uf": "SP",
            "cep": "01310100"
          }
        },
        "items": [
          {
            "codigo": "SKU-001",
            "descricao": "Camiseta Algodão",
            "ncm": "61091000",
            "cfop": "6102",
            "unidade": "UN",
            "quantidade": 2,
            "valorUnitario": 59.9,
            "icms": { "csosn": "102" }
          }
        ],
        "pagamentos": [{ "forma": "01", "valor": 119.8 }]
      }
    ]
  }'

Resposta 201: confirma o enfileiramento, não a autorização:

json
{
  "data": { "queued": 1, "ids": ["4b1f0a6e-..."] },
  "meta": { "requestId": "req_abc123", "timestamp": "2026-07-31T12:00:00.000Z" }
}

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.

RegraComportamento
Máximo de notas por chamada50. Acima disso: 422 com code: LOTE_ACIMA_DO_LIMITE, e nenhuma nota do envio é enfileirada
Máximo de tamanho do corpo100 kB (limite global da API, não só do lote). Acima disso: 413 com code: PAYLOAD_TOO_LARGE e details.limiteBytes/details.tamanhoBytes
Validação de cada notaIdêntica ao POST /v1/nfe. Qualquer nota inválida reprova o lote inteiro com 400
Onde o erro apontaerrors[].field traz o índice da nota, ex.: notas.2.items.0.ncm
Ordem de checagemForma antes de tamanho de lote: um envio com 60 notas em que uma é inválida recebe 400 (validação), não 422. Corrija a nota e o 422 do limite aparece no reenvio
EmissorUm lote emite por um emissor: use issuerId na raiz do corpo. issuerId dentro de uma nota divergindo do lote → 422 ISSUER_DIVERGENTE_NO_LOTE
numero explícitoSe você mandar numero nas notas, a API não checa colisão entre as notas do mesmo lote. Duas notas com o mesmo numero/série: a primeira emite, a segunda falha na SEFAZ por duplicidade e aparece como FAILED em GET /v1/nfe/queue. Omita numero para deixar a numeração automática cuidar disso
Campos não previstos no contratoSão descartados antes do enfileiramento (mesmo comportamento do endpoint singular)

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

bash
curl https://api.engineapi.com.br/v1/nfe/queue?status=FAILED \
  -H "x-api-key: ek_live_sua_chave"

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 NFe autorizada em até 24 horas após a autorização. Prazo geral, também regulamentado por UF.

bash
curl -X POST https://api.engineapi.com.br/v1/nfe/{idOuChave}/cancelar \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "justificativa": "Erro nos dados do destinatário informado na venda" }'

idOuChave aceita o id (UUID) da NFe 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 NFCe (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 NFCe 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)

bash
curl -X POST https://api.engineapi.com.br/v1/nfe/{accessKey}/carta-correcao \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "correcao": "Corrijo o endereço do destinatário para Av Brasil, 500" }'

Só permitida em NFe 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.

bash
curl -X POST https://api.engineapi.com.br/v1/nfe/inutilizar \
  -H "x-api-key: SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serie": 1,
    "numInicial": 45,
    "numFinal": 45,
    "justificativa": "Numero pulado por falha no sistema de emissao"
  }'
CampoTipoObrigatórioDescrição
serienúmero (ou string só-dígitos)SimSérie da faixa a inutilizar (inteiro 0–999)
numInicialnúmero (ou string só-dígitos)SimPrimeiro número da faixa (inteiro 1–999999999)
numFinalnúmero (ou string só-dígitos)SimÚltimo número da faixa (inteiro 1–999999999), precisa ser maior ou igual a numInicial, e a faixa (numFinal - numInicial + 1) não pode passar de 10.000 números por chamada
justificativastringSimEntre 15 e 255 caracteres (limite do campo xJust do leiaute)
issuerIdstring (UUID)NãoSó necessário com 2+ emissores cadastrados
anonúmeroNãoAno-calendário da numeração inutilizada, entre anoCorrente - 5 e anoCorrente. Default: ano corrente. Use quando o número foi pulado num ano e a inutilização só está sendo feita no ano seguinte (ex.: gap aberto em dezembro, inutilizado em janeiro). Sem isso a inutilização seria registrada no ano ERRADO

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:

json
{
  "data": {
    "success": true,
    "protocol": "135260000009876",
    "message": "Inutilização série 1 nº 45 a 45 homologada",
    "xml": "<inutNFe>...</inutNFe>"
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-07-20T12:00:00.000Z"
  }
}

O campo xml (XML de retorno da SEFAZ) é servido quando a ACBrLib 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:

json
{
  "data": {
    "success": false,
    "cStat": 563,
    "xMotivo": "Rejeicao: Ja existe pedido de Inutilizacao com a mesma faixa de numeracao",
    "erros": [
      { "codigo": "563", "descricao": "Rejeicao: Ja existe pedido de Inutilizacao com a mesma faixa de numeracao" }
    ],
    "message": "Rejeicao: Ja existe pedido de Inutilizacao com a mesma faixa de numeracao"
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-07-20T12:00:00.000Z"
  }
}

Ramifique pelo campo success, não pelo status HTTP: rejeição da SEFAZ na inutilização não é 400. Ver também Erros e Rejeições.

Outros status possíveis (nenhum é "sempre 200": só o desfecho da SEFAZ é):

StatusQuandoCorpo
400serie/numInicial/numFinal/justificativa inválidos (shape, tipo ou numFinal < numInicial), ou 2+ emissores cadastrados sem issuerIdErro de validação padrão (RFC 7807)
403Partner sem nenhum emissor cadastradoErro padrão
404issuerId informado não pertence ao partner autenticadoErro padrão
422CERTIFICADO_AUSENTE (emissor sem certificado A1 instalado) ou FAIXA_INUTILIZACAO_MUITO_GRANDE (mais de 10.000 números na faixa){ code, message, details }
500Timeout do worker que fala com a ACBrLib/SEFAZ (120s): a inutilização pode ou não ter sido homologada do lado da SEFAZ, a resposta não chegou a tempoErro genérico

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

json
{
  "error": {
    "type": "https://engineapi.com.br/errors/BAD_REQUEST",
    "title": "Requisição Inválida",
    "status": 400,
    "detail": "SEFAZ rejeitou (CStat=696): Rejeicao: Obrigatoria as informacoes do responsavel tecnico pela emissao do DF-e",
    "erros": [
      {
        "codigo": "696",
        "descricao": "Rejeicao: Obrigatoria as informacoes do responsavel tecnico pela emissao do DF-e"
      }
    ],
    "instance": "/v1/nfe",
    "requestId": "req_uofirusmuamw",
    "timestamp": "2026-07-05T11:06:14.453Z"
  }
}

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

json
{
  "id": "9f1c2b3a-...-uuid",
  "type": "invoice.authorized",
  "timestamp": "2026-04-26T18:30:00.000Z",
  "data": {
    "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "model": "55",
    "number": 1,
    "series": 1,
    "accessKey": "35260211222333000181550010000000011000000019",
    "status": "AUTHORIZED",
    "amount": 119.80
  }
}

Configure seus webhooks em Dashboard → Configurações → Webhooks ou via guia de webhooks.


Próximos passos