Skip to main content
A NFS-e é o documento fiscal para prestação de serviços. Diferente da NF-e, é autorizada pela prefeitura municipal (ABRASF) ou pela SEFIN/ADN (Padrão Nacional). Cada padrão tem seu próprio contrato. Endpoint base: https://api.engineapi.com.br/v1/nfse A emissão assina a DPS com o certificado A1 do emissor. O prontoPara.nfse do cadastro recusa antes de gastar nDPS, com nome próprio em faltando.nfse: certificado (422 CERTIFICADO_AUSENTE), certificadoVencido (422 CERTIFICADO_VENCIDO) ou certificadoOutroCnpj (422 CERTIFICADO_OUTRO_CNPJ, raiz diferente; filial com e-CNPJ da matriz não recusa). Emissor em sandbox não passa por essas checagens. NF-e e NFC-e seguem cobrando só a presença do A1.
Quais campos enviar? A lista completa de campos (todos os parâmetros, tipos e obrigatoriedade), navegável por grupo (Identificação, Tomador, Serviço, Retenções, DPS Nacional…) e gerada direto do contrato real, está no Catálogo de campos: NFS-e. Para navegar por endpoint em vez de por documento, veja a Referência da API.

Multi-prefeitura

Suporte a padrões municipais (ABRASF) e ao Padrão Nacional (SEFIN/ADN)

ISS incluso

Alíquota e retenções conforme legislação local, passthrough, a Engine não calcula

Emissão assistida

Com resolverTributacao: true, o Cérebro Fiscal completa campos ausentes da DPS
NFS-e é o único módulo em produção que exige issuerId explícito no corpo da requisição. Em NF-e e NFC-e o campo é opcional (obrigatório só a partir do segundo emissor); ver Autenticação.

Diferenças NFS-e vs NF-e


Emitir NFS-e

servico.itemListaServico é obrigatório no fluxo manual, só pode ficar de fora com resolverTributacao: true.
dpsNacional pode ficar de fora. Sem o bloco, a engineAPI deriva opSimpNac, cTribNac e tribISSQN do cadastro do emissor (crt/mei/cTribNacPadrao) — não é preciso adivinhar código do ADN. O regApTribSN só é derivado para Issuer.crt = 1 (Simples): para Issuer.crt = 2 (Simples com excesso de sublimite) ele é obrigatório na requisição, porque o crt sozinho não diz o regime de apuração e não existe padrão desse regime no cadastro — sem ele a emissão recusa com 422 (CRT2_SEM_REGIME_APURACAO), inclusive com resolverTributacao: true. Se o emissor não tiver cTribNacPadrao cadastrado, a emissão rejeita com 400 ("Campos fiscais do Padrão Nacional ausentes: cTribNac") pedindo o cadastro ou o campo manual. Num emissor crt = 2 sem regApTribSN, porém, esse 400 não chega a acontecer: a recusa 422 acima vem primeiro, porque o 400 só nasce no provider, depois dessa verificação. dpsNacional informado explicitamente (como no exemplo abaixo) sempre vence a derivação, campo a campo. Ver O que significa cada código de opSimpNac antes de copiar o exemplo para um emissor de regime diferente.

Códigos de opSimpNac

dpsNacional.opSimpNac declara a situação do emissor perante o Simples Nacional — não é uma escolha livre, é o enum oficial do leiaute da DPS. Copiar o valor errado do regime do seu emissor é rejeitado pela SEFIN em produção (422 E0160, “a opção de situação perante o Simples Nacional do prestador informada na DPS não está de acordo com o cadastro”). Issuer.crt aceita 4 valores (create-company.dto.ts): 1 (Simples), 2 (Simples com excesso de sublimite de receita bruta), 3 (Regime Normal) e 4 (MEI). Um emissor crt = 2 continua optante do Simples mesmo com o sublimite estourado — não é Não Optante; é essa diferença que a tabela acima cobre. dpsNacional.regApTribSN (regime de apuração do Simples) só acompanha opSimpNac = 3 — MEI (opSimpNac = 2) não leva o campo. Quando você omite dpsNacional inteiro, a engineAPI deriva opSimpNac a partir do cadastro do emissor com esta mesma tabela: é o que mata a rejeição de quem copia o exemplo desta página sem ajustar ao próprio regime. Para Issuer.crt = 1 a derivação também completa regApTribSN = '1' (não existe outro valor possível nesse enum). Para Issuer.crt = 2 o crt sozinho não diz o regime de apuração: não há campo de cadastro pra isso, e chutar '1' arriscaria trocar a rejeição E0160 (divergência cadastral) por uma rejeição de regime incoerente na SEFIN. Sem dpsNacional.regApTribSN explícito, a emissão de um emissor crt = 2 recusa com 422 (CRT2_SEM_REGIME_APURACAO) antes de transmitir — informe o campo pra seguir. Essa exigência vale em qualquer forma de emissão: dpsNacional omitido, parcial, preenchido com opSimpNac, cTribNac e tribISSQN, ou com resolverTributacao: true. Mandar o trio completo não dispensa o regApTribSN; ligar a tributação assistida também não, porque o Cérebro Fiscal não tem fonte pra esse campo neste regime. Declarar opSimpNac = 1 num emissor crt = 2 não é saída — é exatamente a divergência cadastral que gera a E0160. Emissor MEI (Issuer.mei = true ou Issuer.crt = 4) não é afetado: deriva opSimpNac = 2, que não leva o campo. A verificação roda no último ponto antes da transmissão, sobre a DPS final: nada é transmitido e nenhum número de DPS é consumido.

Campos de Referência

Validação de endereço e alíquota

tomador.endereco.complemento é opcional e aceita até 156 caracteres (o teto do leiaute, publicado no contrato OpenAPI). Você pode omiti-lo ou enviar uma string vazia ou composta só por espaços dentro desse teto, que a API trata como ausência e não escreve na DPS; acima de 156 caracteres a recusa é 400 nomeando o campo, mesmo que o conteúdo seja só espaço. Valor de tipo errado (por exemplo, um número) também devolve 400 dizendo o tipo esperado. Já logradouro, numero e bairro exigem conteúdo depois de remover os espaços das bordas: branco somente devolve 400 com o nome do campo, antes de reservar número da DPS. Os quatro campos recusam caracteres fora da faixa do leiaute, como travessão, aspas curvas, reticências e emoji. servico.aliquotaIss aceita de 0 a 9.99, com no máximo duas casas decimais. Valor com três ou mais casas, ou dois dígitos na parte inteira, devolve 400 antes da emissão; a API não arredonda uma alíquota declarada pelo integrador.
Não existem os campos valorISS, issRetido nem codigoServico no payload de NFS-e. Use aliquotaIss (a engineAPI não calcula o valor do ISS), retencoes.issRetidoPor e itemListaServico.

Serviço B2B com retenção

Na prestação para empresa, o tomador costuma reter tributos na fonte e recolher no lugar do prestador. A DPS declara isso em dois lugares do leiaute nacional, e a engineAPI escreve os dois a partir do bloco retencoes:
A alíquota da retenção federal é sua. A engineAPI transcreve o valor retido que você informou e confere coerência: ela não aplica percentual de IRRF/CSLL/PIS/COFINS/INSS, porque isso depende da natureza do serviço, da dispensa por valor mínimo e do regime do tomador. Quem calcula é o seu sistema (ou o seu contador).

PIS/COFINS: indicador, não valor

O leiaute do Padrão Nacional não tem campo para o valor retido de PIS e COFINS. Os campos vPis e vCofins da DPS existem, mas são o débito de apuração própria do prestador, e o total de retenções da NFS-e é calculado sem eles:
Por isso a retenção de PIS/COFINS (e da CSLL, quando você quer declará-la junto) se informa pelo indicador dpsNacional.tpRetPisCofins, que aceita os códigos 0 a 9 da tabela oficial. Os mais usados: Mandar valor em retencoes.pis ou retencoes.cofins recusa com 422 RETENCAO_SEM_CAMPO_NO_LEIAUTE: escrever esse valor em vPis/vCofins declararia um débito próprio que não é o seu.
Conciliação pelo valor líquido: como PIS e COFINS retidos não entram no vTotalRet da NFS-e, eles também não são descontados do valor líquido do documento (vLiq = serviço menos descontos menos retenções). Quem concilia recebimento pelo líquido da nota precisa subtrair PIS/COFINS por fora, a partir do indicador declarado.
cURL
O documento autorizado sai com tpRetISSQN = 2, os três valores federais nos campos próprios e tpRetPisCofins = 3 (PIS/COFINS/CSLL retidos). O que foi declarado volta também na consulta: GET /v1/nfse/{id} e a listagem trazem o campo retencoes com os nomes do leiaute (tpRetISSQN, vRetCP, vRetIRRF, vRetCSLL, tpRetPisCofins), ou nulo quando não houve retenção.
Recusas antes de consumir numeração. Retenção maior que servico.valorServicos (422 RETENCAO_MAIOR_QUE_SERVICO), valor em retencoes.pis/retencoes.cofins ou retencoes.outrasRetencoes (422 RETENCAO_SEM_CAMPO_NO_LEIAUTE), indicador sem dpsNacional.cstPisCofins (422 RETENCAO_PIS_COFINS_SEM_CST), indicador que contradiz retencoes.csll em qualquer dos dois sentidos (422 RETENCAO_PIS_COFINS_INCOERENTE) e issRetidoPor conflitando com dpsNacional.tpRetISSQN (422 RETENCAO_ISS_CONFLITO) param a emissão antes de transmitir: nenhum número de DPS é gasto.
No sandbox, a emissão com retenção funciona, mas nada é escrito. A emissão simulada não gera uma DPS real, então a retenção declarada não entra em documento nenhum e não fica registrada na nota. A resposta traz isso em avisos[], para você exercitar o fluxo sem concluir que a retenção foi transmitida. Em ambiente configurado para o provedor municipal (que também não escreve retenção), a emissão recusa com 422 RETENCAO_PROVIDER_NAO_ESCREVE, porque ali a nota sairia de verdade sem o que você declarou.O grupo intermediario (identificação de quem intermediou a prestação) ainda não é aceito no payload: issRetidoPor: "intermediario" declara a retenção pelo intermediário, mas sem identificá-lo.

Emissão assistida (Cérebro Fiscal)

Isto é diferente da derivação básica de opSimpNac/cTribNac/tribISSQN (e de regApTribSN só para Issuer.crt = 1; sempre ativa, sem custo, ver Códigos de opSimpNac): a assistida completa mais campos (itemListaServico, codigoNBS), audita cada resolução e exige plano/feature habilitados. Com "resolverTributacao": true, campos ausentes da DPS (dpsNacional.cTribNac, opSimpNac, tribISSQN, tpRetISSQN, servico.itemListaServico, servico.codigoNBS) são preenchidos a partir do cadastro do emissor (cTribNacPadrao/servicoPadraoLc116 em Issuer). Requer feature de plano + Issuer.fiscalBrainEnabled (403 sem isso). Campo obrigatório sem fonte cadastrada → 422 com camposNaoResolvidos e nada é emitido. O Cérebro Fiscal não tem fonte para regApTribSN num emissor crt = 2: nesse caso a recusa sai nomeada, com CRT2_SEM_REGIME_APURACAO, dizendo qual campo informar — ligar resolverTributacao não dispensa o campo. aliquotaIss nunca é opinada pelo Cérebro: se o payload mandar um valor mesmo assim, ele é ignorado (ecoar um valor divergente do cálculo da SEFIN causa rejeição E1235), e esse descarte aparece na resposta, em avisos[] (string), além do log do servidor. Sem a flag, o comportamento é o de sempre (passthrough manual, sem avisos).

Códigos de Serviço LC116

Os códigos (servico.itemListaServico) são definidos pela Lei Complementar 116/2003:
Cada município pode ter lista complementar de códigos. Consulte a legislação local ou o site da prefeitura emissora.

Response de Sucesso

Envelopado em { data, meta }:
valorServicos é string decimal ("5000", sem zeros à direita), não number cru, mesmo tratamento do amount de NF-e/NFC-e (evita imprecisão de ponto flutuante). Não há issuer/customer embutidos, nem linkNfse/pdfUrl/xmlUrl/xmlPath na resposta de emissão. downloads.xml/downloads.pdf apontam pras rotas reais de download (GET /v1/nfse/xml/{id}, GET /v1/nfse/pdf/{id}).
data.avisos (array de string) é opcional, só aparece quando há algo a avisar sobre a emissão que acabou de acontecer (hoje: emissão assistida com servico.aliquotaIss informado no payload, o valor foi ignorado, ver Emissão assistida acima). Ausência do campo = nada a avisar.
Para empresa do Simples, o percentual total de tributos do cadastro tem um mês de referência: pTotTribSNCompetencia no formato AAAA-MM. Esse percentual muda todo mês. Se a emissão usar um mês anterior ou se o mês não estiver cadastrado, a nota segue — o valor é declaratório — e a resposta traz um aviso para confirmar com o contador e atualizar o cadastro. Um valor enviado em dpsNacional.pTotTribSN tem prioridade e segue exatamente como foi informado.

Cancelar NFS-e

{id} é o id (UUID) retornado na emissão. Diferente de NF-e/NFC-e (campo justificativa), o corpo do cancelamento de NFS-e usa motivo. Se a SEFIN rejeitar o cancelamento, a resposta 400 traz erros[] estruturado (codigo/descricao/complemento verbatim do Padrão Nacional), nunca vazio, para sua aplicação ramificar por código.
As regras de cancelamento variam por município/provider. A API retorna erro se o cancelamento não for permitido.

Consultar NFS-e na SEFIN

{id} é o id (UUID) retornado na emissão. Consulta a NFS-e diretamente na SEFIN/ADN pela chave de acesso e atualiza o xmlContent local quando a SEFIN devolve um XML.
A consulta na SEFIN (NfseConsulta) enxerga apenas se o documento existe: ela não informa eventos de cancelamento. Por isso, se a NFS-e já tiver cancelamento homologado no seu cadastro (status: CANCELED), a engineAPI cruza os dois dados antes de responder: o status retornado nunca volta AUTORIZADA para um documento cancelado, vem CANCELADA, refletindo o estado real. Para qualquer outro desfecho, o status é repassado verbatim da SEFIN.

Cobertura da NFS-e por município

Nem todo município já aderiu ao Sistema Nacional da NFS-e (Padrão Nacional/ADN) — quem não aderiu emite pelo padrão municipal antigo (ABRASF), quando o município tiver um provider configurado. Antes de cadastrar um emissor ou diagnosticar por que uma DPS foi rejeitada, consulte a cobertura do IBGE:
situacao igual a "desconhecido" não é “não aderente”. Acontece quando não há certificado A1 disponível para consultar o ADN (leitura exige mTLS com um certificado ICP-Brasil — qualquer emissor do parceiro serve), quando a última consulta falhou por rede/timeout, ou quando o espelho está velho demais sem reconsulta bem-sucedida. desconhecido nunca bloqueia emissão — só nao_aderente confirmado bloqueia.
A engineAPI mantém um espelho local (TTL padrão 24h, renovado automaticamente quando você cadastra/atualiza certificado, muda ibgeCode ou troca de ambiente) e revalida diariamente as linhas vencidas — nunca varre os ~5.570 municípios de uma vez (o ADN não tem endpoint de lista em massa). Use ?atualizar=true para forçar uma reconsulta imediata, ignorando o TTL:
O cadastro de empresa consome o mesmo espelho: prontoPara.nfse em POST/GET/PATCH /v1/companies só marca o município como bloqueante quando a cobertura é nao_aderente confirmada — nesse caso faltando.nfse inclui municipioAderente, e a resposta traz prontoPara.cobertura: { situacao, consultadoEm } (campo aditivo, presente sempre que já houve alguma consulta). O POST /v1/companies consulta a cobertura na hora do cadastro, usando o certificado de qualquer emissor do mesmo parceiro (o recém-criado ainda não tem o próprio). Sem certificado no parceiro, a cobertura fica desconhecido e o cadastro não falha. A mesma resposta traz avisos[] (array sempre presente, vazio quando não há aviso). Só emite um item quando a cobertura é nao_aderente confirmada:
desconhecido não gera aviso. Na emissão, o mesmo código vira 422 antes de numerar se o espelho já diz nao_aderente; se a SEFIN ainda assim devolver E0037, a recusa também sai 422 com details.mensagemSefin preservando a mensagem original. Ver Erros e respostas.

Veja também