Python client for the BCB SGS (Sistema Gerenciador de Series Temporais) API from the Banco Central do Brasil.
Fetch Brazilian economic and financial time series as pandas DataFrames with a simple, Pythonic interface. Includes 114 curated series codes covering exchange rates, interest rates, inflation, GDP, employment, and more.
pip install bcbpyOr from source:
git clone https://github.com/rteoo/bcbpy.git
cd bcbpy
pip install .- Python 3.10+
- pandas
- requests
from bcbpy import fetch_series, fetch_last, fetch_multiple, INTEREST_RATES, EXCHANGE_RATES
# Last 10 CDI daily rates
cdi = fetch_last(INTEREST_RATES["CDI_DAILY"], n=10)
print(cdi)
# USD/BRL exchange rate for 2024
usd = fetch_series(EXCHANGE_RATES["USD_SALE_DAILY"], start_date="2024-01-01", end_date="2024-12-31")
print(usd)
# Multiple series merged into one DataFrame
df = fetch_multiple(
{"CDI": INTEREST_RATES["CDI_DAILY"], "SELIC": INTEREST_RATES["SELIC_DAILY"]},
start_date="2024-01-01",
end_date="2024-12-31",
)
print(df.tail())Fetch a time series by its SGS numeric code. Returns a pandas DataFrame indexed by date.
from bcbpy import fetch_series
# Accepts YYYY-MM-DD or DD/MM/YYYY date formats
ipca = fetch_series(433, start_date="2023-01-01", end_date="2024-12-31")Daily series (CDI, Selic, USD/BRL, …) require a start_date: BCB rejects undated daily queries with HTTP 406, which surfaces as SGSHTTPError carrying BCB's explanation.
Fetch the last N observations of a series.
from bcbpy import fetch_last
selic = fetch_last(11, n=5)Fetch multiple series and merge them into a single DataFrame, one column per series.
from bcbpy import fetch_multiple
df = fetch_multiple({"CDI": 12, "SELIC": 11, "TR": 226}, start_date="2024-01-01")Fetch one SGS window as a RawResult (payload bytes plus request metadata). Same 10-year single-call limit as fetch_series. Does not parse the body.
from bcbpy import fetch_raw
raw = fetch_raw(12, start_date="2024-01-01", end_date="2024-01-31")
print(raw.sha256, raw.source_url, raw.params)Like fetch_raw, but splits ranges longer than 10 years into bounded partitions. Adjacent partitions do not share a calendar day.
from bcbpy import fetch_raw_range
parts = fetch_raw_range(433, start_date="2010-01-01", end_date="2024-12-31")Print all available series codes. Pass a category name to filter.
from bcbpy import list_codes
list_codes() # all 114 codes across 14 categories
list_codes("INTEREST_RATES") # only interest rate codesSearch codes by keyword (case-insensitive). Returns a dict of matches.
from bcbpy import search_codes
results = search_codes("IPCA") # finds 15 IPCA-related codes
results = search_codes("USD") # finds USD exchange rate codes| Exception | When |
|---|---|
SGSError |
Base class for every error raised by bcbpy, including malformed or non-JSON responses (e.g. an unknown series code) |
SGSHTTPError |
Any other HTTP error status, with BCB's error text in the message. Also a requests.HTTPError. |
SGSRateLimitError |
API returns HTTP 429 (too many requests). retry_after is set from Retry-After when present. |
SGSEmptyResponseError |
No data returned for the given query |
Network failures (timeouts, connection errors) are raised by requests unchanged.
from bcbpy import fetch_series, SGSRateLimitError, SGSEmptyResponseError
try:
df = fetch_series(433, start_date="2024-01-01")
except SGSRateLimitError:
print("Rate limited — wait and retry")
except SGSEmptyResponseError:
print("No data for this date range")114 curated codes organized in 14 categories:
| Category | Series | Examples |
|---|---|---|
EXCHANGE_RATES |
6 | USD/BRL daily sale/purchase, monthly averages |
INTEREST_RATES |
10 | Selic, CDI, TR, TBF, TJLP |
INFLATION |
17 | IPCA, INPC, IGP-M, IGP-DI, IPC-Fipe |
IPCA_BREAKDOWN |
11 | Tradeable, non-tradeable, durables, services, cores |
IPCA_CATEGORIES |
9 | Food, housing, transport, health, education |
GDP |
13 | GDP current/constant/USD, per capita, quarterly components |
EMPLOYMENT |
7 | Unemployment rate, labor force, income |
INDUSTRIAL_PRODUCTION |
6 | Manufacturing, mining, capital/intermediate/consumer goods |
FINANCIAL_MARKETS |
7 | Gold, Bovespa, IMA-B |
SAVINGS |
2 | Savings rate and return |
CONFIDENCE |
4 | Consumer (ICC) and business (ICEI) confidence |
ECONOMIC_ACTIVITY |
1 | IBC-Br (GDP proxy, seasonally adjusted) |
BASIC_BASKET |
16 | Cost of living by capital city |
EXCHANGE_RATE_INDEX |
5 | Effective currency basket and bilateral real indices (USD, JPY, DEM, ARS) |
Use any code directly by number or via the category dictionaries:
from bcbpy import INFLATION, GDP
# These are equivalent:
fetch_series(433)
fetch_series(INFLATION["IPCA"])Version 3.0 renames seven registry keys to match the SGS series names. Update dictionary lookups and any saved key names using this table:
| Category | Old key (2.x) | New key (3.0) | SGS code |
|---|---|---|---|
EMPLOYMENT |
AVG_NOMINAL_INCOME |
AVG_REAL_HABITUAL_INCOME |
24382 |
INTEREST_RATES |
SELIC_OVERNIGHT_ANNUAL |
SELIC_MONTHLY_ANNUALIZED |
4189 |
INTEREST_RATES |
CDI_OVERNIGHT |
CDI_MONTHLY_ANNUALIZED |
4392 |
EXCHANGE_RATE_INDEX |
REER_USD |
RER_USD |
11753 |
EXCHANGE_RATE_INDEX |
REER_JPY |
RER_JPY |
11754 |
EXCHANGE_RATE_INDEX |
REER_EUR |
RER_DEM |
11755 |
EXCHANGE_RATE_INDEX |
REER_ARS |
RER_ARS |
11756 |
The old keys are removed from the category dictionaries and ALL_CODES;
lookups raise KeyError. list_codes and search_codes expose the new names.
Numeric SGS codes, the 114-series count, and fetch behavior are unchanged.
Code 24382 measures real habitual income of employed people. Codes 4189 and
4392 measure Selic and CDI accumulated over the month, annualized on a
252-day basis. RER_DEM is the Deutsche mark index. The four RER_* indices
are bilateral; REER_BASKET (11752) remains the effective currency-basket
index. All five exchange-rate indices are IPCA-based, with June 1994 = 100.
from bcbpy import fetch_last, EMPLOYMENT, INTEREST_RATES, EXCHANGE_RATE_INDEX
income = fetch_last(EMPLOYMENT["AVG_REAL_HABITUAL_INCOME"])
selic = fetch_last(INTEREST_RATES["SELIC_MONTHLY_ANNUALIZED"])
dem = fetch_last(EXCHANGE_RATE_INDEX["RER_DEM"])These registered series have stopped updating in SGS (last observation as of September 2026). Historical data is still available; recent windows return SGSEmptyResponseError.
| Series | Last observation |
|---|---|
FINANCIAL_MARKETS: GOLD_BMF_GRAM, GOLD_LONDON_OZ, BOVESPA_INDEX, BOVESPA_VOLUME |
Sep 2019 |
EMPLOYMENT["FORMAL_EMPLOYMENT_TOTAL"] |
Dec 2019 |
INFLATION["ICV_DIEESE"] |
Feb 2020 |
FINANCIAL_MARKETS: IMA_B, IMA_B5, IMA_B5_PLUS |
May 2023 |
BASIC_BASKET (all cities) |
Jun 2025 |
INFLATION: IGP_M_1ST_DECENNIAL, IGP_M_2ND_DECENNIAL, IPC_FIPE_1ST_QUAD, IPC_FIPE_2ND_QUAD, IPC_FIPE_3RD_QUAD |
Jul 2025 |
- Date range: max 10 years per single query (BCB restriction since March 2025).
fetch_series/fetch_rawstill enforce that limit.fetch_raw_rangesplits longer windows into bounded requests. - Rate limiting: HTTP 429 on excessive requests (no official limit documented).
SGSRateLimitError.retry_aftercarriesRetry-Afterwhen the API sends it; the client does not auto-retry. - Daily series: a
start_dateis mandatory; undated queries return HTTP 406. - Unknown series codes: SGS answers with an HTML page (HTTP 200) after about 30 seconds instead of a 404. With the client's 30-second timeout this usually surfaces as
requests.ReadTimeout; when the page arrives in time it raisesSGSError. - Date formats: the client accepts both
YYYY-MM-DDandDD/MM/YYYY
bcbpy/
├── bcbpy/
│ ├── __init__.py # Public API exports
│ ├── artifacts.py # RawResult descriptor
│ ├── client.py # API client functions and exceptions
│ ├── codes.py # 114 curated series codes in 14 categories
│ └── constants.py # Base URLs and API configuration
├── pyproject.toml # PyPI packaging metadata
├── BCB_API_REFERENCE.md # SGS API reference and series code table
└── README.md
All data is fetched from the BCB Open Data Portal under the Open Database License (ODbL).
MIT (see LICENSE). The BCB data accessed through this client remains under ODbL; users must comply with ODbL when redistributing data.
The version in bcbpy/__init__.py must already be merged to main. From a clean checkout matching origin/main, run python release.py --tag vX.Y.Z --dry-run, then rerun without --dry-run and type the tag to confirm.
The release helper runs these gates locally: python -m pytest -m "not integration" -v and python -m build. It never bumps or commits main; PyPI publication remains the OIDC GitHub Actions workflow. A retry is safe only for the same tag when the existing tag points at the exact merged commit and no GitHub release exists. PyPI releases are immutable; rollback means following the package-recovery process rather than deleting or replacing a published version. The helper is platform-neutral Python, but hosted Actions behavior is not proven by local execution.