How to Access Brazilian CVM Filings with Python
If you build data pipelines for Brazilian equities, you eventually need filings from the CVM (Comissão de Valores Mobiliários) — annual statements, quarterly reports, and reference forms. The official portals are built for humans, not scripts. apicvm gives you a stable HTTP API to resolve companies, list documents, and download originals from Python.
This guide walks through the full flow with PETR4 and a DFP 2024 filing as concrete examples.
The problem
Brazilian regulatory data is scattered across CVM portals, open-data bulk downloads, and company IR sites. Common pain points for Python developers:
- No ticker-first API on official open data — you get ZIP/CSV dumps, not
GET /filings?ticker=PETR4. - Scraping breaks when portal HTML or CAPTCHAs change.
- Metadata is messy — matching a PDF to the right company, year, and filing type takes manual work.
You need a programmatic path: resolve the company once, filter documents by type and year, download the file you chose.
Background: CVM filing types
The CVM requires Brazilian public companies to file standardized documents. The ones you will use most often:
| Type | Name | Cadence | Typical use |
|---|---|---|---|
| DFP | Demonstrações Financeiras Padronizadas | Annual | Full-year financials |
| ITR | Informações Trimestrais | Quarterly | Interim financials |
| FRE | Formulário de Referência | Annual (updated) | Company profile, governance, risks |
apicvm exposes these as filterable document types on GET /v1/documents.
Setup
You need an API key and the base URL:
export APICVM_URL='https://apicvm.dev'
export APICVM_KEY='apicvm_...' # from /signup
All business endpoints require authentication via Authorization: Bearer or X-API-Key.
Step 1: Resolve the company
Start with a ticker, CNPJ, or company name. Resolution returns a single company or an ambiguity error with candidates.
curl -H "Authorization: Bearer $APICVM_KEY" \
"$APICVM_URL/v1/companies/resolve?query=PETR4&by=ticker"
Response shape (simplified):
{
"id": 123,
"name": "PETROBRAS",
"cnpj": "33000167000101",
"sector": "Petróleo, Gás e Biocombustíveis",
"tickers": [
{ "id": 1, "idCompany": 123, "ticker": "PETR4", "tickerClass": "PN" }
]
}
If multiple companies match, the API returns 409 AMBIGUOUS_RESULT with details.candidates[] — narrow your query or pass by=cnpj.
Step 2: List documents
Filter by ticker, document type, and year:
curl -H "Authorization: Bearer $APICVM_KEY" \
"$APICVM_URL/v1/documents?ticker=PETR4&type=DFP&year=2024&perPage=20"
Each item in data[] includes:
id(UUID) — use this for download or text extractiontype,year,dateRef,name- nested
companywith tickers
Pick a document explicitly. The API does not auto-select when filters match multiple results.
Step 3: Download the PDF
Stream the original file from the bucket:
curl -OJ -H "Authorization: Bearer $APICVM_KEY" \
"$APICVM_URL/v1/documents/<document-id>/file"
The response includes Content-Type (usually application/pdf) and Content-Disposition with the filename.
Full Python example
import os
import requests
BASE = os.environ["APICVM_URL"]
HEADERS = {"Authorization": f"Bearer {os.environ['APICVM_KEY']}"}
def apicvm_get(path: str, **params):
r = requests.get(f"{BASE}{path}", headers=HEADERS, params=params)
r.raise_for_status()
return r.json()
# 1. Resolve company
company = apicvm_get("/v1/companies/resolve", query="PETR4", by="ticker")
print(f"Resolved: {company['name']} (id={company['id']})")
# 2. List DFP filings for 2024
docs = apicvm_get(
"/v1/documents",
ticker="PETR4",
type="DFP",
year=2024,
perPage=20,
)
if not docs["data"]:
raise SystemExit("No DFP 2024 documents found for PETR4")
document = docs["data"][0]
doc_id = document["id"]
print(f"Selected: {document['type']} {document['year']} — {document['name']}")
# 3. Download PDF
file_resp = requests.get(
f"{BASE}/v1/documents/{doc_id}/file",
headers=HEADERS,
stream=True,
)
file_resp.raise_for_status()
filename = f"petr4-dfp-{document['year']}.pdf"
with open(filename, "wb") as f:
for chunk in file_resp.iter_content(chunk_size=8192):
f.write(chunk)
print(f"Saved {filename}")
For page-level markdown instead of raw PDF bytes, see Extract Text from CVM PDFs for AI Agents.
Pagination and filters
List endpoints support page, perPage (max 50), field, and order. Default document sort is year desc, then dateRef desc.
Useful filters on GET /v1/documents:
types=DFP,ITR— multiple types in one callcnpjorcompanyId— when you already resolved the companysearch— partial match on document name or type
Current limitations
- Corpus coverage — apicvm serves companies and documents already ingested in the Hold database. Not every listed B3 company may be available yet.
- No automatic document selection — you must pick a
document.idfrom list results. - Rate limit — 60 requests per minute per API key by default (see
X-RateLimit-* headers). - Ingestion lag — new CVM filings appear after the upstream pipeline processes them; this is not a real-time feed.
Next steps
- Read the API docs for full endpoint reference and error codes
- Get an API key to start building
- Download DFP and ITR filings — focused guide on financial statement types
- Brazil CVM vs SEC EDGAR — if you are coming from US markets
Ready to integrate?
Get an API key and start querying Brazilian CVM filings programmatically.