Acesso Externo via API (Web Service)

Índice

Apresentação

Visando atender a necessidade dos contribuintes que precisam enviar ou consultar muitos registros, ou que desejam automatizar o acesso fazendo integração nos seus próprios sistemas, a prefeitura disponibiliza um acesso externo via API (Web Service).

Ambiente de Teste e Produção

O usuário tem à disposição dois ambientes, um somente para realizar testes, chamado de Ambiente de Teste, acessível através da URL https://meu_municipio.teste.joinfiscal.com.br , e o outro chamado Ambiente de Produção, acessível através da URL https://meu_municipio.joinfiscal.com.br . Antes de submeter qualquer dado para o Ambiente de Produção, é altamente recomendável utilizar primeiro o Ambiente de Teste para entender como a API funciona.

Qualquer usuário cadastrado no sistema pode fazer o acesso via API, desde que tenha a devida permissão para fazer operações de consulta, criação, atualização ou cancelamento sobre o registro em questão. Se ainda não está cadastrado, faça o seu cadastro antes de continuar, bastando acessar os links postados anteriormente. Observe que no Ambiente de Produção, é necessário aguardar a aprovação do cadastro para acessar o sistema (e consequentemente a API).

Autenticação

Para fazer a comunicação com a API, o usuário pode usar qualquer linguagem de programação capaz de fazer chamadas XML-RPC. Também é necessário criar uma Chave de API, bastando acessar o menu Perfil > Preferências:

Acessando aba Segurança da Conta:

Clicando no botão para criar uma Nova Chave de API:

O usuário pode criar quantas chaves quiser, bem como pode revogar o acesso para as chaves existentes. Isso é muito útil para fornecer acesso à sistemas de terceiros, sem precisar informar a sua senha de acesso ao sistema (login).

De posse do nome de usuário (o mesmo usado para fazer login no sistema) e da chave de API (recém criada anteriormente), basta preencher os seguintes parâmetros:

                              
# Ambiente de Produção
# url = 'https://meu_municipio.joinfiscal.com.br'

# Ambiente de Teste
url = 'https://meu_municipio.teste.joinfiscal.com.br'


banco = 'meu_municipio'
usuario = 'inserir nome de usuário'
chave = 'inserir chave da API'

                            
                              
// Ambiente de Produção
// final String url = "https://meu_municipio.joinfiscal.com.br";

// Ambiente de Teste
final String url = "https://meu_municipio.teste.joinfiscal.com.br";

final String db = "meu_municipio",
       username = "nserir nome de usuário",
       password = "inserir chave da API";

                            
                              
# Ambiente de Produção
# url = 'https://meu_municipio.joinfiscal.com.br'

# Ambiente de Teste
url = 'https://meu_municipio.teste.joinfiscal.com.br'

db = 'meu_municipio'
username = 'inserir nome de usuário'
password = 'inserir chave da API'

                            

Utilize os parâmetros anteriores para autenticar:

                              
import xmlrpc.client

# ==========================
# configuracoes
# ==========================
URL = 'https://teresina.joinfiscal.com.br'  # endereco eletrônico do sistema
BANCO = 'teresina'  # nome do municipio, de acordo com o endereco eletronico
USUARIO = '000.000.000-00'  # login, CPF ou CNPJ
CHAVE = 'aafd1ed92dbe5274ceff4d0ef51e415645de9123'  # exemplo de chave de API

 def conectar():
   common = xmlrpc.client.ServerProxy(f'{URL}/xmlrpc/2/common')
   uid = common.authenticate(BANCO, USUARIO, CHAVE, {})

   if not uid:
       raise Exception('falha na autenticacao. verifique usuario ou chave')

   models = xmlrpc.client.ServerProxy(f'{URL}/xmlrpc/2/object')
   return uid, models


# ==========================
# funcao auxiliar
# ==========================
def call(models, uid, model, method, args=None, kwargs=None):
    if args is None:
        args = []
    if kwargs is None:
        kwargs = {}

    return models.execute_kw(
        BANCO,
        uid,
        CHAVE,
        model,
        method,
        args,
        kwargs
    )


                            

Note que a biblioteca xmlrpc já vem instalada por padrão em qualquer ambiente Python, não sendo necessária nenhuma dependência externa para realizar a comunicação.

                              
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.XmlRpcException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

final XmlRpcClient client = new XmlRpcClient();

final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl();
common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url)));

int uid = (int)client.execute(
    common_config, "authenticate",
    Arrays.asList(db, username, password, Collections.emptyMap())
);
final XmlRpcClient models = new XmlRpcClient() {{
    setConfig(new XmlRpcClientConfigImpl() {{
        setServerURL(new URL(String.format("%s/xmlrpc/2/object", url)));
    }});
}};

                            
                              
require "xmlrpc/client"

common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common")
uid = common.call('authenticate', db, username, password, {})
models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy

                            

Note que a biblioteca xmlrpc já vem instalada por padrão em qualquer ambiente Ruby, não sendo necessária nenhuma dependência externa para realizar a comunicação.

Consultar Notas Fiscais

Liste as dez primeiras identificações id, nome de notas fiscais do usuário:

                              
def main():
  uid, models = conectar()

  # exemplo: buscar 10 notas fiscais
  notas = call(
      models,
      uid,
      'prefeitura.nota_fiscal',
      'search_read',
      [[]],
      {
          'fields': ['id', 'name'],
          'limit': 10
      }
  )

                            
                              
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "search",
    Arrays.asList(Collections.emptyList()),
    new HashMap() {{ put("limit", 10); }}
)));

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_read', [[]],
                  {limit: 10})

                            

Exemplo de resultado:

                            
{'id': 6207, 'name': 'NF-2024-933-00007'}
{'id': 6352, 'name': 'NF-2024-933-00009'}
{'id': 5668, 'name': 'NF-2024-933-00004'}
{'id': 4958, 'name': 'NF-2023-933-00002'}
{'id': 6848, 'name': 'NF-2024-933-00012'}
{'id': 7268, 'name': 'NF-2024-933-00015'}
{'id': 5884, 'name': 'NF-2024-933-00005'}
{'id': 5486, 'name': 'NF-2023-933-00006'}
{'id': 4820, 'name': 'NF-2023-933-00001'}
{'id': 9931, 'name': 'NF-2025-933-00009'}

                          

Pula os três primeiros registros e lista as sete identificações id da sequência de notas fiscais (utilize offset e limit para controlar a paginação):

                              
notas = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'search_read',
        [[]],
        {
            'fields': ['id', 'name'],
            'offset': 3,
            'limit': 7
        }
    )

                            
                              
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "search",
    Arrays.asList(Collections.emptyList()),
    new HashMap() {{ put("offset", 3); put("limit", 7); }}
)));

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_read', [[]],
                  {offset: 3, limit: 7})

                            

Exemplo de resultado:

                            
{'id': 11782, 'name': 'NF-2026-2060-00001'}
{'id': 11745, 'name': 'NF-2025-2060-00001'}
{'id': 11783, 'name': 'NF-2026-933-00001'}
{'id': 4958, 'name': 'NF-2023-933-00002'}
{'id': 5486, 'name': 'NF-2023-933-00006'}
{'id': 5668, 'name': 'NF-2024-933-00004'}
{'id': 5884, 'name': 'NF-2024-933-00005'}

                          

Utilize filtros para listar as dez primeiras identificações id de notas fiscais já canceladas, em ordem decrescente pela data de cancelamento (data_hora_cancelamento):

                              
notas = call(
    models,
    uid,
    'prefeitura.nota_fiscal',
    'search_read',
    [
        [
            ['state', '=', 'CANCELADA']
         ]
    ],
    {
        'fields': ['id', 'name', 'state', 'data_hora_cancelamento'],
        'order': 'data_hora_cancelamento DESC',
        'limit': 10
    }
)

                            
                              
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "search",
    Arrays.asList(Arrays.asList(Arrays.asList("state", "=", "CANCELADA"))),
    new HashMap() {{
        put("limit", 10);
        put("order", "data_hora_cancelamento DESC");
    }}
)));

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_read',
                  [[['state', '=', 'CANCELADA']]],
                  {limit: 10, order: 'data_hora_cancelamento DESC'})

                            

Exemplo de resultado:

                            
{'id': 11773, 'name': 'NF-2025-1160-00037', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-30 12:39:08'}
{'id': 11648, 'name': 'NF-2025-2025-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-23 16:39:41'}
{'id': 11772, 'name': 'NF-2025-1160-00036', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-23 16:33:30'}
{'id': 11417, 'name': 'NF-2025-1808-00005', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-23 15:00:45'}
{'id': 11736, 'name': 'NF-2025-2050-00001', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-19 12:36:32'}
{'id': 11724, 'name': 'NF-2025-2015-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-18 13:33:13'}
{'id': 11697, 'name': 'NF-2025-1812-00016', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 13:36:11'}
{'id': 11695, 'name': 'NF-2025-416-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 12:35:28'}
{'id': 11555, 'name': 'NF-2025-204-00007', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 11:35:38'}
{'id': 11407, 'name': 'NF-2025-1214-00002', 'state': 'CANCELADA', 'data_hora_cancelamento': '2025-12-16 11:32:53'}

                          

Utilize o método search_count para contar a quantidade de registros de um determinado filtro:

                              
notas = call(
    models,
    uid,
    'prefeitura.nota_fiscal',
    'search_count',
    [
        [
            ['state', '=', 'CANCELADA']
        ]
    ]
)

                            
                              
(Integer)models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "search_count",
    Arrays.asList(Arrays.asList(Arrays.asList("state", "=", "CANCELADA")))
));

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'search_count',
                  [[['state', '=', 'CANCELADA']]])

                            

Exemplo de resultado:

                            
23

                          

Obtenha todos os nomes dos campos, tipos e descrições de ajuda de um determinado model, sendo nesse caso prefeitura.nota_fiscal (funciona para inspecionar qualquer model):

                              
campos = call(
    models,
    uid,
    'prefeitura.nota_fiscal',
    'fields_get',
    [],
    {
        'attributes': [
            'string',
            'help',
            'type',
            'required',
            'selection',
            'relation'
        ]
    }
)

                            

Exemplo de resultado:

                                
'autenticidade_codigo': {'required': False, 'string': 'Código', 'type': 'char'}
'autenticidade_valida': {'required': False, 'string': 'É Válida', 'type': 'boolean'}
'legislacao_atual': {'required': False, 'selection': [['LEGISLACAO_I', 'CTM 2019'], ['LEGISLACAO_II', 'CTM 2023']], 'string': 'Legislação Atual', 'type': 'selection'}
'pix_foi_desativado': {'help': 'Utilizado para desativar funcionalidade do PIX (caso não exista convênio ou sistema esteja offline).', 'required': False, 'string': 'Pix Foi Desativado', 'type': 'boolean'}

                              
                              
(Map<String, Map<String, Object>>)models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "fields_get",
    Collections.emptyList(),
    new HashMap() {{
        put("attributes", Arrays.asList("string", "help", "type", "required",
                                        "selection", "relation"));
    }}
));

                            

Exemplo de resultado:

                                
{
    'name': {'type': 'char',
             'required': False,
             'string': 'Número da Nota'},
    'state': {'type': 'selection',
              'required': False,
              'selection': [['RASCUNHO', 'Rascunho'],
                            ['CONCLUIDO', 'Emitida'],
                            ['CANCELADA', 'Cancelada']],
              'string': 'Estado'},
    'prestador_id': {'type': 'many2one',
                     'help': 'Identificação do PRESTADOR.\n É importante que a Atividade Econômica esteja previamente registrada no cadastro do Prestador.',
                     'required': True,
                     'string': 'Prestador de Serviço',
                     'relation': 'res.partner'},
    'tomador_id': {'type': 'many2one',
                   'help': "Identificação do TOMADOR.\n Para criar um novo registro basta digitar o nome do prestador e teclar 'Enter'. \nLogo após, uma tela será exibida para completar o cadastro. \nDá próxima vez este tomador estará disponível para seleção.",
                   'required': True,
                   'string': 'Tomador de Serviço',
                   'relation': 'prefeitura.tomador'},
    'city_id': {'type': 'many2one',
                'required': True,
                'string': 'Local de Prestação do Serviço',
                'relation': 'res.city'},
    'description': {'type': 'text',
                    'help': 'Escreva aqui informações sobre os serviços prestados.',
                    'required': False,
                    'string': 'Descrição'},
    'competencia': {'type': 'date',
                    'help': 'Selecione uma data dentro do mês corrente. ',
                    'required': True,
                    'string': 'Competência'},
    'emissao': {'type': 'datetime',
                'required': False,
                'string': 'Data de Emissão'},
    'data_hora_cancelamento': {'type': 'datetime',
                               'required': False,
                               'string': 'Data de Cancelamento'},
    'cnae_ids': {'type': 'many2many',
                 'help': 'Selecione entre os CNAEs informados no cadastro do prestador.',
                 'required': False,
                 'string': 'CNAE',
                 'relation': 'prefeitura.cnae'},
    'servico_ids': {'type': 'many2many',
                    'help': 'Selecione entre os serviços informados no cadastro do prestador.',
                    'required': True,
                    'string': 'Serviços',
                    'relation': 'prefeitura.servico'},
    'local_incidencia_iss': {'type': 'selection',
                             'required': False,
                             'selection': [['PRESTADOR', 'Local do Prestador'],
                                           ['TOMADOR', 'Local do Tomador'],
                                           ['SERVICO', 'Local da prestação de serviço']],
                             'string': 'Local de Incidência do ISS'},
    'tipo_recolhimento': {'type': 'selection',
                          'required': False,
                          'selection': [['ISS_RETIDO', 'ISS retido.'],
                                        ['ISS_A_RECOLHER', 'ISS a recolher.'],
                                        ['SEM_INCIDENCIA', 'Sem incidência.']],
                          'string': 'Tipo de Recolhimento'},
    'item_ids': {'type': 'one2many',
                 'help': 'Especifique os serviços prestados informando a quantidade e valor unitário para cada ítem.',
                 'required': False,
                 'string': 'Itens',
                 'relation': 'prefeitura.item_nota_fiscal'},
    'item_total': {'type': 'float',
                   'required': False,
                   'string': 'Total dos Itens'},
    'base_calculo': {'type': 'monetary',
                     'help': 'Valor utilizado para a base de cálculo do ISS.',
                     'required': False,
                     'string': 'Base de Cálculo'},
    'aliquota_iss': {'type': 'float',
                     'help': 'Alíquota estabelecida pelo CTM.',
                     'required': False,
                     'string': 'Alíquota do ISS (%)'},
    'valor_iss': {'type': 'monetary',
                  'required': False,
                  'string': 'Valor do ISS'},
    'valor_total_deducoes': {'type': 'monetary',
                             'required': False,
                             'string': 'Valor Total das Retenções'},
    'nome_arquivo_pdf': {'type': 'char',
                         'help': 'Nome do arquivo PDF.',
                         'required': False,
                         'string': 'Nome do Arquivo PDF'},
    'arquivo_pdf': {'type': 'binary',
                    'help': 'Este arquivo permanecerá como RASCUNHO até a confirmação do pagamento da guia de ISS. \n Exceto para contribuintes optantes pelo Simples Nacional.',
                    'required': False,
                    'string': 'Arquivo PDF'},
    'nome_arquivo_xml': {'type': 'char',
                         'help': 'Nome do arquivo XML.',
                         'required': False,
                         'string': 'Nome do Arquivo XML'},
    'arquivo_xml': {'type': 'binary',
                    'help': 'Arquivo no formato XML.',
                    'required': False,
                    'string': 'Arquivo XML'},
    'boleto_id': {'type': 'many2one', 'required': False, 'string': 'Boleto', 'relation': 'prefeitura.boleto'},
    'boleto_state': {'type': 'selection',
                     'required': False,
                     'selection': [['RASCUNHO', 'Rascunho'],
                                   ['ABERTO', 'Emitido'],
                                   ['VENCIDO', 'Vencido'],
                                   ['PAGO', 'Pago'],
                                   ['CANCELADO', 'Cancelado']],
                     'string': 'Estado do Boleto'},
    'possui_pagamento_pendente': {'type': 'boolean',
                                  'required': False,
                                  'string': 'Possui Pagamento Pendente'},
}

                              
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal',
                  'fields_get', [],
                  {attributes: %w(string help type required selection relation)})

                            

Exemplo de resultado:

                                
{
    "name" => {
        "type" => "char",
        "required" => false,
        "string" => "Número da Nota"},
    "state" => {
        "type" => "selection",
        "required" => false,
        "selection" => [
            ["RASCUNHO", "Rascunho"],
            ["CONCLUIDO", "Emitida"],
            ["CANCELADA", "Cancelada"]
        ],
        "string" => "Estado"},
    "prestador_id" => {
        "type" => "many2one",
        "help" => "Identificação do PRESTADOR.\n É importante que a Atividade Econômica esteja previamente registrada no cadastro do Prestador.",
        "required" => true,
        "string" => "Prestador de Serviço",
        "relation" => "res.partner"},
    "tomador_id" => {
        "type" => "many2one",
        "help" => "Identificação do TOMADOR.\n Para criar um novo registro basta digitar o nome do prestador e teclar 'Enter'. \nLogo após, uma tela será exibida para completar o cadastro. \nDá próxima vez este tomador estará disponível para seleção.",
        "required" => true,
        "string" => "Tomador de Serviço",
        "relation" => "prefeitura.tomador"},
    "city_id" => {
        "type" => "many2one",
        "required" => true,
        "string" => "Local de Prestação do Serviço",
        "relation" => "res.city"},
    "description" => {
        "type" => "text",
        "help" => "Escreva aqui informações sobre os serviços prestados.",
        "required" => false,
        "string" => "Descrição"},
    "competencia" => {
        "type" => "date",
        "help" => "Selecione uma data dentro do mês corrente. ",
        "required" => true,
        "string" => "Competência"},
    "emissao" => {
        "type" => "datetime",
        "required" => false,
        "string" => "Data de Emissão"},
    "data_hora_cancelamento" => {
        "type" => "datetime",
        "required" => false,
        "string" => "Data de Cancelamento"},
    "cnae_ids" => {
        "type" => "many2many",
        "help" => "Selecione entre os CNAEs informados no cadastro do prestador.",
        "required" => false,
        "string" => "CNAE",
        "relation" => "prefeitura.cnae"},
    "servico_ids" => {
        "type" => "many2many",
        "help" => "Selecione entre os serviços informados no cadastro do prestador.",
        "required" => true,
        "string" => "Serviços",
        "relation" => "prefeitura.servico"},
    "local_incidencia_iss" => {
        "type" => "selection",
        "required" => false,
        "selection" => [
            ["PRESTADOR", "Local do Prestador"],
            ["TOMADOR", "Local do Tomador"],
            ["SERVICO", "Local da prestação de serviço"]
        ],
        "string" => "Local de Incidência do ISS"},
    "tipo_recolhimento" => {
        "type" => "selection",
        "required" => false,
        "selection" => [
            ["ISS_RETIDO", "ISS retido."],
            ["ISS_A_RECOLHER", "ISS a recolher."],
            ["SEM_INCIDENCIA", "Sem incidência."]
        ],
        "string" => "Tipo de Recolhimento"},
    "item_ids" => {
        "type" => "one2many",
        "help" => "Especifique os serviços prestados informando a quantidade e valor unitário para cada ítem.",
        "required" => false,
        "string" => "Itens",
        "relation" => "prefeitura.item_nota_fiscal"},
    "item_total" => {
        "type" => "float",
        "required" => false,
        "string" => "Total dos Itens"},
    "base_calculo" => {
        "type" => "monetary",
        "help" => "Valor utilizado para a base de cálculo do ISS.",
        "required" => false,
        "string" => "Base de Cálculo"},
    "aliquota_iss" => {
        "type" => "float",
        "help" => "Alíquota estabelecida pelo CTM.",
        "required" => false,
        "string" => "Alíquota do ISS (%)"},
    "valor_iss" => {
        "type" => "monetary",
        "required" => false,
        "string" => "Valor do ISS"},
    "valor_total_deducoes" => {
        "type" => "monetary",
        "required" => false,
        "string" => "Valor Total das Retenções"},
    "nome_arquivo_pdf" => {
        "type" => "char",
        "help" => "Nome do arquivo PDF.",
        "required" => false,
        "string" => "Nome do Arquivo PDF"},
    "arquivo_pdf" => {
        "type" => "binary",
        "help" => "Este arquivo permanecerá como RASCUNHO até a confirmação do pagamento da guia de ISS. \n Exceto para contribuintes optantes pelo Simples Nacional.",
        "required" => false,
        "string" => "Arquivo PDF"},
    "nome_arquivo_xml" => {
        "type" => "char",
        "help" => "Nome do arquivo XML.",
        "required" => false,
        "string" => "Nome do Arquivo XML"},
    "arquivo_xml" => {
        "type" => "binary",
        "help" => "Arquivo no formato XML.",
        "required" => false,
        "string" => "Arquivo XML"},
    "boleto_id" => {
        "type" => "many2one",
        "required" => false,
        "string" => "Boleto",
        "relation" => "prefeitura.boleto"},
    "boleto_state" => {
        "type" => "selection",
        "required" => false,
        "selection" => [
            ["RASCUNHO", "Rascunho"],
            ["ABERTO", "Emitido"],
            ["VENCIDO", "Vencido"],
            ["PAGO", "Pago"],
            ["CANCELADO", "Cancelado"]
        ],
        "string" => "Estado do Boleto"},
    "possui_pagamento_pendente" => {
        "type" => "boolean",
        "required" => false,
        "string" => "Possui Pagamento Pendente"
    }
}

                              

Emitir Notas Fiscais

O processo segue o mesmo procedimento de emissão de nota fiscal utilizando o formulário no navegador, primeiro é necessário criar o registro, que por padrão estará no estado RASCUNHO. Nessa fase, o usuário pode realizar alterações e correções nos dados submetidos. Após tudo conferido, o usuário executa a ação de Emitir, que mudará o estado do registro para CONCLUIDO (emitido).

Antes de realizar a criação da nota fiscal, é preciso definir os valores dos campos obrigatórios. Abaixo está uma lista completa dos campos necessários:

Campo Tipo Obrigatório Descrição
prestador_id Many2one Padrão Prestador de Serviço. O sistema obtém automaticamente baseado no usuário logado. Não é necessário informar via API.
tomador_id Many2one Sim Tomador de Serviço. ID do registro em prefeitura.tomador. Deve ser buscado antes da criação.
city_id Many2one Sim Local da Prestação do Serviço. ID do registro em res.city. Deve ser buscado antes da criação.
competencia Date Sim Data de competência da nota fiscal. Formato: 'YYYY-MM-DD'. Deve estar dentro do mês corrente (ou até 30 dias retroativos).
servico_ids Many2many Padrão Serviços. O sistema obtém automaticamente baseado no prestador. Para emissão, deve conter exatamente 1 serviço.
servico_nbs_id Many2one Emissão Serviço NBS (IBS). Obrigatório apenas na emissão da nota. Deve estar cadastrado no prestador.
item_ids One2many Emissão Itens da Nota Fiscal. Obrigatório ter pelo menos 1 item antes da emissão. Cada item deve ter: name (descrição), price (preço unitário) e quantity (quantidade).
tipo_recolhimento Selection Emissão Tipo de Recolhimento. Obrigatório na emissão. Valores: 'ISS_RETIDO', 'ISS_A_RECOLHER' ou 'SEM_INCIDENCIA'. Geralmente calculado automaticamente.
description Text Não Descrição adicional sobre os serviços prestados. Campo opcional.
cnae_ids Many2many Não CNAEs. O sistema obtém automaticamente baseado no prestador. Campo opcional.
base_calculo Monetary Não Base de Cálculo. Calculado automaticamente como soma dos itens. Pode ser editado se permite_deducao for True.

Notas importantes:

  • Campos com padrão automático: prestador_id e servico_ids são obtidos automaticamente do usuário logado, não é necessário informá-los.
  • Campos obrigatórios para criação: Apenas tomador_id, city_id e competencia são obrigatórios na criação do rascunho.
  • Campos obrigatórios para emissão: Além dos campos acima, na emissão também são necessários: item_ids (pelo menos 1 item), servico_nbs_id e tipo_recolhimento.
  • Itens da Nota: Cada item deve ter name (descrição), price (preço unitário) e quantity (quantidade, padrão: 1). O campo total é calculado automaticamente.
  • Serviços: Na emissão, deve haver exatamente 1 serviço selecionado em servico_ids.
  • Competência: A data deve estar dentro do mês corrente ou até 30 dias retroativos (exceto para administradores que podem ir até 90 dias).

Exemplo de busca dos campos obrigatórios antes da criação:

                              
uid, models = conectar()

# Buscar tomador
tomador_result = call(
    models,
    uid,
    'prefeitura.tomador',
    'search',
    [[('name', 'ilike', 'peças')]],
    {'limit': 1}
)

if not tomador_result:
    raise Exception('nenhum tomador encontrado')

tomador_id = tomador_result[0]

# Buscar cidade
city_result = call(
    models,
    uid,
    'res.city',
    'search',
    [[
        ('name', 'ilike', 'riachão'),
        ('state_id.name', 'ilike', 'maranhão'),
    ]],
    {'limit': 1}
)

if not city_result:
    raise Exception('nenhuma cidade encontrada')

city_id = city_result[0]

                            
                              
Object tomadorId = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.tomador", "search",
    Arrays.asList(Arrays.asList(Arrays.asList("name", "ilike", "peças"))),
    new HashMap() {{
        put("limit", 1);
    }}
))).get(0);

Object cityId = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "res.city", "search",
    Arrays.asList(Arrays.asList(
        Arrays.asList("name", "ilike", "riachão"),
        Arrays.asList("state_id.name", "ilike", "maranhão")
    )),
    new HashMap() {{
        put("limit", 1);
    }}
))).get(0);

                            
                              
uid, models = conectar()

tomador_id = call(
    models,
    uid,
    'prefeitura.tomador',
    'search',
    [[['name', 'ilike', 'peças']]],
    {limit: 1}
)

if tomador_id.empty?
    raise 'nenhum tomador encontrado'
end

city_id = call(
    models,
    uid,
    'res.city',
    'search',
    [[
        ['name', 'ilike', 'riachão'],
        ['state_id.name', 'ilike', 'maranhão'],
    ]],
    {limit: 1}
)

if city_id.empty?
    raise 'nenhuma cidade encontrada'
end

                            

Observe que os comandos anteriores estão realizando consulta de dados em diferentes tabelas. A cidade está sendo consultada na tabela res.city, procurando o primeiro registro que possua o nome riachão em que está localizado no estado maranhão. O tomador é localizado na tabela prefeitura.tomador, a consulta procura o primeiro registro que possua o nome peças. Note que foi utilizado o operador ilike. É possível usar qualquer um dos seguintes operadores:

Operador Descrição
= Igual.
!= Diferente.
> Maior.
>= Maior ou igual.
< Menor.
<= Menor ou igual.
=? Indefinido ou igual (retorna true se valor é None ou False, do contrário se comporta como =).
=like Corresponde field_name no seguinte padrão de valor: um sublinhado _ no padrão significa (corresponde) qualquer caractere único; e um sinal de porcentagem % corresponde qualquer string de zero ou mais caracteres.
like Corresponde field_name no padrão %valor%. Parecido com =like mas embrulha o valor com % antes de corresponder.
not like Não corresponde no padrão %value%.
ilike Caso maúsculo ou minúsculo de like.
not ilike Caso maúsculo ou minúsculo de not like.
=ilike Caso maúsculo ou minúsculo de =like.
in É igual a qualquer um dos itens da lista (nos casos do valor ser uma lista).
not in É diferente de qualquer um dos itens da lista (nos casos do valor ser uma lista).

Exemplo completo de como criar uma nota fiscal(RASCUNHO)

                              
uid, models = conectar()

    # ==========================
    # 1. Conectar ao sistema
    # ==========================
    uid, models = conectar()

    # ==========================
    # 2. Buscar tomador
    # ==========================
    tomador_result = call(
        models,
        uid,
        'prefeitura.tomador',
        'search',
        [[('name', 'ilike', 'Prefeitura de Riachao ')]],
        {'limit': 1}
    )

    if not tomador_result:
        raise Exception('Tomador não encontrado')

    tomador_id = tomador_result[0]

    # ==========================
    # 3. Buscar cidade
    # ==========================
    city_result = call(
        models,
        uid,
        'res.city',
        'search',
        [[
            ('name', 'ilike', 'Riachão'),
            ('state_id.name', 'ilike', 'Maranhão'),
        ]],
        {'limit': 1}
    )

    if not city_result:
        raise Exception('Cidade não encontrada')

    city_id = city_result[0]

    # ==========================
    # 4. Criar nota fiscal (rascunho)
    # ==========================
    from datetime import date
    nota_id = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'create',
        [
            {
                'tomador_id': tomador_id,
                'city_id': city_id,
                'competencia': date.today().strftime('%Y-%m-%d'),
                'description': 'Serviços de suporte técnico mensal.',
            }
        ]
    )



                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'create', [[
    {
        tomador_id: tomador_id,
        city_id: city_id,
        competencia: '2023-02-10',
    },
    {
        tomador_id: tomador_id,
        city_id: city_id,
        competencia: '2023-02-10',
    },
]])

                            

Observe que o registros está no estado de RASCUNHO. Significa que ele podem ser alterado (ainda não foi emitido CONCLUIDO). Para adicionar um ou mais Itens da Nota, execute:

                              
nota = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'search_read',
        [[('id', '=', '5669')]],
        {
            'fields': [
                'id', 'name', 'state', 'servico_ids', 'servico_nbs_id', 'prestador_id'
            ],
            'limit': 1
        }
    )[0]
#  adiciona dois itens na nota fiscal
    call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'write',
        [
            [nota.get('id')],
            {
                'item_ids': [
                    (0, 0, {
                        'name': 'Consultoria em Desenvolvimento de Software',
                        'price': 5000.00,
                        'quantity': 1,
                    }),
                    (0, 0, {
                        'name': 'Desenvolvimento de Software',
                        'price': 3000.00,
                        'quantity': 1,
                    }),
                ]
            }
        ]
    )

                            
                              
(Boolean) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "write",
    Arrays.asList(
        Arrays.asList(notaId),
        new HashMap() {{
            put("item_ids", Arrays.asList(
                Arrays.asList(0, 0, new HashMap() {{
                    put("name", "Primeiro Item");
                    put("price", 123.45);
                    put("quantity", 2);
                }}),
                Arrays.asList(0, 0, new HashMap() {{
                    put("name", "Segundo Item");
                    put("price", 321.54);
                    put("quantity", 1);
                }})
            ));
        }}
    )
))

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'write', [[nota_id], {
    item_ids: [
        [0, 0, {
            name: "Primeiro Item",
            price: 123.45,
            quantity: 2,
        }],
        [0, 0, {
            name: "Segundo Item",
            price: 321.54,
            quantity: 1,
        }],
    ]
}])

                            

Para consultar os campos alterados dos Itens da Nota, execute:

                              
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "read",
    Arrays.asList(Arrays.asList(5669)),
    new HashMap() {{
        put("fields", Arrays.asList("id", "name", "state", "item_ids"));
    }}
)))

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'read',
                  [[nota_id]],
                  {fields: %w(id name state item_ids)})

                            
                              
nota = call(
    models,
    uid,
    'prefeitura.nota_fiscal',
    'search_read',
    [[('id', '=', nota_id)]],
    {
        'fields': [
            'id', 'name', 'state', 'item_ids'
        ],
        'limit': 1
    }
)[0]

                            

Resultado:

                              
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "read",
    Arrays.asList(Arrays.asList(notaId)),
    new HashMap() {{
        put("fields", Arrays.asList("id", "name", "state", "item_ids"));
    }}
)))

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'read',
                  [[nota_id]],
                  {fields: %w(id name state item_ids)})

                            
                            
[
    {'id': 5669, 'name': 'NF-2024-1152-00001', 'state': 'RASCUNHO', 'item_ids': [11929, 11930, 5669, 5670]}
]

                          

Observe que os comandos anteriores estão acessando os registros relacionados (itens da nota) usando o seguinte formato:

Comando Descrição
(0, 0, valores) Adiciona um novo registro, criado a partir do dicionário valores fornecido.
(1, id, valores) Atualiza um registro existente de id com os valores fornecidos.
(2, id, 0) Remove o registro de id da relação, e depois deleta da tabela de origem.
(3, id, 0) Remove o registro de id da relação, mas não deleta da tabela de origem.
(4, id, 0) Adiciona um registro existente de id na relação.
(5, 0, 0) Remove todos os registros da relação, e não os remove da tabela de origem.
(6, 0, ids) Substitui todos os registros existentes da relação pelos ids da lista, e não os remove da tabela de origem.

Finalmente, para emitir a nota fiscal, execute:

                              
result = call(
    models,
    uid,
    'prefeitura.nota_fiscal',
    'action_emitir_nota_fiscal',
    [[nota.get('id')]]
)

                            
                              
(Object) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "action_emitir_nota_fiscal",
    Arrays.asList(Arrays.asList(notaId))
))

                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal',
                  'action_emitir_nota_fiscal', [[nota_id]])

                            

Exemplo como EMITIR nota fiscal

                              
uid, models = conectar()

# ==========================
# EMITIR NOTA FISCAL
# ==========================

# ==========================
# 5. Adicionar itens à nota fiscal
# ==========================
call(
    models,
    uid,
    'prefeitura.item_nota_fiscal',
    'create',
    [{
        'nota_fiscal_id': nota_id,
        'name': 'Consultoria em Desenvolvimento de Software',
        'price': 5000.00,
        'quantity': 1,
    }]
)

# ==========================
# 6. Adicionar Serviço (IBS) à nota fiscal
# ==========================

nota = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'search_read',
        [[('id', '=', '5669')]],
        {
            'fields': [
                'id', 'name', 'state', 'servico_ids', 'servico_nbs_id', 'prestador_id'
            ],
            'limit': 1
        }
    )[0]

prestador = call(
    models,
    uid,
    'res.partner',
    'read',
    [[nota['prestador_id'][0]]],
    {'fields': ['servico_nbs_ids','servico_ids','cnae_ids']}
)

# Prestador tem N serviços nbs, mas só é possível adicionar um serviço nbs

if prestador and prestador[0].get('servico_nbs_ids'):
    servico_nbs_id = prestador[0].get('servico_nbs_ids')
    call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'write',
        [
            [nota.get('id')],
            {
                'servico_nbs_id': servico_nbs_id[0]
            }
        ]
    )



# Adicione os serciços na nota, o servico_ids contem todos os serviços do prestador, então escolha apenas o que desejar.
# Aqui estou adicionando apenas o primeiro, mas poderia adicionar todos se fossem necessário
if prestador and prestador[0].get('servico_ids'):
    servico_ids = prestador[0].get('servico_ids')
    call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'write',
        [
            [nota.get('id')],
            {
                # adiciona apenas o primeiro
                'servico_ids': [(6, 0, [servico_ids[0]])]
                # adiciona todos
                # 'servico_ids': [(6, 0, servico_ids)]
            }
        ]
    )


# Adicione os cnaes na nota, o cnae_ids todos os cnaes do prestador, então escolha apenas o que desejar.
# Aqui estou adicionando apenas o primeiro, mas poderia adicionar todos se fossem necessário
if prestador and prestador[0].get('cnae_ids'):
    cnae_id = prestador[0].get('cnae_ids')
    call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'write',
        [
            [nota.get('id')],
            {
                'cnae_ids': [(6, 0, [cnae_id[0]])]
            }
        ]
    )


try:
    result = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'action_emitir_nota_fiscal',
        [[nota.get('id')]]
    )

    # Verificar resultado
    nota_emitida = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'read',
        [[nota.get('id')]],
        {
            'fields': [
                'id', 'name', 'state', 'emissao', 'valor_iss',
                'boleto_id', 'possui_pagamento_pendente'
            ]
        }
    )[0]

    print(f'\n✅ Nota fiscal emitida com sucesso!')
    print(f'  - Número: {nota_emitida["name"]}')
    print(f'  - Estado: {nota_emitida["state"]}')
    print(f'  - Data de emissão: {nota_emitida.get("emissao", "N/A")}')
    print(f'  - Valor do ISS: R$ {nota_emitida.get("valor_iss", 0):.2f}')
    print(f'  - Possui boleto: {bool(nota_emitida.get("boleto_id"))}')

except Exception as e:
    print(f'\n❌ Erro ao emitir nota fiscal: {str(e)}')
    print('Verifique se todos os campos obrigatórios foram preenchidos.')



                            
                              
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal', 'create', [[
    {
        tomador_id: tomador_id,
        city_id: city_id,
        competencia: '2023-02-10',
    },
    {
        tomador_id: tomador_id,
        city_id: city_id,
        competencia: '2023-02-10',
    },
]])

                            

Baixar PDF do Boleto da Nota Fiscal

Após emissão da nota fiscal (p.ex. id = 5669), caso exista algum débito a ser pago, você poderá baixar o PDF do boleto de cobrança utilizando a URL do arquivo:

                              
URL = 'https://meu_municipio.teste.joinfiscal.com.br'

nota = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'search_read',
        [[('id', '=', '5669')]],
        {
            'fields': [
                'id', 'name', 'state', 'item_ids'
            ],
            'limit': 1
        }
    )[0]

result = call(
    models,
    uid,
    'prefeitura.nota_fiscal',
    'action_baixar_arquivo_pdf',
    [[nota.get('id')]]
)

download_url = f"{URL}{result['url']}"
print(download_url)

                            
                              
String baseUrl = "https://meu_municipio.teste.joinfiscal.com.br";

String path = ((Map) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal", "action_baixar_arquivo_pdf",
    Arrays.asList(Arrays.asList(4382))
))).get("url");

System.out.println(baseUrl + "/" + path);

                            
                              
url = 'https://meu_municipio.teste.joinfiscal.com.br'
path = models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal',
                         'action_baixar_arquivo_pdf', [[4382]])['url']
puts url + '/' + path

                            
                            
"https://meu_municipio.teste.joinfiscal.com.br/web/content/prefeitura.nota_fiscal/4382/arquivo_pdf/nota-fiscal-nf-2023-23-00001.pdf?download=true"

                          

Cancelar Notas Fiscais

Para cancelar uma nota fiscal emitida, é necessário informar o motivo do cancelamento. A nota fiscal deve estar no estado EMITIDA para poder ser cancelada.

                              

nota = call(
        models,
        uid,
        'prefeitura.nota_fiscal',
        'search_read',
        [[('id', '=', '5669')]],
        {
            'fields': [
                'id', 'name', 'state', 'item_ids'
            ],
            'limit': 1
        }
    )[0]

# Criar registro do diálogo de cancelamento
dialogo_id = call(
    models,
    uid,
    'prefeitura.nota_fiscal_dialogo',
    'create',
    [{
        'nota_fiscal_id': nota.get('id'),
        'motivo_cancelamento': 'Erro na emissão da nota fiscal. Necessário cancelar e reemitir.'
    }]
)

# Confirmar o cancelamento
result = call(
    models,
    uid,
    'prefeitura.nota_fiscal_dialogo',
    'confirmar_cancelamento',
    [[dialogo_id]]
)

                            
                              
// Criar registro do diálogo de cancelamento
Integer dialogoId = (Integer) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal_dialogo", "create",
    Arrays.asList(Arrays.asList(
        new HashMap() {{
            put("nota_fiscal_id", notaId);
            put("motivo_cancelamento", "Erro na emissão da nota fiscal. Necessário cancelar e reemitir.");
        }}
    ))
));

// Confirmar o cancelamento
(Boolean) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.nota_fiscal_dialogo", "confirmar_cancelamento",
    Arrays.asList(Arrays.asList(dialogoId))
))

                            
                              
# Criar registro do diálogo de cancelamento
dialogo_id = models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal_dialogo',
                               'create', [{
                                   nota_fiscal_id: nota_id,
                                   motivo_cancelamento: 'Erro na emissão da nota fiscal. Necessário cancelar e reemitir.'
                               }])

# Confirmar o cancelamento
models.execute_kw(db, uid, password, 'prefeitura.nota_fiscal_dialogo',
                  'confirmar_cancelamento', [[dialogo_id]])

                            

Consultar Faturas

Para listar todas as faturas em aberto, execute:

                              
faturas = call(
    models,
    uid,
    'account.move',
    'search_read',
    [[
        ('state', '=', 'posted'),
        ('payment_state', '=', 'not_paid')
    ]]
)

                            
                              
Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "account.move", "search",
    Arrays.asList(Arrays.asList(
        Arrays.asList("state", "=", "posted"),
        Arrays.asList("payment_state", "=", "not_paid")
    )),
    new HashMap() {{ }}
)));

                            
                              
models.execute_kw(db, uid, password, 'account.move', 'search_read', [[
    ['state', '=', 'posted'],
    ['payment_state', '=', 'not_paid'],
]], {})

                            

Exemplo de resultado:

                            
[356, 341, 134]

                          

Gerar Boleto de Faturas Selecionadas

É possível gerar um boleto para pagar uma ou mais faturas, nesse caso para as faturas ids = [356, 341, 134]. Mas primeiro, certifique-se que todas as faturas possuem o mesmo contribuinte associado:

                              
contribuintes = call(
    models,
    uid,
    'account.move',
    'search_read',
    [[('id', 'in', [356, 341, 134])]],
    {'fields': ['partner_id']}
)

for contribuinte in contribuintes:
    print(contribuinte)

                            
                              
List<Object> contribuintes = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "account.move", "search_read",
    Arrays.asList(
        Arrays.asList(Arrays.asList("id", "in", Arrays.asList(356, 341, 134))),
        Arrays.asList("partner_id")
    ),
    new HashMap() {{ }}
)));

for (Object contribuinte : contribuintes) {
    System.out.println(contribuinte);
}

                            
                              
contribuintes = models.execute_kw(db, uid, password, 'account.move', 'search_read', [[
    ['id', 'in', [356, 341, 134]],
], ['partner_id']], {})

for contribuinte in contribuintes do
  puts contribuinte
end

                            

Exemplo de resultado:

                            
{'id': 356, 'partner_id': [378, 'Felipe Silva']}
{'id': 341, 'partner_id': [378, 'Felipe Silva']}
{'id': 134, 'partner_id': [378, 'Felipe Silva']}

                          

Após confirmar que todas as faturas ids = [356, 341, 134] pertencem ao mesmo contribuinte id = 378, basta criar o boleto:

                              
result = call(
    models,
    uid,
    'prefeitura.boleto',
    'create',
    [{
        'contribuinte_id': 378,
        'move_ids': [356, 341, 134],
    }]
)

print(result)

                            
                              
List<Object> result = Arrays.asList((Object[])models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.boleto", "create",
    Arrays.asList(Arrays.asList(
        new HashMap() {{
            put("contribuinte_id", 378);
            put("move_ids", Arrays.asList(356, 341, 134));
        }}
    ))
)));

System.out.println(result);

                            
                              
result = models.execute_kw(db, uid, password, 'prefeitura.boleto', 'create', [[
    {
        contribuinte_id: 378,
        'move_ids': [356, 341, 134],
    },
]])

puts result

                            

Exemplo de resultado:

                            
[495]

                          

O boleto recém-criado estará no estado RASCUNHO. Nessa fase, o usuário pode realizar alterações e correções nos dados submetidos. Após tudo conferido, o usuário executa a ação de Emitir Boleto:

                              
result = call(
    models,
    uid,
    'prefeitura.boleto',
    'action_gerar_boleto',
    [[boleto.get('id')]]
)

print(result)

                            
                              
Object result = (Object) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.boleto", "action_gerar_boleto",
    Arrays.asList(Arrays.asList(boletoId))
));

System.out.println(result);

                            
                              
result = models.execute_kw(db, uid, password, 'prefeitura.boleto',
                           'action_gerar_boleto', [[boleto_id]])
puts result

                            

Exemplo de resultado:

                            
True

                          

Após emissão do boleto de cobrança id = 495, você poderá baixar o arquivo PDF utilizando a URL do arquivo:

                              
URL = 'https://meu_municipio.teste.joinfiscal.com.br'

result = call(
    models,
    uid,
    'prefeitura.boleto',
    'action_baixar_arquivo_pdf',
    [[boleto.get('id')]]
)

download_url = f"{URL}{result['url']}"
print(download_url)

                            
                              
String URL = "https://meu_municipio.teste.joinfiscal.com.br";

Map result = (Map) models.execute("execute_kw", Arrays.asList(
    db, uid, password,
    "prefeitura.boleto", "action_baixar_arquivo_pdf",
    Arrays.asList(Arrays.asList(boletoId))
));

String downloadUrl = URL + result.get("url");
System.out.println(downloadUrl);

                            
                              
URL = 'https://meu_municipio.teste.joinfiscal.com.br'

result = models.execute_kw(db, uid, password, 'prefeitura.boleto',
                           'action_baixar_arquivo_pdf', [[boleto_id]])

download_url = "#{URL}#{result['url']}"
puts download_url

                            
                            
"https://meu_municipio.teste.joinfiscal.com.br/web/content/prefeitura.boleto/495/conteudo_pdf/boleto-495-felipe-silva-aberto-2023-04-10.pdf?download=true"

                          
  • Prefeitura de Lima Campos
  • Praça Duque de Caxias, nº S/N
  • Lima Campos - MA 65728-000
  • (86) 9 9903-9494
  • suporte@jointecdesenvolvimento.com.br
  • Ver localização no Google Maps