engineAPIengineAPI
// comece aqui

Guia: Primeira Emissão

Guia completo para emitir seu primeiro documento fiscal. Todos os campos obrigatórios, erros comuns e melhores práticas.

Primeira Emissão

Este é o checklist rápido de pré-requisitos antes da 1ª emissão. Pra ver todos os campos, regimes tributários e tratamento de erros, veja o guia completo Emitir NF-e.

Antes de emitir sua primeira NFe em homologação (sandbox), você precisa ter três coisas prontas. Sem uma delas, a emissão vai falhar. Para o que muda ao ir pra produção, ver Sandbox.

Conta criada

Partner registrado e token JWT (ou API Key) em mãos

Empresa cadastrada

CNPJ emissor cadastrado via POST /v1/companies

Certificado enviado

Arquivo .pfx (A1) com senha válida

Não tem os três? Volte para o Quickstart e siga os passos.


Checklist de pré-requisitos

·

Credencial disponível

Você tem uma API Key (ek_live_/ek_test_, formato recomendado para integração server-to-server) ou um JWT de POST /v1/auth/login (data.access_token, fluxo de dashboard)? Ver Autenticação.

·

Empresa cadastrada

Você já fez POST /v1/companies? O id retornado é o issuerId, usado para escolher o emissor em qualquer módulo (ver aviso abaixo sobre obrigatoriedade por módulo).

·

Certificado enviado

Você já fez POST /v1/companies/{id}/certificate (campo multipart file, não certificate)? Se sim, a resposta veio sem erro de senha?

·

Ambiente correto para o teste

Todo emissor novo nasce em homologação (ambienteFiscal: 2). Não tente emitir em produção sem antes validar em homologação. Ver Sandbox para o estado real da promoção pra produção.

NFe e NFCe aceitam issuerId opcional no payload. Se você só tem uma empresa cadastrada, pode omitir; a API usa o seu emissor. Com dois ou mais emissores, informe issuerId (UUID) para escolher o CNPJ; sem ele a API responde 400. NFSe exige issuerId explícito no corpo da requisição.


Estrutura de uma NFe

Uma NFe é composta por 4 blocos no payload (o emissor não entra no body, é resolvido pela API Key/JWT):

font-mono text-sm bg-slate-800 text-[var(--eng-glow)] rounded px-1.5 py-0.5
POST /v1/nfe
├── 1. Identificação (naturezaOperacao, série, número, todos opcionais)
├── 2. Destinatário (cnpjCpf, nome, endereço)
├── 3. Itens (produtos, NCM, CFOP, impostos)
└── 4. Pagamentos (array, forma + valor)

Campos obrigatórios

Um payload com nomes de campo diferentes destes é rejeitado com 400 antes de chegar na SEFAZ.

Raiz

CampoTipoObrigatórioExemplo
naturezaOperacaostringNão"VENDA DE MERCADORIA"
destinatarioobjetoSimver abaixo
itemsarraySim (mín. 1)ver abaixo, não é itens
pagamentosarraySim (mín. 1)ver abaixo, não é pagamento singular
resolverTributacaobooleanNãotrue ativa a emissão assistida (Cérebro Fiscal, requer plano/feature habilitados)

Destinatário

CampoTipoObrigatórioExemplo
cnpjCpfstring (11 a 14 dígitos)Sim, não é cnpj/cpf"99888777000100"
nomestringSim"Cliente Exemplo SA"
iestringNão, não é inscricaoEstadual"1234567890"
indicadorIEnúmeroNão (opcional)1 = contribuinte, 9 = não contribuinte
endereco.logradourostringSim"Av Brasil"
endereco.numerostringSim"500"
endereco.bairrostringSim"Centro"
endereco.codigoMunicipiostringSim"3550308" (São Paulo)
endereco.municipiostringSim"São Paulo"
endereco.ufstring (2)Sim"SP"
endereco.cepstringSim"01001000"

Item (dentro de items[])

CampoTipoObrigatórioExemplo
codigostringSim"PROD001"
descricaostringSim"Produto Teste"
ncmstring (8 dígitos exatos)Sim"84713012"
cfopstringSim"5102" (venda interna)
unidadestringSim"UN"
quantidadenúmero (min 0.0001)Sim2
valorUnitarionúmero (min 0.01)Sim150.00
valorTotalnúmeroNão (calculado se ausente)300.00
icms/pis/cofins/ipi/ibsCbsobjetoNãover abaixo

Não há campo numero por item: o índice do array já identifica o item. O item não exige icms preenchido: sem resolverTributacao, os campos fiscais viajam como passthrough (o que você mandar é o que vai pro XML); com resolverTributacao: true, o Cérebro Fiscal completa icms.csosn/icms (Regime Normal)/ibsCbs ausentes.

ICMS simplificado (Simples Nacional)

json
"icms": {
  "origem": 0,
  "csosn": "400"
}

ICMS no Regime Normal (Lucro Real / Lucro Presumido)

O emissor crt: 3 não informa cst à mão: icms.cst é 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 é resolverTributacao: true: o motor calcula CST, base, alíquota e valor a partir de NCM, CFOP, UF e origem:

json
{
  "resolverTributacao": true,
  "items": [{
    "codigo": "P1",
    "descricao": "Farinha de Trigo Tipo 1 - 1kg",
    "ncm": "11029000",
    "cfop": "5102",
    "unidade": "UN",
    "quantidade": 2,
    "valorUnitario": 500,
    "icms": { "origem": 0 }
  }]
}

Detalhe completo do cenário (pré-requisitos, resposta, XML, erros e limitações) em Regime Normal.

Pagamentos (array, mínimo 1)

CampoTipoValores
formastring"01" Dinheiro, "03" Cartão crédito, "04" Cartão débito, "15" Boleto, "99" Outros
valornúmeroValor da parcela/forma
json
"pagamentos": [
  { "forma": "01", "valor": 100.00 }
]

Exemplo completo mínimo

bash
curl -X POST https://api.engineapi.com.br/v1/nfe \
  -H "x-api-key: ek_test_SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "naturezaOperacao": "VENDA DE MERCADORIA",
    "destinatario": {
      "cnpjCpf": "99888777000100",
      "nome": "Cliente Exemplo SA",
      "indicadorIE": 1,
      "endereco": {
        "logradouro": "Av Brasil",
        "numero": "500",
        "bairro": "Centro",
        "codigoMunicipio": "3550308",
        "municipio": "São Paulo",
        "uf": "SP",
        "cep": "01001000"
      }
    },
    "items": [{
      "codigo": "PROD001",
      "descricao": "Produto Teste",
      "ncm": "84713012",
      "cfop": "5102",
      "unidade": "UN",
      "quantidade": 1,
      "valorUnitario": 100.00,
      "icms": {
        "origem": 0,
        "csosn": "400"
      }
    }],
    "pagamentos": [
      { "forma": "01", "valor": 100.00 }
    ]
  }'

Resposta de sucesso

Mesmo contrato de resposta nos dois caminhos de emissão (síncrono e lote), envelopado por { data, meta }:

json
{
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "status": "AUTHORIZED",
    "accessKey": "35260211222333000181550010000000011000000019",
    "protocol": "135260000001234",
    "number": 1,
    "series": 1,
    "model": "55",
    "amount": "100",
    "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"
  }
}

data.status: "AUTHORIZED" (inglês, mesmo enum usado em GET /v1/nfe/{id} e no webhook invoice.authorized) está aprovada pela SEFAZ e tem validade fiscal (se o emissor está em produção: ambienteFiscal: 1). Não há issuer/customer embutidos, nem xml/xmlPath/pdfPath/invoiceId/message/success/invoice aninhado: o PDF/XML têm rotas próprias de download (downloads.xml/downloads.pdf apontam pra elas). amount é string decimal ("100", sem zeros à direita), nunca number cru nem o Decimal.js interno serializado.


Erros mais comuns nesta etapa

ErroCausaSolução
403 Nenhum emissor configuradoNenhuma empresa cadastrada para o partnerFaça POST /v1/companies primeiro
500/erro de certificado na emissãoNenhum .pfx enviado ou senha inválidaFaça POST /v1/companies/{id}/certificate com o campo file
400 com error.erros[] (cStat 539)Nota duplicada (mesmo número/série)Incremente o número da nota, ou deixe a API alocar automaticamente
400 com error.erros[] (cStat 225)Campo NCM ou CFOP inválidoVerifique a tabela de CFOP
400 Bad Request (validação)Campo obrigatório ausente ou nome de campo errado (ex.: itens em vez de items)Confira contra a tabela de campos acima
401 Token inválidoToken expirado (24h)Faça login novamente

Rejeição SEFAZ é sempre 400, nunca 422. O corpo carrega error.erros[] ({codigo, descricao} verbatim da SEFAZ). Veja o formato completo em Erros e Rejeições. O 422 existe na API, mas só para a emissão assistida (resolverTributacao: true) quando um campo fiscal não tem fonte para ser resolvido, não é o mesmo caso de rejeição SEFAZ.


Próximos passos