Skip to main content

Python library for Brazilian NFSe Nacional (Padrao Nacional) API integration

Project description

pynfse-nacional

Biblioteca Python para integração com a API do NFSe Nacional (Padrão Nacional).

Índice

Visão Geral

Esta biblioteca fornece um cliente para interagir com a API do NFSe Nacional (SEFIN Nacional), que se tornou obrigatória para todos os municípios brasileiros a partir de janeiro de 2026.

Funcionalidades

  • Autenticação mTLS com certificados ICP-Brasil A1/A3
  • Geração de XML da DPS (Declaração de Prestação de Serviços)
  • Assinatura digital de XML (XMLDSIG)
  • Compressão GZip e codificação Base64
  • Emissão, consulta, cancelamento e substituição de NFSe
  • Download e geração local do DANFSe em PDF
  • Consulta de convênio municipal
  • Validação de campos com mensagens em português

Instalação

uv add pynfse-nacional

Ou com pip:

pip install pynfse-nacional

Para geração local de PDF (DANFSe):

uv add "pynfse-nacional[pdf]"

Início Rápido

from datetime import datetime
from decimal import Decimal

from pynfse_nacional import NFSeClient, DPS, Prestador, Tomador, Servico, Endereco

# Criar endereço do prestador
endereco_prestador = Endereco(
    logradouro="Rua Exemplo",
    numero="100",
    complemento="Sala 1",
    bairro="Centro",
    codigo_municipio=3550308,  # Código IBGE do município
    uf="SP",
    cep="01310100",
)

# Criar prestador (emissor da nota)
prestador = Prestador(
    cnpj="12345678000199",
    inscricao_municipal="12345",
    razao_social="Empresa Exemplo LTDA",
    nome_fantasia="Empresa Exemplo",
    endereco=endereco_prestador,
    email="contato@empresa.com",
    telefone="11999999999",
)

# Criar tomador (cliente)
tomador = Tomador(
    cpf="12345678901",
    razao_social="Joao da Silva",
    endereco=Endereco(
        logradouro="Av. Brasil",
        numero="500",
        bairro="Jardins",
        codigo_municipio=3550308,
        uf="SP",
        cep="01430001",
    ),
)

# Criar serviço
servico = Servico(
    codigo_lc116="04.03.01",  # Código completo com subitem (XX.XX.XX)
    discriminacao="Consulta médica em consultório",
    valor_servicos=Decimal("500.00"),
    iss_retido=False,
    aliquota_simples=Decimal("18.83"),  # Para Simples Nacional
)

# Criar DPS (não definir id_dps - será gerado automaticamente)
dps = DPS(
    serie="900",
    numero=1,
    competencia="2026-01",
    data_emissao=datetime.now(),
    prestador=prestador,
    tomador=tomador,
    servico=servico,
    regime_tributario="simples_nacional",
    optante_simples=True,
    incentivador_cultural=False,
)

# Inicializar cliente com certificado
client = NFSeClient(
    cert_path="/caminho/para/certificado.pfx",
    cert_password="sua-senha",
    ambiente="homologacao",  # ou "producao"
)

# Enviar e obter NFSe
response = client.submit_dps(dps)

if response.success:
    print(f"NFSe emitida: {response.nfse_number}")
    print(f"Chave de acesso: {response.chave_acesso}")
else:
    print(f"Erro: {response.error_message}")

Referência da API

NFSeClient

Cliente principal para a API do NFSe Nacional.

Emissão e Consulta de NFSe:

  • submit_dps(dps: DPS) -> NFSeResponse - Envia DPS e recebe NFSe
  • query_nfse(chave_acesso: str) -> NFSeQueryResult - Consulta NFSe pela chave de acesso
  • download_danfse(chave_acesso: str) -> bytes - Baixa o DANFSe em PDF
  • cancel_nfse(chave_acesso, reason, codigo_motivo=1, cnpj_prestador="") -> EventResponse - Cancela NFSe
  • substitute_nfse(chave_acesso_original, new_dps, motivo, codigo_motivo) -> NFSeResponse - Substitui NFSe existente

Consulta de Convênio Municipal:

  • query_convenio_municipal(codigo_municipio) -> ConvenioMunicipal - Consulta se município tem convênio com o sistema nacional

Verificando Convênio Municipal

Antes de emitir uma NFSe, verifique se o município tem convênio com o sistema nacional:

# Verificar se o município tem convênio
convenio = client.query_convenio_municipal(1302603)

if convenio.aderido:
    print("Município tem convênio com o sistema nacional")
    print(f"Dados: {convenio.raw_data}")
else:
    print("Município NÃO tem convênio")

Nota: A API de parametrização (alíquotas por serviço) está com problemas no ambiente de homologação. Apenas a consulta de convênio municipal está disponível.

Cancelando NFSe

Para cancelar uma NFSe emitida, utilize a chave de acesso e o CNPJ do prestador (obrigatório para identificação junto à SEFIN):

result = client.cancel_nfse(
    chave_acesso="13026032242713924000185000000000010626030410654816",
    reason="Erro na emissão do serviço prestado",
    codigo_motivo=1,          # 1=erro na emissão, 2=serviço não prestado, 4=duplicidade
    cnpj_prestador="42713924000185",  # CNPJ do prestador, somente dígitos
)

if result.success:
    print(f"NFS-e cancelada. Protocolo: {result.protocolo}")
else:
    print(f"Erro ao cancelar: [{result.error_code}] {result.error_message}")

Códigos de motivo (codigo_motivo):

Código Descrição
1 Erro na emissão
2 Serviço não prestado
4 Duplicidade

Observações:

  • O cnpj_prestador é obrigatório — sem ele, a SEFIN retorna HTTP 404.
  • Alguns municípios configuram um valor máximo para cancelamento via API. Se o valor da NFS-e exceder esse limite, o cancelamento deve ser feito pelo portal municipal.
  • O prazo para cancelamento varia por município.

Substituindo NFSe

Para corrigir informações em uma NFSe já emitida, você pode substituí-la por uma nova:

from datetime import datetime
from decimal import Decimal

# Criar novo DPS com as informações corrigidas
new_dps = DPS(
    serie="900",
    numero=2,  # Novo número sequencial
    competencia="2026-01",
    data_emissao=datetime.now(),
    prestador=prestador,
    tomador=tomador,
    servico=Servico(
        codigo_lc116="04.03.01",
        discriminacao="Descrição corrigida do serviço prestado",  # Corrigido
        valor_servicos=Decimal("500.00"),
    ),
    regime_tributario="simples_nacional",
)

# Substituir a NFSe original
response = client.substitute_nfse(
    chave_acesso_original="12345678901234567890123456789012345678901234567890",
    new_dps=new_dps,
    motivo="Correção da descrição do serviço prestado",
    codigo_motivo=99,  # 99 = outros
)

if response.success:
    print(f"NFSe substituta emitida: {response.nfse_number}")
    print(f"Nova chave de acesso: {response.chave_acesso}")
else:
    print(f"Erro: {response.error_message}")

Regras de substituição:

  • A substituição deve ser feita em até 35 dias após a emissão original
  • Não é permitido substituir NFSe onde o tomador não foi identificado
  • Não é permitido alterar o tomador para outra pessoa/empresa
  • O motivo deve ter entre 15 e 255 caracteres

Gerando DANFSe (PDF)

A biblioteca permite gerar o DANFSe localmente a partir do XML da NFSe:

from pynfse_nacional.pdf_generator import (
    generate_danfse_from_base64,
    generate_danfse_from_xml,
    HeaderConfig,
)

# Apos emitir a NFSe, gerar PDF a partir da resposta
response = client.submit_dps(dps)

if response.success:
    # Gerar PDF a partir do XML comprimido retornado pela API
    pdf_bytes = generate_danfse_from_base64(
        nfse_xml_gzip_b64=response.nfse_xml_gzip_b64,
        output_path="/caminho/para/danfse.pdf",  # Opcional - salva em arquivo
    )

    # Ou gerar a partir de XML string
    pdf_bytes = generate_danfse_from_xml(
        xml_content=response.xml_nfse,
        output_path="/caminho/para/danfse.pdf",
    )

Com cabeçalho personalizado (logo da empresa):

header = HeaderConfig(
    image_path="/caminho/para/logo.png",
    title="Nome da Empresa",
    subtitle="Serviços Médicos",
    phone="(11) 99999-9999",
    email="contato@empresa.com",
)

pdf_bytes = generate_danfse_from_base64(
    nfse_xml_gzip_b64=response.nfse_xml_gzip_b64,
    output_path="/caminho/para/danfse.pdf",
    header_config=header,
)

Modelos

  • DPS - Declaração de prestação de serviços
  • Prestador - Prestador de serviços (emissor)
  • Tomador - Tomador de serviços
  • Servico - Detalhes do serviço
  • ConvenioMunicipal - Informações de convênio municipal
  • SubstituicaoNFSe - Informações de substituição de NFSe

Ambientes

  • Homologação: sefin.producaorestrita.nfse.gov.br
  • Produção: sefin.nfse.gov.br

Documentação

Documentação Oficial

Manuais da API

Swagger / OpenAPI

  • Homologação: https://sefin.producaorestrita.nfse.gov.br/API/SefinNacional/docs/index
  • Produção: https://sefin.nfse.gov.br/API/SefinNacional/docs/index

Recursos da Comunidade

Licença

GNU Affero General Public License v3 (AGPL-3.0)


English Version

Python library for Brazilian NFSe Nacional (Padrao Nacional) API integration.

Overview

This library provides a client for interacting with the NFSe Nacional (SEFIN Nacional) API, which became mandatory for all Brazilian municipalities starting January 2026.

Features

  • mTLS authentication with ICP-Brasil A1/A3 certificates
  • DPS (Declaracao de Prestacao de Servicos) XML generation
  • XML digital signing (XMLDSIG)
  • GZip compression and Base64 encoding
  • NFSe issuance, query, and cancellation
  • DANFSe PDF download and local generation
  • Municipal agreement (convenio) query
  • Field validation with Portuguese error messages

Installation

uv add pynfse-nacional

Or with pip:

pip install pynfse-nacional

For local PDF generation (DANFSe):

uv add "pynfse-nacional[pdf]"

Quick Start

from datetime import datetime
from decimal import Decimal

from pynfse_nacional import NFSeClient, DPS, Prestador, Tomador, Servico, Endereco

# Create provider address
provider_address = Endereco(
    logradouro="Rua Exemplo",
    numero="100",
    complemento="Sala 1",
    bairro="Centro",
    codigo_municipio=3550308,  # IBGE municipality code
    uf="SP",
    cep="01310100",
)

# Create provider (invoice issuer)
prestador = Prestador(
    cnpj="12345678000199",
    inscricao_municipal="12345",
    razao_social="Example Company LTDA",
    nome_fantasia="Example Company",
    endereco=provider_address,
    email="contact@company.com",
    telefone="11999999999",
)

# Create recipient (client)
tomador = Tomador(
    cpf="12345678901",
    razao_social="John Smith",
    endereco=Endereco(
        logradouro="Av. Brasil",
        numero="500",
        bairro="Jardins",
        codigo_municipio=3550308,
        uf="SP",
        cep="01430001",
    ),
)

# Create service
servico = Servico(
    codigo_lc116="04.03.01",  # Full code with subitem (XX.XX.XX)
    discriminacao="Medical consultation",
    valor_servicos=Decimal("500.00"),
    iss_retido=False,
    aliquota_simples=Decimal("18.83"),  # For Simples Nacional
)

# Create DPS (do NOT set id_dps - it will be auto-generated)
dps = DPS(
    serie="900",
    numero=1,
    competencia="2026-01",
    data_emissao=datetime.now(),
    prestador=prestador,
    tomador=tomador,
    servico=servico,
    regime_tributario="simples_nacional",
    optante_simples=True,
    incentivador_cultural=False,
)

# Initialize client with certificate
client = NFSeClient(
    cert_path="/path/to/certificate.pfx",
    cert_password="your-password",
    ambiente="homologacao",  # or "producao"
)

# Submit and get NFSe
response = client.submit_dps(dps)

if response.success:
    print(f"NFSe issued: {response.nfse_number}")
    print(f"Access key: {response.chave_acesso}")
else:
    print(f"Error: {response.error_message}")

API Reference

NFSeClient

Main client for NFSe Nacional API.

NFSe Issuance and Query:

  • submit_dps(dps: DPS) -> NFSeResponse - Submit DPS and receive NFSe
  • query_nfse(chave_acesso: str) -> NFSeQueryResult - Query NFSe by access key
  • download_danfse(chave_acesso: str) -> bytes - Download DANFSe PDF
  • cancel_nfse(chave_acesso, reason, codigo_motivo=1, cnpj_prestador="") -> EventResponse - Cancel NFSe
  • substitute_nfse(chave_acesso_original, new_dps, motivo, codigo_motivo) -> NFSeResponse - Substitute existing NFSe

Municipal Agreement Query:

  • query_convenio_municipal(codigo_municipio) -> ConvenioMunicipal - Query if municipality has agreement with the national system

Checking Municipal Agreement

Before issuing an NFSe, check if the municipality has an agreement with the national system:

# Check if the municipality has an agreement
convenio = client.query_convenio_municipal(1302603)

if convenio.aderido:
    print("Municipality has agreement with the national system")
    print(f"Data: {convenio.raw_data}")
else:
    print("Municipality does NOT have agreement")

Note: The parametrization API (service tax rates) has issues in the homologation environment. Only the municipal agreement query is available.

Cancelling NFSe

To cancel an issued NFSe, provide the access key and the provider's CNPJ (required by SEFIN to identify the requester):

result = client.cancel_nfse(
    chave_acesso="13026032242713924000185000000000010626030410654816",
    reason="Erro na emissão do serviço prestado",
    codigo_motivo=1,           # 1=issuance error, 2=service not rendered, 4=duplicate
    cnpj_prestador="42713924000185",   # Provider CNPJ, digits only
)

if result.success:
    print(f"NFSe cancelled. Protocol: {result.protocolo}")
else:
    print(f"Cancellation failed: [{result.error_code}] {result.error_message}")

Cancellation reason codes (codigo_motivo):

Code Description
1 Issuance error
2 Service not rendered
4 Duplicate

Notes:

  • cnpj_prestador is required — without it SEFIN returns HTTP 404.
  • Some municipalities configure a maximum cancellation value via API. If the NFSe value exceeds that limit, cancellation must be done through the municipal portal.

Generating DANFSe (PDF)

The library allows generating DANFSe locally from the NFSe XML:

from pynfse_nacional.pdf_generator import (
    generate_danfse_from_base64,
    generate_danfse_from_xml,
    HeaderConfig,
)

# After issuing the NFSe, generate PDF from the response
response = client.submit_dps(dps)

if response.success:
    # Generate PDF from compressed XML returned by the API
    pdf_bytes = generate_danfse_from_base64(
        nfse_xml_gzip_b64=response.nfse_xml_gzip_b64,
        output_path="/path/to/danfse.pdf",  # Optional - saves to file
    )

    # Or generate from XML string
    pdf_bytes = generate_danfse_from_xml(
        xml_content=response.xml_nfse,
        output_path="/path/to/danfse.pdf",
    )

With custom header (company logo):

header = HeaderConfig(
    image_path="/path/to/logo.png",
    title="Company Name",
    subtitle="Medical Services",
    phone="(11) 99999-9999",
    email="contact@company.com",
)

pdf_bytes = generate_danfse_from_base64(
    nfse_xml_gzip_b64=response.nfse_xml_gzip_b64,
    output_path="/path/to/danfse.pdf",
    header_config=header,
)

Models

  • DPS - Service declaration
  • Prestador - Service provider (issuer)
  • Tomador - Service recipient
  • Servico - Service details
  • ConvenioMunicipal - Municipal agreement information
  • SubstituicaoNFSe - NFSe substitution information

Environments

  • Homologacao (staging): sefin.producaorestrita.nfse.gov.br
  • Producao (production): sefin.nfse.gov.br

Documentation

Community Resources

License

GNU Affero General Public License v3 (AGPL-3.0)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pynfse_nacional-0.4.6.tar.gz (362.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pynfse_nacional-0.4.6-py3-none-any.whl (43.2 kB view details)

Uploaded Python 3

File details

Details for the file pynfse_nacional-0.4.6.tar.gz.

File metadata

  • Download URL: pynfse_nacional-0.4.6.tar.gz
  • Upload date:
  • Size: 362.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pynfse_nacional-0.4.6.tar.gz
Algorithm Hash digest
SHA256 8e1b96e7d8f579279c86b0894d37d664cdcb7a4a13fefcb33e794ba612e80e6d
MD5 e5b9147a1520c6b71047b114f4069e73
BLAKE2b-256 218a5c104712bc96b2292b02a2ec231cfbcbd27a16e524dd62f1a686713399d3

See more details on using hashes here.

File details

Details for the file pynfse_nacional-0.4.6-py3-none-any.whl.

File metadata

  • Download URL: pynfse_nacional-0.4.6-py3-none-any.whl
  • Upload date:
  • Size: 43.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pynfse_nacional-0.4.6-py3-none-any.whl
Algorithm Hash digest
SHA256 6d676ba547a76d5d8d0de56f207ae2db15e80c65ab1f64929e299fcd17d49cd7
MD5 f7a0e85995f76f00ff9342511392c6de
BLAKE2b-256 84576abf4024dc0e64584f6133dcd06f03dd1084d02270badc180d3f5e6fc787

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page