Paginate and Filter CVM Documents via API

A useful CVM documents API filter lets you ask precise questions: “PETR4 DFP for 2024”, “ITR + DFP for one year”, or “FRE sections whose name contains risk”. apicvm exposes those filters on GET /v1/documents, with pagination capped so clients stay predictable under rate limits.

The problem

Brazilian issuers file many PDFs per year. FRE packages alone can return dozens of section files. Without filters you page through noise; without pagination you hit perPage validation errors or blow your request budget. Product code needs stable query parameters, not ad-hoc CSV joins.

Filter parameters

Parameter Type Use
ticker string Issuer ticker (e.g. PETR4)
cnpj string Company tax id
companyId integer Internal company id
companyName string Partial company name
type string Single type: DFP, ITR, FRE, …
types string Comma-separated: DFP,ITR
year integer Filing year
dateRef string YYYY-MM-DD or "Sem data"
name string Partial document name
search string Name or type search
id UUID Exact document id

Combine filters liberally. Typical product query: ticker + type + year.

Pagination

Parameter Rules
page 1-based page index
perPage Integer 1–50 (default 10)
field / order Sort control when supported by the list endpoint

Exceeding perPage=50 returns 422 validation errors. Loop pages until data is empty or shorter than perPage.

Example: curl — DFP for PETR4

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&page=1"

Multiple types in one call

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

Narrow FRE by name

curl -H "Authorization: Bearer $APICVM_KEY" \
  "$APICVM_URL/v1/documents?ticker=VALE3&type=FRE&year=2025&name=DescricaoFatoresRisco&perPage=5"

Example: Python — walk all pages

import os, requests

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

def list_all(**params):
    page, out = 1, []
    while True:
        r = requests.get(
            f"{BASE}/v1/documents",
            headers=H,
            params={**params, "page": page, "perPage": 50},
        )
        r.raise_for_status()
        batch = r.json()["data"]
        out.extend(batch)
        if len(batch) < 50:
            break
        page += 1
    return out

docs = list_all(ticker="PETR4", type="ITR", year=2024)
print(len(docs), "ITR files")
for d in docs[:3]:
    print(d["id"], d["name"], d["dateRef"])

Each item includes id, type, year, dateRef, name, and nested company with tickers — enough to choose a file before GET /v1/documents/:id/file.

Choosing filters for common jobs

Job Suggested filters
Annual package ticker + type=DFP + year
Quarterly monitor ticker + type=ITR + year (then sort client-side by dateRef)
Both financial forms types=DFP,ITR + year
FRE section hunt type=FRE + name= partial section title
Exact known file id= (skip broad lists)

FRE years often return many section PDFs. Prefer name filters (for example risk factors) instead of downloading every page of every file. See also filter by section name.

Validation errors on pagination

perPage outside 1–50 returns 422 with VALIDATION_FAILED and field messages in error.details. Fix the client — do not retry the same invalid value.

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

From list → file → text

Filtering only returns metadata. Next steps:

  1. Pick document.id from data[]
  2. GET /v1/documents/:id if you need a single metadata refresh
  3. GET /v1/documents/:id/file for the original PDF
  4. POST /v1/document-text-extractions when you need page markdown (async callback)

Keep list queries cheap (perPage just large enough) so watchlist jobs stay inside the rate limit window.

Current limitations

  • perPage maximum is 50.
  • Coverage is the ingested corpus (primarily DFP, ITR, FRE), not every CVM portal category.
  • Filtering by name is partial-match on stored document names; naming conventions vary by issuer.
  • types accepts comma-separated values; unknown types simply yield empty data for that slice of the corpus.

Next steps

Ready to integrate?

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