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.
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:
an_exercicio: ano de referência;id_ente: código IBGE do ente federativo.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
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.
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.
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.
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.
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.
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"
)
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"
)
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.