Target Specific CVM Filing Sections in AI Agent Pipelines

AI agents that analyze Brazilian equities fail when they treat CVM filings as single documents. A FRE for one company can be 70+ files; a DFP bundle separates statements, notes, and management reports. Pulling everything into context wastes tokens, slows extraction, and buries the answer in irrelevant pages.

apicvm gives agents a section-aware tool layer: resolve ticker, filter by document name, download or extract only the relevant section, return markdown with page numbers for citation.

Who this is for

  • Agent builders adding Brazil coverage to research assistants
  • RAG pipelines indexing CVM text by topic (risk, governance, footnotes)
  • Quant and credit teams automating issuer monitoring with LLM summarization
  • Fintech products exposing "ask about this company's risk factors" features

The problem

Generic filing tools force agents into a bad loop:

  1. List all documents for type=FRE → 70 candidates
  2. Guess which file contains risk factors
  3. Extract full PDF → 40 pages of governance prose the user did not ask for
  4. Stuff context window → hallucination risk rises

Section-aware agents instead map user intent → section name prefix → document ID → targeted extraction.

Section intent mapping

Define a lookup table from agent intents to CVM catalog name prefixes:

Agent intent type name prefix examples
Risk factors FRE DescricaoFatoresRisco, DescricaoRiscosMercado
Internal controls FRE DescricaoControlesInternos
Board governance FRE InformacoesConselhoAdm
Executive pay FRE PoliticaPraticaRemuneracao, PlanoRemuneracaoBaseadoAcoes
Financial notes DFP / ITR Notas
Management narrative DFP / ITR Relatório da Administração
Legal contingencies FRE Processos (varies — discover via list)

The agent selects prefixes based on the user's question, not the full filing type.

Agent tool design

Expose three tools to your LLM:

resolve_company(ticker) → company metadata
list_filing_sections(ticker, type, year, name_prefix) → [{id, name, dateRef}]
extract_section(document_id) → async; pages arrive via callback

Tool 2: list_filing_sections

import os, requests

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

INTENT_TO_SECTIONS = {
    "risk": ["DescricaoFatoresRisco", "Descricao5PrincipaisFatoresRisco", "DescricaoRiscosMercado"],
    "governance": ["InformacoesConselhoAdm", "DescricaoControlesInternos"],
    "compensation": ["PoliticaPraticaRemuneracao", "PlanoRemuneracaoBaseadoAcoes"],
    "footnotes": ["Notas"],
    "mda": ["Relatório da Administração", "Comentário"],
}

def list_filing_sections(ticker: str, intent: str, doc_type: str, year: int) -> list[dict]:
    prefixes = INTENT_TO_SECTIONS.get(intent, [])
    results = []
    for prefix in prefixes:
        resp = requests.get(
            f"{BASE}/v1/documents",
            params={"ticker": ticker, "type": doc_type, "year": year, "name": prefix, "perPage": 10},
            headers=H,
        )
        resp.raise_for_status()
        results.extend(resp.json()["data"])
    return results

Return compact JSON to the agent — id, name, type, dateRef only. Let the LLM pick the best match or ask the user to disambiguate.

Tool 3: extract_section

def extract_section(document_id: str, callback_url: str) -> dict:
    resp = requests.post(
        f"{BASE}/v1/document-text-extractions",
        json={"callback_url": callback_url, "document": {"id": document_id}},
        headers=H,
    )
    resp.raise_for_status()
    return resp.json()  # {"id": "42", "status": "queued", "document_id": "..."}

Your callback handler stores pages and signals the agent when extraction completes:

{
  "status": "success",
  "document_id": "...",
  "page": { "number": 3, "markdown": "..." },
  "current_page": 3,
  "total_pages": 12,
  "is_last_page": false
}

Example agent turn

User: "What are Vale's main commodity risk disclosures in the latest FRE?"

Agent reasoning:

  1. resolve_company("VALE3") → confirmed
  2. list_filing_sections("VALE3", intent="risk", doc_type="FRE", year=2024)
  3. Pick DescricaoFatoresRisco-4_1 document ID
  4. extract_section(id) → wait for callback pages
  5. Answer with quotes and page numbers from markdown

User: "Compare RADL3 footnotes on tax contingencies vs the management report."

Agent reasoning:

  1. list_filing_sections("RADL3", intent="footnotes", doc_type="DFP", year=2024)
  2. list_filing_sections("RADL3", intent="mda", doc_type="DFP", year=2024)
  3. Extract both sections (respect daily extraction quota)
  4. Cross-reference amounts and narrative

Token and cost optimization

Section filtering reduces agent cost materially:

Approach Typical pages extracted Context size
Full FRE bundle 200–500+ Too large for single prompt
Risk sections only 10–30 Fits structured analysis
DFP notes only 20–60 Footnote Q&A viable
Single governance section 5–15 Fast governance checks

Combine with retrieval: index extracted section markdown in a vector store keyed by (ticker, type, year, section_name).

Guardrails for agent builders

  • Always cite page numbers from callback payloads — apicvm markdown maps to source PDF pages
  • Disambiguate when meta.total > 1 — ask user or pick latest dateRef
  • Do not invent section names — discover via list calls when intent mapping returns empty
  • State extraction limits — Pro plan: 1 markdown extraction per API key per UTC day; batch overnight or upgrade for Scale
  • Async by design — agent must handle waiting/polling via your callback store, not synchronous HTTP

Current limitations

  • No semantic section search — agents must know CVM name prefixes or discover them via list + search param
  • No page-level classification in API — within a section PDF, your agent still parses markdown structure
  • Callback infrastructure required — extraction cannot complete inside a single synchronous tool call
  • Student plan — markdown extraction not available (403 FORBIDDEN); agents need Pro or above
  • Corpus gaps — agent should handle empty list results gracefully

Next steps

Ready to integrate?

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