How to Build a RAG Pipeline with Brazil CVM Filings

Brazil financial filings LLM workflows need more than raw PDFs. Retrieval-augmented generation (RAG) over CVM documents means: resolve the company, pick the right filing type, convert pages to markdown, chunk and embed, then answer questions with citations back to the original PDF.

apicvm handles the regulatory-data layer — company resolve, document listing, async page extraction — so your RAG stack focuses on retrieval and generation.

The problem

CVM filings are long PDFs in Portuguese with tables, footnotes, and multi-column layouts. Common RAG blockers:

Blocker Why it hurts
No ticker-first API You cannot ask "PETR4 DFP 2024" in one call
PDF parsing quality Naive extraction loses tables and section structure
Missing provenance LLM answers without document_id + page number
Batch-only open data ZIP dumps lack per-filing download and page callbacks

You need a pipeline that ingests page-level markdown with stable metadata: ticker, type, year, dateRef, page index.

Architecture overview

apicvm resolve/list
       │
       ▼
POST /v1/document-text-extractions
       │
       ▼ (callbacks, one page each)
Chunk + metadata (ticker, type, year, page)
       │
       ▼
Embed → vector store
       │
       ▼
User query → retrieve → LLM answer + citations

Step 1: Select filings to ingest

For financial Q&A, a practical starting set per company:

Type RAG use
DFP Annual financials, audit notes
ITR Quarterly updates, interim trends
FRE Governance, risks, business description

List candidates:

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"

Pick explicit document.id values from name and dateRef. DFP years often return multiple files — choose the main financial statements section, not every committee report, unless your use case needs them.

Step 2: Enqueue extraction

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

The API returns 202. Each callback delivers one page of markdown. Implement an HTTPS endpoint that:

  1. Validates the callback payload
  2. Attaches metadata (ticker, type, year, page)
  3. Queues the page for chunking

There is no HTTP status endpoint for job progress — track completion by counting callbacks or timeouts in your handler.

Step 3: Chunk with metadata

Store chunks with fields your retriever can filter:

chunk_meta = {
    "ticker": "PETR4",
    "type": "DFP",
    "year": 2024,
    "date_ref": "2024-12-31",
    "document_id": "<uuid>",
    "page": 42,
    "source_url": f"/v1/documents/<uuid>/file",
}

Chunking tips for CVM PDFs:

  • Prefer page boundaries from apicvm callbacks — they align with the source PDF.
  • Keep table rows together when possible; split on section headings in markdown.
  • Include document_id and page in every chunk for citations.

Step 4: Embed and query

Use any vector store (pgvector, Pinecone, Chroma, etc.). At query time:

# Pseudocode — adapt to your stack
results = vector_store.search(
    query=user_question,
    filter={"ticker": "PETR4", "type": ["DFP", "ITR"]},
    top_k=8,
)

context = "\n\n".join(
    f"[{r.meta['type']} {r.meta['year']} p.{r.meta['page']}]\n{r.text}"
    for r in results
)
answer = llm.generate(system=CITATION_PROMPT, context=context, question=user_question)

Filter by type and year when the question is time-bound ("Q3 2024 revenue" → prefer ITR 2024).

Step 5: Citations in the LLM response

Prompt the model to cite document_id and page. Example citation format for users:

> Source: PETR4 DFP 2024, page 42 (download PDF)

Linking to apicvm download URLs (or your cached copy) keeps answers auditable — the same requirement as SEC EDGAR workflows, adapted for Brazil.

Python orchestration sketch

import os, requests

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

def ingest_filing(ticker: str, doc_type: str, year: int, callback_url: str):
    docs = requests.get(
        f"{BASE}/v1/documents",
        headers=H,
        params={"ticker": ticker, "type": doc_type, "year": year, "perPage": 20},
    ).json()
    for doc in docs["data"]:
        if "Demonstrações Financeiras" in doc["name"]:  # example filter
            requests.post(
                f"{BASE}/v1/document-text-extractions",
                headers=H,
                json={"callback_url": callback_url, "document": {"id": doc["id"]}},
            )

Your callback handler does chunking and embedding — apicvm stops at page markdown delivery.

LLM tool discovery

Agent frameworks can load /llms.txt for endpoint summaries. For agent loops (not just batch RAG), see Brazil CVM Filings for AI Agents.

Current limitations

  • Extraction is async via callback — plan for latency between enqueue and full ingest.
  • Callback URL must be HTTPS in production.
  • Corpus grows with ingestion — verify availability before building company-specific indexes.
  • Filings are in Portuguese — add translation in your pipeline if users query in English.
  • apicvm delivers markdown per page, not pre-chunked or pre-embedded content.

Next steps

Ready to integrate?

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