Build a Streamlit Dashboard for Brazil CVM Filings

A small Streamlit CVM filings dashboard is a fast way to demo apicvm internally: pick a ticker, list filings, open PDFs. This uses Python requests + Streamlit — not an official Streamlit component.

Why this converts

Product and research teams often need a UI before they approve an API key budget. Streamlit ships that UI in ~50 lines against the same /v1 contract as production jobs.

Dependencies

pip install streamlit requests
export APICVM_URL='https://apicvm.dev'
export APICVM_KEY='apicvm_...'

App sketch

import os
import requests
import streamlit as st

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

st.title("Brazil CVM filings")

ticker = st.text_input("Ticker", "PETR4").upper().strip()
doc_type = st.selectbox("Type", ["DFP", "ITR", "FRE"])
year = st.number_input("Year", min_value=2010, max_value=2030, value=2024)

if st.button("Load filings"):
    company = requests.get(
        f"{BASE}/v1/companies/resolve",
        params={"query": ticker, "by": "ticker"},
        headers=H,
        timeout=30,
    )
    company.raise_for_status()
    st.json(company.json())

    docs = requests.get(
        f"{BASE}/v1/documents",
        params={"ticker": ticker, "type": doc_type, "year": int(year), "perPage": 20},
        headers=H,
        timeout=30,
    )
    docs.raise_for_status()
    rows = docs.json().get("data") or []
    for d in rows:
        st.write(d["name"], d["id"])
        st.markdown(f"[Download PDF]({BASE}/v1/documents/{d['id']}/file)")

Note: browser PDF links need the Authorization header — for downloads from the UI, fetch bytes server-side and use st.download_button instead of a naked URL:

pdf = requests.get(f"{BASE}/v1/documents/{d['id']}/file", headers=H, timeout=120)
st.download_button("PDF", pdf.content, file_name=f"{d['id']}.pdf")

Extend with FRE name filters

name = st.text_input("Name contains", "FatoresRisco")
params = {"ticker": ticker, "type": "FRE", "year": int(year), "name": name, "perPage": 20}

Cache resolve results

@st.cache_data(ttl=3600)
def resolve(ticker: str):
    r = requests.get(
        f"{BASE}/v1/companies/resolve",
        params={"query": ticker, "by": "ticker"},
        headers=H,
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

Caching cuts rate-limit usage when users toggle years for the same issuer.

Show empty corpus honestly

If data is empty, display “Not in corpus for this filter” — not “company has no filings.” Ingestion lag is normal. Pair with Getting started for expected document types.

Security note

Streamlit Cloud secrets should store APICVM_KEY. Never put keys in st.session_state that gets logged. Prefer server-side st.download_button over exposing Bearer tokens in browser links.

Current limitations

  • This dashboard lists ingested filings only.
  • Do not embed API keys in Streamlit Cloud secrets shared publicly.
  • Page-level OCR extraction remains async — keep it out of the first demo.

Next steps

Ready to integrate?

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