Load Brazil CVM Filings into Polars

Want a Polars CVM filings API workflow instead of pandas? apicvm returns paginated JSON. Use httpx or requests, then pl.DataFrame — same /v1 contract as the pandas guide.

The problem

Data teams building Brazil filing inventories often:

  • Paste portal CSVs into notebooks that break on layout changes
  • Mix quotes APIs with regulatory PDFs and lose auditability
  • Need lazy, typed tables for multi-ticker scans

Polars fits batch joins; apicvm supplies stable document UUIDs.

Setup

pip install polars httpx
export APICVM_URL='https://apicvm.dev'
export APICVM_KEY='apicvm_...'

Resolve + list into a DataFrame

import os
import httpx
import polars as pl

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

with httpx.Client(base_url=BASE, headers=H, timeout=30.0) as client:
    company = client.get(
        "/v1/companies/resolve",
        params={"query": "PETR4", "by": "ticker"},
    )
    company.raise_for_status()

    docs = client.get(
        "/v1/documents",
        params={"ticker": "PETR4", "type": "DFP", "year": 2024, "perPage": 50},
    )
    docs.raise_for_status()
    payload = docs.json()

df = pl.DataFrame(payload["data"])
print(df.select(["id", "name", "type", "year", "dateRef"]))

Paginate all pages

def list_all_documents(client: httpx.Client, **params) -> pl.DataFrame:
    page = 1
    frames: list[pl.DataFrame] = []
    while True:
        r = client.get("/v1/documents", params={**params, "page": page, "perPage": 50})
        r.raise_for_status()
        body = r.json()
        chunk = body.get("data") or []
        if not chunk:
            break
        frames.append(pl.DataFrame(chunk))
        meta = body.get("meta") or {}
        if page >= int(meta.get("lastPage") or page):
            break
        page += 1
    return pl.concat(frames) if frames else pl.DataFrame()

with httpx.Client(base_url=BASE, headers=H, timeout=30.0) as client:
    itr = list_all_documents(client, ticker="VALE3", type="ITR", year=2024)
    print(itr.height, itr.columns)

See Paginate and filter CVM documents.

Multi-ticker inventory

tickers = ["PETR4", "VALE3", "VIVT3"]
parts = []
with httpx.Client(base_url=BASE, headers=H, timeout=30.0) as client:
    for t in tickers:
        parts.append(list_all_documents(client, ticker=t, type="FRE", year=2024))

universe = pl.concat(parts).with_columns(pl.col("ticker").cast(pl.Utf8))
print(universe.group_by("ticker").len())

Watch X-RateLimit-Remaining when the basket grows. Details: Handle errors and rate limits.

Download is separate from the frame

curl -OJ -H "Authorization: Bearer $APICVM_KEY" \
  "$APICVM_URL/v1/documents/<document-id>/file"

Keep UUIDs in Polars; pull PDFs in a second pass. File download does not consume extraction credits.

Current limitations

  • Responses are JSON document metadata — not XBRL fact tables.
  • Text extraction is async via callback and credit-based on Pro.
  • Empty frames mean corpus miss or filter miss, not “no filing exists at CVM.”

Next steps

Ready to integrate?

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