How to Download DFP and ITR Filings from Brazilian Public Companies

DFP and ITR are the two CVM filing types you need for financial statement analysis: annual standardized statements and quarterly updates. If you are building a DFP ITR API integration, the workflow is list by ticker and type, pick a document, download the PDF.

Note on ITR: here ITR means Informações Trimestrais (Brazilian quarterly filings) — not an income tax return API.

This guide shows curl and Python examples using PETR4 (annual DFP) and VALE3 (quarterly ITR) with apicvm.

The problem

Financial data pipelines for Brazilian equities need:

  • DFP — full-year audited statements (roughly analogous to a 10-K)
  • ITR — quarterly interim reports (Informações Trimestrais; roughly analogous to a 10-Q, not a tax filing)

Finding the correct PDF on CVM portals means navigating Portuguese UI, matching CNPJ to ticker, and handling inconsistent naming. You want GET filings?ticker=VALE3&type=ITR&year=2024 — that is what apicvm provides.

What DFP and ITR contain

Type Full name When filed Typical contents
DFP Demonstrações Financeiras Padronizadas After fiscal year close Balance sheet, income statement, cash flow, notes
ITR Informações Trimestrais (not income tax) Each quarter Unaudited/quarterly financials, MD&A updates

Both are submitted to the CVM by B3-listed companies. apicvm indexes them with type, year, dateRef, and name metadata.

List DFP filings

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

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

Sample response item:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "DFP",
  "year": 2024,
  "dateRef": "2024-12-31",
  "name": "Demonstrações Financeiras Padronizadas",
  "idCompany": 123,
  "company": {
    "name": "PETROBRAS",
    "tickers": [{ "ticker": "PETR4", "tickerClass": "PN" }]
  }
}

List ITR filings

Same endpoint, different type:

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

Filter multiple types in one request:

curl -H "Authorization: Bearer $APICVM_KEY" \
  "$APICVM_URL/v1/documents?ticker=VALE3&types=DFP,ITR&year=2024&perPage=50"

Download the PDF

Use the UUID from list results — never guess document IDs:

curl -OJ -H "Authorization: Bearer $APICVM_KEY" \
  "$APICVM_URL/v1/documents/550e8400-e29b-41d4-a716-446655440000/file"

Returns the original PDF with Content-Disposition filename headers.

Python: download latest DFP and ITR for a ticker

import os
import requests
from pathlib import Path

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


def list_documents(ticker: str, doc_type: str, year: int) -> list:
    r = requests.get(
        f"{BASE}/v1/documents",
        headers=H,
        params={"ticker": ticker, "type": doc_type, "year": year, "perPage": 50},
    )
    r.raise_for_status()
    return r.json()["data"]


def download_document(doc_id: str, dest: Path) -> None:
    r = requests.get(f"{BASE}/v1/documents/{doc_id}/file", headers=H, stream=True)
    r.raise_for_status()
    with dest.open("wb") as f:
        for chunk in r.iter_content(8192):
            f.write(chunk)


def fetch_filing(ticker: str, doc_type: str, year: int, out_dir: Path) -> Path | None:
    docs = list_documents(ticker, doc_type, year)
    if not docs:
        print(f"No {doc_type} {year} for {ticker}")
        return None
    doc = docs[0]  # first result; inspect docs[] to pick a specific filing
    filename = out_dir / f"{ticker.lower()}-{doc_type.lower()}-{year}.pdf"
    download_document(doc["id"], filename)
    print(f"Downloaded {filename}")
    return filename


out_dir = Path("filings")
out_dir.mkdir(exist_ok=True)

fetch_filing("PETR4", "DFP", 2024, out_dir)
fetch_filing("VALE3", "ITR", 2024, out_dir)

Tip: When list results return multiple documents, inspect name and dateRef to select the right one. The API returns an explicit list — it does not auto-pick.

Pagination

Default perPage is 10; maximum is 50. For companies with long filing histories:

page = 1
all_docs = []
while True:
    r = requests.get(
        f"{BASE}/v1/documents",
        headers=H,
        params={"ticker": "PETR4", "type": "ITR", "page": page, "perPage": 50},
    )
    r.raise_for_status()
    body = r.json()
    all_docs.extend(body["data"])
    if page >= body["meta"]["lastPage"]:
        break
    page += 1

Default sort is year desc, dateRef desc — recent filings appear first.

Beyond download: text extraction

PDFs work for archival and manual review. For NLP, RAG, or LLM agents, request page-level markdown:

curl -X POST -H "Authorization: Bearer $APICVM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"callback_url":"https://your-app.example.com/callbacks/apicvm","document":{"id":"<document-id>"}}' \
  "$APICVM_URL/v1/document-text-extractions"

See Extract Text from CVM PDFs for AI Agents for the callback flow.

Current limitations

  • Availability — DFP/ITR exist in apicvm only for companies and periods in the ingested corpus.
  • No XBRL API — downloads are PDF originals, not structured XBRL feeds.
  • Multiple filings per year — some companies file revisions; always inspect list results.
  • Rate limit — 60 req/min per API key for most endpoints; markdown extraction is limited to 1 document per key per UTC day.

Next steps

Ready to integrate?

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