1 Introdução

O Sistema de Informações Contábeis e Fiscais do Setor Público Brasileiro (SICONFI), mantido pela Secretaria do Tesouro Nacional (STN), disponibiliza um amplo conjunto de informações fiscais e contábeis dos entes subnacionais brasileiros.

Entre essas informações estão as Declarações de Contas Anuais (DCA), que consolidam dados detalhados de receitas, despesas e demais demonstrativos contábeis para cada ente federativo.

Embora o site do SICONFI permita o download manual dessas informações, o acesso via API é particularmente útil para pesquisas empíricas que envolvam múltiplos anos, diversos entes federativos e a necessidade de reprodutibilidade dos resultados.

O objetivo deste documento é apresentar uma forma robusta, escalável e reprodutível de acessar os dados da DCA das UF brasileiras via API do SICONFI utilizando o R.

2 Acesso ao SICONFI via API

O acesso aos dados da DCA por meio da API do SICONFI segue uma estrutura REST simples. Em linhas gerais, são necessários:

As informações necessárias para acessar a API do SICONFI constam na sua documentação, no endereço:

https://apidatalake.tesouro.gov.br/docs/siconfi/

A URL base do SICONFI, já com o endpoint da DCA, é a seguinte:

https://apidatalake.tesouro.gov.br/ords/siconfi/tt/dca

Os principais parâmetros utilizados nas consultas são:

3 Implementação Simples

Como exemplo, vamos consultar a DCA do ano de 2022 para a UF do Rio de Janeiro. Para isso, utilizamos o pacote httr e fazemos uma requisição GET definindo o ano desejado (2022) e o código do ente (35) com a função GET().

Em seguida, é feita a extração do conteúdo obtido e, posteriormente, tranformamos o objeto em um data.frame.

library(httr)
library(jsonlite)

resp <- GET(
  "https://apidatalake.tesouro.gov.br/ords/siconfi/tt/dca",
  query = list(
    an_exercicio = "2022",
    id_ente = "35"
  )
)

txt <- content(resp, as = "text", encoding = "UTF-8")
parsed <- fromJSON(txt)
dados <- parsed$items

4 Limitações de uma Implementação Simples

Ao tentar escalar a consulta para vários anos e todas as UFs, surgem problemas como:

Por isso, precisamos elaborar uma consulta que seja robusta aos problemas que podem ocorrer durante a requisição.

5 Implementação Robusta

5.1 Pacotes e Variáveis

A primeira parte da implementação tem o seguinte código:

library(httr)
library(jsonlite)
library(dplyr)

periodo <- 2019:2022

# Tabela de UFs com códigos IBGE
tabela_uf <- data.frame(
  codigo = c(
    11, 12, 13, 14, 15, 16, 17,
    21, 22, 23, 24, 25, 26, 27,
    28, 29,
    31, 32, 33, 35,
    41, 42, 43,
    50, 51, 52, 53
  ),
  uf = c(
    "RO", "AC", "AM", "RR", "PA", "AP", "TO",
    "MA", "PI", "CE", "RN", "PB", "PE", "AL",
    "SE", "BA",
    "MG", "ES", "RJ", "SP",
    "PR", "SC", "RS",
    "MS", "MT", "GO", "DF"
  ),
  nome_uf = c(
    "Rondônia", "Acre", "Amazonas", "Roraima", "Pará", "Amapá", "Tocantins",
    "Maranhão", "Piauí", "Ceará", "Rio Grande do Norte", "Paraíba", "Pernambuco", "Alagoas",
    "Sergipe", "Bahia",
    "Minas Gerais", "Espírito Santo", "Rio de Janeiro", "São Paulo",
    "Paraná", "Santa Catarina", "Rio Grande do Sul",
    "Mato Grosso do Sul", "Mato Grosso", "Goiás", "Distrito Federal"
  ),
  stringsAsFactors = FALSE
)

codigos <- tabela_uf$codigo

base_url <- "https://apidatalake.tesouro.gov.br/ords/siconfi/tt/dca"

respostas_brutas <- list()
dados_parsed <- list()

No bloco inicial acima são carregados os pacotes necessários, definidos os parâmetros básicos da consulta à API do SICONFI e criadas as estruturas que irão armazenar os resultados das requisições.

Primeiramente, são carregados os pacotes utilizados ao longo do script. O pacote httr permite realizar requisições HTTP do tipo GET, essenciais para o acesso à API do SICONFI. O jsonlite é responsável por converter as respostas da API, que vêm no formato JSON, em objetos utilizáveis no R. Por fim, o dplyr é utilizado nas etapas posteriores de manipulação e filtragem dos dados.

Em seguida, define-se o vetor periodo, que contém os anos de referência para os quais as Declarações de Contas Anuais (DCA) serão consultadas. Esse vetor será utilizado posteriormente em um loop, permitindo escalar a consulta para múltiplos anos de forma automática.

Na sequência, é criada uma tabela contendo os códigos dos entes federativos (UFs). A partir dessa tabela, extrai-se a coluna codigo, que contém os códigos IBGE necessários para o parâmetro id_ente da API do SICONFI.

Também é definida a base_url, que corresponde à URL da API do SICONFI já apontando para o endpoint específico das Declarações de Contas Anuais (DCA). Centralizar essa informação em um único objeto torna o código mais legível e facilita eventuais alterações futuras.

Por fim, são criadas duas listas vazias. A lista respostas_brutas será utilizada para armazenar as respostas completas retornadas pela função GET, incluindo metadados e códigos de status HTTP, o que é útil para fins de depuração. Já a lista dados_parsed armazenará versões tratadas dessas respostas, contendo informações sobre o sucesso ou falha da requisição e, quando aplicável, os dados já convertidos para data.frame. Essas estruturas são fundamentais para garantir que o processo seja robusto a erros e facilmente auditável.

5.2 Loop de Consulta

Em seguida temos o bloco de código que efetivamente realiza a consulta:

for (ano in periodo) {
  for (cod in codigos) {

    chave <- paste0(ano, "_", cod)
    cat("Consultando:", chave, "\n")

    resp <- tryCatch(
      GET(
        base_url,
        query = list(
          an_exercicio = as.character(ano),
          id_ente = as.character(cod)
        ),
        timeout(30)
      ),
      error = function(e) e
    )

    respostas_brutas[[chave]] <- resp

    if (inherits(resp, "error")) {
      dados_parsed[[chave]] <- list(ok = FALSE, reason = resp$message, df = data.frame())
      next
    }

    if (status_code(resp) != 200) {
      dados_parsed[[chave]] <- list(ok = FALSE, reason = paste0("status_", status_code(resp)), df = data.frame())
      next
    }

    txt <- content(resp, as = "text", encoding = "UTF-8")

    parsed <- tryCatch(fromJSON(txt, flatten = TRUE), error = function(e) e)

    if (inherits(parsed, "error")) {
      dados_parsed[[chave]] <- list(ok = FALSE, reason = parsed$message, df = data.frame())
      next
    }

    if (!("items" %in% names(parsed)) || length(parsed$items) == 0) {
      dados_parsed[[chave]] <- list(ok = TRUE, reason = "empty", df = data.frame())
      next
    }

    df <- as.data.frame(parsed$items, stringsAsFactors = FALSE)
    df$ano <- ano
    df$id_ente <- cod

    dados_parsed[[chave]] <- list(ok = TRUE, reason = "ok", df = df)

    Sys.sleep(0.12)
  }
}

O bloco de código acima implementa o processo de consulta à API do SICONFI de forma sistemática, robusta e escalável, percorrendo todas as combinações de anos e entes federativos definidas anteriormente.

O primeiro for percorre o vetor periodo, iterando sobre cada ano de referência. Para cada ano, um segundo for percorre o vetor codigos, que contém os códigos IBGE dos entes federativos. Dessa forma, o script realiza uma requisição distinta para cada combinação ano–ente, permitindo a coleta completa dos dados da DCA para todo o período analisado.

Dentro do loop, é criada uma variável chamada chave, que concatena o ano e o código do ente ("ANO_CODIGO"). Essa chave funciona como um identificador único para cada requisição e é utilizada como índice nas listas respostas_brutas e dados_parsed. Esse mecanismo torna simples identificar posteriormente qual combinação de ano e ente gerou determinado resultado, facilitando auditoria, depuração e reaproveitamento dos dados.

Em seguida, a função GET() é utilizada para realizar a requisição HTTP à API do SICONFI, passando como parâmetros o ano (an_exercicio) e o código do ente (id_ente). A chamada é envolvida por um tryCatch(), que captura erros de execução no R — como falhas de conexão, problemas de SSL ou timeouts — evitando que um erro pontual interrompa todo o processo. Caso ocorra um erro, o próprio objeto de erro é retornado e tratado posteriormente.

A resposta bruta da requisição, seja ela bem-sucedida ou não, é armazenada na lista respostas_brutas utilizando a chave como índice. Isso preserva integralmente o retorno da API, incluindo cabeçalhos e códigos de status HTTP, o que pode ser útil para inspeções futuras.

O primeiro teste lógico verifica se o objeto retornado é um erro de R, usando inherits(resp, "error"). Se esse for o caso, a requisição é marcada como malsucedida em dados_parsed, registrando a mensagem de erro e associando um data.frame vazio. O comando next faz com que o loop avance imediatamente para a próxima combinação ano–ente.

Caso não haja erro de execução, o script verifica o código de status HTTP da resposta. Se o status for diferente de 200, isso indica que a requisição foi processada pelo servidor, mas não retornou sucesso. Nessa situação, o status é registrado como motivo da falha e, novamente, o loop segue para a próxima iteração.

Somente quando a resposta é válida (status 200) o conteúdo é extraído no formato de texto JSON, utilizando a função content(). Em seguida, esse texto é convertido para um objeto R por meio da função fromJSON(), também protegida por tryCatch(). Essa segunda camada de proteção é necessária porque, mesmo com status 200, a resposta pode conter JSON malformado ou incompatível.

Após o parsing, o código verifica se o objeto resultante contém o elemento items e se esse elemento possui comprimento maior que zero. A API do SICONFI frequentemente retorna respostas válidas, porém vazias, quando não há dados disponíveis para determinada combinação de ano e ente. Nesse caso, a requisição é considerada bem-sucedida, mas sem dados, e isso é registrado explicitamente.

Quando existem dados disponíveis, o conteúdo de items é convertido em um data.frame. Em seguida, são adicionadas duas colunas auxiliares — ano e id_ente — que identificam a origem de cada observação. Essa etapa é fundamental para permitir a agregação posterior dos dados provenientes de múltiplas requisições.

O resultado final de cada iteração é armazenado na lista dados_parsed, contendo três elementos: um indicador de sucesso (ok), uma descrição resumida do resultado (reason) e o data.frame com os dados propriamente ditos.

Por fim, o comando Sys.sleep(0.12) introduz uma pequena pausa entre as requisições. Essa prática é importante para evitar sobrecarga da API do SICONFI, reduzir a probabilidade de bloqueios e garantir um comportamento mais regular do script em relação ao servidor.

6 Consolidação dos Dados

Em seguida, temos um bloco de código que consolida todos os dados requisitados:

lista_dfs <- lapply(dados_parsed, function(x) {
  if (is.list(x) && !is.null(x$df) && nrow(x$df) > 0) x$df else NULL
})

lista_dfs <- Filter(Negate(is.null), lista_dfs)

if (length(lista_dfs) > 0) {
  todos_df <- do.call(rbind, lista_dfs)
} else {
  todos_df <- data.frame()
}

Após a execução do loop de consultas, os resultados de todas as requisições à API do SICONFI encontram-se armazenados na lista dados_parsed. Cada elemento dessa lista corresponde a uma combinação específica de ano e ente federativo e contém, entre outras informações, um data.frame com os dados retornados (ou um data.frame vazio, quando não houve observações).

O objetivo deste bloco é extrair apenas os resultados válidos e consolidá-los em um único data.frame, facilitando análises posteriores.

O primeiro passo utiliza a função lapply() para percorrer todos os elementos da lista dados_parsed. Para cada elemento x, o código verifica três condições simultaneamente: (i) se o objeto é de fato uma lista, (ii) se o elemento df existe e não é nulo, e (iii) se esse data.frame possui pelo menos uma linha.

Quando todas essas condições são satisfeitas, o data.frame contido em x$df é retornado. Caso contrário, a função retorna NULL. Como resultado, lista_dfs torna-se uma lista que contém apenas data.frames válidos ou valores NULL.

Na sequência, a função Filter() é utilizada para remover explicitamente todos os elementos NULL da lista. A combinação de Filter() com Negate(is.null) mantém apenas os objetos que efetivamente contêm dados, garantindo que a lista resultante seja composta exclusivamente por data.frames prontos para agregação.

Em seguida, o código verifica se a lista resultante possui ao menos um elemento. Caso positivo, a função do.call() combinada com rbind é utilizada para empilhar todos os data.frames verticalmente, formando um único objeto chamado todos_df. Essa abordagem é eficiente e evita problemas comuns associados à concatenação incremental dentro de loops.

Caso não exista nenhum data.frame válido — por exemplo, se todas as requisições retornaram erro ou respostas vazias — o código cria explicitamente um data.frame vazio. Essa verificação impede falhas posteriores no script e garante que o objeto todos_df exista independentemente do resultado das consultas.

Esse procedimento assegura que apenas dados consistentes e não vazios sejam utilizados nas etapas seguintes de tratamento, mantendo o fluxo do script robusto e previsível mesmo em cenários adversos.

7 Separação de receitas e despesas

Por fim, fazemos uma simples separação dos dados de receitas e despesas com a função filter(), e renomeamos algumas colunas para que fiquem no mesmo formato da planilha de dados da DCA do site do SICONFI.

7.1 Receitas

receitas_siconfi <- todos_df %>%
  filter(anexo == "DCA-Anexo I-C", coluna == "Receitas Brutas Realizadas") %>%
  select(exercicio, uf, conta, coluna, valor)

colnames(receitas_siconfi) <- c(
  "AN_EXERCICIO",
  "SG_ENTE",
  "ELEMENTLABEL",
  "NO_LABEL_EIXO_X",
  "VALUE"
)

7.2 Despesas

dados_gastos <- todos_df %>%
  filter(anexo == "DCA-Anexo I-E", coluna == "Despesas Empenhadas") %>%
  select(exercicio, uf, rotulo, coluna, conta, valor, instituicao, id_ente, populacao)

colnames(dados_gastos) <- c(
  "Ano",
  "UF",
  "NO_LABEL_EIXO_Y",
  "NO_LABEL_EIXO_X",
  "ELEMENTLABEL",
  "VALUE",
  "NO_ORGAO",
  "NO_ENTE",
  "QT_HABITANTE"
)

8 Considerações Finais

Este documento apresentou uma estratégia completa para acesso às Declarações de Contas Anuais (DCA) do SICONFI via API em R, adequada para análises empíricas que exigem escala, robustez e reprodutibilidade.

É importante destacar que, embora tanto a DCA quanto o RREO sejam divulgados por meio do SICONFI, eles atendem a propósitos distintos e são complementares. O Relatório Resumido da Execução Orçamentária (RREO) possui caráter intra-anual, com periodicidade bimestral, e é voltado principalmente ao acompanhamento da execução orçamentária ao longo do exercício fiscal.

Já a Declaração de Contas Anuais (DCA) consolida, ao final do exercício, informações contábeis mais detalhadas, permitindo uma análise estrutural das receitas, despesas e demonstrativos fiscais dos entes federativos. Para pesquisas empíricas que demandam maior granularidade contábil e comparabilidade intertemporal, a DCA constitui uma base especialmente rica.