Skip to main content

Envio de Lote de RPS (Versão 2)

Tutorial completo sobre emissão de NFS-e via lote de RPS usando o schema versão 2, que inclui os campos obrigatórios da Reforma Tributária (IBS/CBS).

Visão Geral

O envio de lote de RPS permite converter múltiplos Recibos Provisórios de Serviço (RPS) em Notas Fiscais de Serviço Eletrônicas (NFS-e) em uma única requisição. A versão 2 do schema é obrigatória para emissões a partir de 2026 e inclui campos para o novo sistema tributário IBS/CBS.

Estrutura Básica

from abstra.connectors import run_connection_action

params = {
"cabecalho": {
"cpf_cnpj_remetente": {"cnpj": "12345678000199"},
"transacao": True,
"dt_inicio": "2025-01-15",
"dt_fim": "2025-01-15",
"qtd_rps": 1,
"versao": 2 # Importante: usar versão 2
},
"rps": [
# Lista de RPS
]
}

result = run_connection_action("nota-paulistana", "envio_lote_rps", params)

Estrutura do RPS (Versão 2)

Campos Obrigatórios

rps = {
# Identificação do RPS
"chave_rps": {
"inscricao_prestador": 12345678, # CCM do prestador (8-12 dígitos)
"serie_rps": "RPS", # Série do RPS (até 5 caracteres)
"numero_rps": 1001 # Número sequencial do RPS
},

# Dados básicos
"tipo_rps": "RPS", # Tipo: RPS, RPS-M, RPS-C
"data_emissao": "2025-01-15", # Data de emissão (AAAA-MM-DD)
"status_rps": "N", # N = Normal, C = Cancelado
"tributacao_rps": "T", # Tipo de tributação

# Valores (versão 2 usa valor_inicial_cobrado OU valor_final_cobrado)
"valor_inicial_cobrado": 1000.00, # Valor antes dos tributos
"valor_deducoes": 0.0,

# Retenções (obrigatórias na versão 2)
"valor_pis": 0.0,
"valor_cofins": 0.0,
"valor_inss": 0.0,
"valor_ir": 0.0,
"valor_csll": 0.0,
"valor_ipi": 0.0,

# Serviço
"codigo_servico": 2800, # Código do serviço municipal
"aliquota": 0.05, # Alíquota ISS (ex: 5% = 0.05)
"iss_retido": False,
"discriminacao": "Descrição do serviço prestado",

# Campos reforma tributária
"nbs": "111032100", # Código NBS (9 dígitos)
"c_loc_prestacao": 3550308, # Código IBGE do município

# IBSCBS - OBRIGATÓRIO na versão 2
"ibscbs": {
"fin_nfse": 0,
"ind_final": 0,
"c_ind_op": "100501", # Código indicador operação
"ind_dest": 0,
"valores": {
"trib": {
"g_ibscbs": {
"c_class_trib": "000001" # Código classificação tributária
}
}
}
}
}

Campos Opcionais

# Dados do tomador (cliente)
rps["cpf_cnpj_tomador"] = {"cnpj": "98765432000188"} # ou {"cpf": "12345678901"}
rps["razao_social_tomador"] = "Empresa Tomadora Ltda"
rps["email_tomador"] = "contato@tomador.com.br"
rps["endereco_tomador"] = {
"tipo_logradouro": "Rua",
"logradouro": "das Flores",
"numero_endereco": "123",
"complemento_endereco": "Sala 101",
"bairro": "Centro",
"cidade": 3550308, # Código IBGE
"uf": "SP",
"cep": "01310100"
}

# Intermediário (quando aplicável)
rps["cpf_cnpj_intermediario"] = {"cnpj": "11222333000144"}
rps["iss_retido_intermediario"] = "N"

# Carga tributária (IBPT)
rps["valor_carga_tributaria"] = 155.60
rps["percentual_carga_tributaria"] = 15.56
rps["fonte_carga_tributaria"] = "IBPT"

Bloco IBSCBS (Obrigatório na Versão 2)

O bloco IBSCBS contém informações obrigatórias para o novo sistema tributário. Embora o XSD indique minOccurs="0", a API rejeita requisições sem este bloco.

Estrutura Completa

"ibscbs": {
"fin_nfse": 0, # 0 = NFS-e regular
"ind_final": 0, # 0 = Não, 1 = Sim (consumidor final)
"c_ind_op": "100501", # Código indicador da operação (6 dígitos)
"ind_dest": 0, # 0 = Nacional, 1 = Exterior
"valores": {
"trib": {
"g_ibscbs": {
"c_class_trib": "000001" # Classificação tributária (6 dígitos)
}
}
}
}

Códigos cIndOp (Indicador de Operação)

O código c_ind_op identifica o tipo de operação e determina o local de incidência do imposto.

CódigoTipo de OperaçãoLocal de Incidência
020101Operações com bem imóvelLocal do imóvel
030101Serviços presenciais sobre pessoaLocal da prestação
040101Eventos (feiras, congressos)Local do evento
050101Serviços sobre bem móvel materialLocal da prestação
100301Demais serviços onerososDomicílio do adquirente
100501Bens imateriais/direitos onerososDomicílio do adquirente

Para serviços de software/licenciamento, use 100501 (bens imateriais).

Códigos cClassTrib (Classificação Tributária)

CódigoDescrição
000001Tributação integral IBS/CBS
200011Serviços de P&D por ICT (reduzido)
200044Serviços profissionais (contabilidade, advocacia, etc.)
400001Transporte coletivo público (isento)

Consulte a tabela completa de correlação para encontrar o código correto para seu serviço.

Códigos NBS por Tipo de Serviço

O NBS (Nomenclatura Brasileira de Serviços) deve corresponder ao serviço prestado:

ServiçoNBScIndOpcClassTrib
Licenciamento de software111032100100501000001
Consultoria em TI115011000100301000001
Desenvolvimento de software115012000100301000001
Suporte técnico em TI115013000050101000001
Serviços contábeis118210100100301200044
Serviços jurídicos118110100100301200044

Exemplo Completo: Licenciamento de Software

from abstra.connectors import run_connection_action

def emitir_nfse_software(
cnpj_prestador: str,
ccm_prestador: int,
numero_rps: int,
valor: float,
descricao: str,
cnpj_tomador: str = None,
razao_tomador: str = None
):
"""
Emite NFS-e para serviço de licenciamento de software.
"""
from datetime import date

hoje = date.today().isoformat()

rps = {
"chave_rps": {
"inscricao_prestador": ccm_prestador,
"serie_rps": "NF",
"numero_rps": numero_rps
},
"tipo_rps": "RPS",
"data_emissao": hoje,
"status_rps": "N",
"tributacao_rps": "T",
"valor_inicial_cobrado": valor,
"valor_deducoes": 0.0,
"valor_pis": 0.0,
"valor_cofins": 0.0,
"valor_inss": 0.0,
"valor_ir": 0.0,
"valor_csll": 0.0,
"valor_ipi": 0.0,
"codigo_servico": 105, # 01.05 - Licenciamento de software
"aliquota": 0.02, # 2% ISS
"iss_retido": False,
"discriminacao": descricao,
"exigibilidade_suspensa": 0,
"pagamento_parcelado_antecipado": 0,
"nbs": "111032100", # Licenciamento de direitos de software
"c_loc_prestacao": 3550308, # São Paulo
"ibscbs": {
"fin_nfse": 0,
"ind_final": 0,
"c_ind_op": "100501", # Bens imateriais - domicílio adquirente
"ind_dest": 0,
"valores": {
"trib": {
"g_ibscbs": {
"c_class_trib": "000001" # Tributação integral
}
}
}
}
}

# Adicionar tomador se informado
if cnpj_tomador:
rps["cpf_cnpj_tomador"] = {"cnpj": cnpj_tomador}
if razao_tomador:
rps["razao_social_tomador"] = razao_tomador

params = {
"cabecalho": {
"cpf_cnpj_remetente": {"cnpj": cnpj_prestador},
"transacao": True,
"dt_inicio": hoje,
"dt_fim": hoje,
"qtd_rps": 1,
"versao": 2
},
"rps": [rps]
}

return run_connection_action("nota-paulistana", "envio_lote_rps", params)


# Uso
result = emitir_nfse_software(
cnpj_prestador="12345678000199",
ccm_prestador=12345678,
numero_rps=1001,
valor=1500.00,
descricao="Licença mensal de uso de software SaaS - Período: Jan/2025",
cnpj_tomador="98765432000188",
razao_tomador="Cliente Exemplo Ltda"
)

if result["data"]["retorno_envio_lote_rps"]["cabecalho"]["sucesso"]:
print("NFS-e emitida com sucesso!")
else:
erros = result["data"]["retorno_envio_lote_rps"].get("erro", [])
for erro in erros:
print(f"Erro {erro['codigo']}: {erro['descricao']}")

Testando Antes de Emitir

Use a ação teste_envio_lote_rps para validar seus parâmetros sem gerar uma NFS-e real:

# Mesmos parâmetros, ação diferente
result = run_connection_action(
"nota-paulistana",
"teste_envio_lote_rps", # Ação de teste
params
)

if result["data"]["retorno_envio_lote_rps"]["cabecalho"]["sucesso"]:
print("Validação OK! Pode enviar em produção.")
else:
print("Erros encontrados:")
for erro in result["data"]["retorno_envio_lote_rps"].get("erro", []):
print(f" - {erro['codigo']}: {erro['descricao']}")

Enviando Múltiplos RPS

from datetime import date

def emitir_lote_nfse(cnpj_prestador: str, ccm_prestador: int, lista_servicos: list):
"""
Emite múltiplas NFS-e em um único lote.

lista_servicos: [{"numero_rps": 1001, "valor": 100.0, "descricao": "..."}]
"""
hoje = date.today().isoformat()

rps_list = []
for idx, servico in enumerate(lista_servicos):
rps = {
"chave_rps": {
"inscricao_prestador": ccm_prestador,
"serie_rps": "NF",
"numero_rps": servico["numero_rps"]
},
"tipo_rps": "RPS",
"data_emissao": hoje,
"status_rps": "N",
"tributacao_rps": "T",
"valor_inicial_cobrado": servico["valor"],
"valor_deducoes": 0.0,
"valor_pis": 0.0,
"valor_cofins": 0.0,
"valor_inss": 0.0,
"valor_ir": 0.0,
"valor_csll": 0.0,
"valor_ipi": 0.0,
"codigo_servico": 105,
"aliquota": 0.02,
"iss_retido": False,
"discriminacao": servico["descricao"],
"exigibilidade_suspensa": 0,
"pagamento_parcelado_antecipado": 0,
"nbs": "111032100",
"c_loc_prestacao": 3550308,
"ibscbs": {
"fin_nfse": 0,
"ind_final": 0,
"c_ind_op": "100501",
"ind_dest": 0,
"valores": {
"trib": {
"g_ibscbs": {
"c_class_trib": "000001"
}
}
}
}
}
rps_list.append(rps)

params = {
"cabecalho": {
"cpf_cnpj_remetente": {"cnpj": cnpj_prestador},
"transacao": True, # True = tudo ou nada
"dt_inicio": hoje,
"dt_fim": hoje,
"qtd_rps": len(rps_list),
"versao": 2
},
"rps": rps_list
}

return run_connection_action("nota-paulistana", "envio_lote_rps", params)

Tratamento de Erros

def processar_resultado(result):
"""Processa o resultado do envio de lote."""
data = result.get("data", {})
retorno = data.get("retorno_envio_lote_rps", {})
cabecalho = retorno.get("cabecalho", {})

if cabecalho.get("sucesso"):
info_lote = cabecalho.get("informacoes_lote", {})
print(f"Lote processado com sucesso!")
print(f" Número do lote: {info_lote.get('numero_lote')}")
print(f" Notas processadas: {info_lote.get('qtd_notas_processadas')}")
return True
else:
erros = retorno.get("erro", [])
if not isinstance(erros, list):
erros = [erros]

print("Erros no processamento:")
for erro in erros:
codigo = erro.get("codigo")
descricao = erro.get("descricao")
chave = erro.get("chave_rps", {})

print(f" [{codigo}] {descricao}")
if chave:
print(f" RPS: {chave.get('serie_rps')}-{chave.get('numero_rps')}")

return False

Erros Comuns e Soluções

Erro 1001: XML não compatível com Schema

Causa: Falta o bloco ibscbs ou campos obrigatórios.

Solução: Adicione o bloco ibscbs completo ao RPS.

Erro 1206: Assinatura Digital do RPS incorreta

Causa: Problema na geração da assinatura.

Solução:

  • Não passe o campo assinatura manualmente
  • Verifique se o certificado está válido
  • Confirme que chave_rps está correto

Erro 628: Classificação tributária inexistente

Causa: Código c_class_trib inválido.

Solução: Use um código válido de 6 dígitos. Para tributação integral, use 000001.

Erro 630: Código indicador da operação inexistente

Causa: Código c_ind_op inválido.

Solução: Consulte a tabela de códigos cIndOp e use o código correto para seu tipo de serviço.

Erro 286: Preenchimento do campo código NBS deve ser obrigatório

Causa: Campo nbs ausente ou vazio.

Solução: Adicione o código NBS correto (9 dígitos sem pontos).

Consultando NFS-e Emitida

Após o envio bem-sucedido, consulte as NFS-e geradas:

def consultar_lote(cnpj: str, numero_lote: int):
"""Consulta as NFS-e geradas de um lote."""
result = run_connection_action(
"nota-paulistana",
"consulta_lote",
{
"cabecalho": {
"cpf_cnpj_remetente": {"cnpj": cnpj},
"numero_lote": numero_lote,
"versao": "2"
}
}
)
return result

Recursos