Guia: NFSe
Emita Notas Fiscais de Serviços Eletrônicas (NFSe) para municípios via engineAPI.
NFSe: Nota Fiscal de Serviços Eletrônica
A NFSe é o documento fiscal para prestação de serviços. Diferente da NFe, é 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
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: NFSe. Para navegar por endpoint em vez de por documento, veja a Referência por Endpoint.
NFSe é o único módulo em produção que exige issuerId explícito no corpo da
requisição. Em NFe e NFCe o campo é opcional (obrigatório só a partir do segundo
emissor); ver Autenticação.
Diferenças NFSe vs NFe
| Aspecto | NFe (mercadorias) | NFSe (serviços) |
|---|---|---|
| Autorizada por | SEFAZ Estadual | Prefeitura (ABRASF) ou SEFIN/ADN (Padrão Nacional) |
| Imposto principal | ICMS | ISS |
| Padrão único | Sim (SEFAZ) | Não, depende do provider configurado no emissor |
| Cancelamento | Até 24h | POST /v1/nfse/{id}/cancelar, prazo definido no provider/legislação municipal |
Emitir NFSe
servico.itemListaServico é obrigatório no fluxo manual, só pode ficar de fora com
resolverTributacao: true.
O exemplo abaixo inclui dpsNacional. O emissor no Padrão Nacional (o provider
da engineAPI hoje) rejeita com 400 ("Campos fiscais do Padrão Nacional ausentes: opSimpNac, cTribNac, tribISSQN") sem esse bloco. dpsNacional só pode ficar de fora
com resolverTributacao: true (emissão assistida, ver abaixo): o Cérebro Fiscal
preenche a partir do cadastro do emissor.
curl -X POST https://api.engineapi.com.br/v1/nfse \
-H "x-api-key: SUA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"issuerId": "ISSUER_ID",
"tomador": {
"cnpjCpf": "99888777000100",
"razaoSocial": "Cliente Exemplo SA",
"email": "financeiro@cliente.com.br",
"telefone": "11999998888",
"endereco": {
"logradouro": "Av Paulista",
"numero": "1000",
"bairro": "Bela Vista",
"codigoMunicipio": "3550308",
"uf": "SP",
"cep": "01310100"
}
},
"servico": {
"codigoMunicipio": "3550308",
"itemListaServico": "01.01",
"discriminacao": "Desenvolvimento de software sob encomenda",
"valorServicos": 5000.00,
"aliquotaIss": 2.00
},
"dpsNacional": {
"opSimpNac": 1,
"cTribNac": "010701",
"tribISSQN": 1
},
"competencia": "2026-04-01",
"informacoesComplementares": "Referente ao projeto engineAPI, Fase 1"
}'
Campos de Referência
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
issuerId | string (UUID) | Sim | ID da empresa prestadora, único módulo que exige este campo hoje |
tomador.cnpjCpf | string | Sim | Não é cnpj/cpf |
tomador.razaoSocial | string | Sim | Não é nome |
tomador.endereco | objeto | Sim | logradouro, numero, bairro, codigoMunicipio, uf, cep obrigatórios |
servico.codigoMunicipio | string | Sim | Código IBGE do município de prestação |
servico.itemListaServico | string | Sim no fluxo manual | Código LC 116 (ex.: 01.01), não é codigoServico. Só pode faltar com resolverTributacao: true |
servico.discriminacao | string | Sim | Não é descricao |
servico.valorServicos | número | Sim | Não é valor |
servico.aliquotaIss | número | Não | N/A. Não existem valorISS/issRetido no payload; retenção fica em retencoes.* |
retencoes.{irrf,csll,cofins,pis,inss,outrasRetencoes} | número | Não | Interino: qualquer valor ≠ 0 recusa com 422 RETENCOES_NAO_SUPORTADAS. O motor ainda não escreve retenções na DPS do Padrão Nacional; aceitar e descartar seria pior que recusar. Omita o bloco (ou mande tudo 0/ausente) |
competencia | string (yyyy-MM-dd) | Não | Formato de data completo, não YYYY-MM; default é a data de emissão |
dpsNacional.* | objeto | Sim no Padrão Nacional (o provider da engineAPI hoje) | Bloco do Padrão Nacional (opSimpNac, cTribNac, tribISSQN mínimos), passthrough, ignorado por providers ABRASF. Sem ele (e sem resolverTributacao: true), a SEFIN rejeita com 400 |
ibsCbs.* | objeto | Não (obrigatório por lei a partir de 01/10/2026 na regra geral) | Grupo IBS/CBS da Reforma Tributária na DPS: cIndOp, cst, cClassTrib são os mínimos. Passthrough puro: o Cérebro Fiscal não resolve nenhum deles, e a DPS não leva alíquota nem valor (quem calcula é o sistema nacional). indDest: "1" recusa com 422 IBSCBS_DPS_DESTINATARIO_NAO_SUPORTADO. Ver Reforma Tributária: datas que importam e Campos da NFSe |
informacoesComplementares | string | Não | N/A |
resolverTributacao | boolean | Não | Emissão assistida: completa dpsNacional/servico.itemListaServico/servico.codigoNBS ausentes a partir do cadastro do emissor. Não toca em ibsCbs |
Não existem os campos valorISS, issRetido nem codigoServico no payload de NFSe.
Use aliquotaIss (a engineAPI não calcula o valor do ISS) e itemListaServico.
Emissão assistida (Cérebro Fiscal)
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.
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:
| Código | Serviço |
|---|---|
01.01 | Análise e desenvolvimento de sistemas |
01.07 | Suporte técnico em informática |
17.01 | Assessoria, consultoria, pesquisa |
17.06 | Propaganda e publicidade |
26.01 | Serviços de coleta, busca e entrega |
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 }:
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"numero": 1234,
"codigoVerificacao": "ABC12345",
"chaveAcesso": "35260211222333...",
"protocol": "135260000001234",
"status": "AUTHORIZED",
"dataEmissao": "2026-04-27T02:00:00.000Z",
"valorServicos": "5000",
"downloads": {
"xml": "/v1/nfse/xml/3fa85f64-5717-4562-b3fc-2c963f66afa6",
"pdf": "/v1/nfse/pdf/3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-07-06T12:00:00.000Z"
}
}
valorServicos é string decimal ("5000", sem zeros à direita), não number cru, mesmo tratamento
do amount de NFe/NFCe (evita imprecisão de ponto flutuante e o Decimal.js interno
vazando cru). 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.
Cancelar NFSe
curl -X POST https://api.engineapi.com.br/v1/nfse/{id}/cancelar \
-H "x-api-key: SUA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "motivo": "Serviço não prestado, contrato cancelado", "codigoMotivo": "2" }'
{id} é o id (UUID) retornado na emissão. Diferente de NFe/NFCe (campo
justificativa), o corpo do cancelamento de NFSe usa motivo.
| Campo | Obrigatório | Descrição |
|---|---|---|
motivo | Sim | Texto livre do motivo (vai verbatim no evento como xMotivo). |
codigoMotivo | Não | Código oficial do motivo (cMotivo, Padrão Nacional): "1" Erro na emissão · "2" Serviço não prestado · "9" Outros. Aceita string ou número. Default: "9" (Outros). Informe o código real sempre que souber; ele fica gravado no evento fiscal permanente. Valor fora do enum → 400 antes de qualquer chamada à SEFIN. |
Se a SEFIN rejeitar o cancelamento, a resposta 400 traz erros[] estruturado
(codigo/descricao/complemento verbatim do Padrão Nacional), nunca vazio,
pra sua aplicação ramificar por código.
As regras de cancelamento variam por município/provider. A API retornará erro se o cancelamento não for permitido.
Consultar NFSe na SEFIN
curl https://api.engineapi.com.br/v1/nfse/{id}/consultar \
-H "x-api-key: SUA_API_KEY"
{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.