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ódigo | Tipo de Operação | Local de Incidência |
|---|---|---|
020101 | Operações com bem imóvel | Local do imóvel |
030101 | Serviços presenciais sobre pessoa | Local da prestação |
040101 | Eventos (feiras, congressos) | Local do evento |
050101 | Serviços sobre bem móvel material | Local da prestação |
100301 | Demais serviços onerosos | Domicílio do adquirente |
100501 | Bens imateriais/direitos onerosos | Domicílio do adquirente |
Para serviços de software/licenciamento, use 100501 (bens imateriais).
Códigos cClassTrib (Classificação Tributária)
| Código | Descrição |
|---|---|
000001 | Tributação integral IBS/CBS |
200011 | Serviços de P&D por ICT (reduzido) |
200044 | Serviços profissionais (contabilidade, advocacia, etc.) |
400001 | Transporte 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ço | NBS | cIndOp | cClassTrib |
|---|---|---|---|
| Licenciamento de software | 111032100 | 100501 | 000001 |
| Consultoria em TI | 115011000 | 100301 | 000001 |
| Desenvolvimento de software | 115012000 | 100301 | 000001 |
| Suporte técnico em TI | 115013000 | 050101 | 000001 |
| Serviços contábeis | 118210100 | 100301 | 200044 |
| Serviços jurídicos | 118110100 | 100301 | 200044 |
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
assinaturamanualmente - Verifique se o certificado está válido
- Confirme que
chave_rpsestá 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