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:
- Validates the callback payload
- Attaches metadata (
ticker,type,year,page) - 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_idandpagein 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
- Read the API docs
- Get an API key
- Extract text from CVM PDFs — callback handler details
- CVM filings API getting started
Ready to integrate?
Get an API key and start querying Brazilian CVM filings programmatically.