Load Brazil CVM Filings into pandas

Data scientists who want a pandas CVM filings API workflow need document metadata in a DataFrame — ticker, type, year, name, stable id — then optional PDF download. apicvm returns JSON lists you can flatten with pandas.json_normalize or a one-line DataFrame(...).

The problem

CVM open-data ZIPs are fine for bulk panels, but a notebook that asks “show me VALE3 FRE 2024 sections” should not re-download the entire market dump. You want:

resolve → list → DataFrame → filter → download selected ids

Setup

import os
import requests
import pandas as pd

BASE = os.environ["APICVM_URL"].rstrip("/")
H = {"Authorization": f"Bearer {os.environ['APICVM_KEY']}"}

def apicvm_get(path: str, **params):
    r = requests.get(f"{BASE}{path}", headers=H, params=params, timeout=30)
    r.raise_for_status()
    return r.json()

Resolve a company

company = apicvm_get("/v1/companies/resolve", query="VALE3", by="ticker")
print(company["name"], company["cnpj"])

List filings as a DataFrame

payload = apicvm_get(
    "/v1/documents",
    ticker="VALE3",
    type="FRE",
    year=2024,
    perPage=50,
)

df = pd.DataFrame(payload["data"])[
    ["id", "type", "year", "name", "dateRef", "idCompany"]
]
print(df.head())

Filter section names client-side or with the name query param:

risks = apicvm_get(
    "/v1/documents",
    ticker="VALE3",
    type="FRE",
    year=2024,
    name="FatoresRisco",
    perPage=20,
)
risk_df = pd.DataFrame(risks["data"])

Paginate into one frame

perPage max is 50. Loop pages when you need a fuller catalog:

def list_all_documents(**params):
    rows, page = [], 1
    while True:
        payload = apicvm_get(
            "/v1/documents",
            page=page,
            perPage=50,
            **params,
        )
        rows.extend(payload["data"])
        meta = payload.get("meta") or {}
        if page >= meta.get("lastPage", page):
            break
        page += 1
    return pd.DataFrame(rows)

itr = list_all_documents(ticker="PETR4", type="ITR", year=2025)
print(itr[["id", "name", "dateRef"]].head())

See paginate and filter CVM documents.

Download selected PDFs

def download_doc(doc_id: str, out_dir: str = "filings") -> str:
    os.makedirs(out_dir, exist_ok=True)
    path = os.path.join(out_dir, f"{doc_id}.pdf")
    r = requests.get(f"{BASE}/v1/documents/{doc_id}/file", headers=H, timeout=120)
    r.raise_for_status()
    open(path, "wb").write(r.content)
    return path

if not df.empty:
    download_doc(df.iloc[0]["id"])

Downloads do not consume extraction credits.

curl equivalent

export APICVM_KEY='apicvm_...'
export APICVM_URL='https://apicvm.dev'

curl -H "Authorization: Bearer $APICVM_KEY" \
  "$APICVM_URL/v1/documents?ticker=VALE3&type=DFP&year=2024&perPage=20"

Text extraction note

Page-level markdown via POST /v1/document-text-extractions is async and Pro-only. For pandas pipelines, prefer listing + download first; add extraction when you need searchable text for RAG. See extract text from CVM PDFs.

Current limitations

  • API returns filing metadata and files, not accounting line items as DataFrames (unlike some open-data CSV parsers).
  • Corpus coverage depends on ingestion — empty filters mean “not ingested,” not “issuer has no filings.”
  • Rate limits apply per API key; batch politely.

Next steps

Ready to integrate?

Get an API key and start querying Brazilian CVM filings programmatically.