Access Brazil CVM Filings with Rust
Need a Rust CVM filings API client for Brazilian public companies? apicvm is plain HTTPS JSON. Use reqwest, a Bearer token, and the /v1/* routes — there is no official Rust crate.
The problem
Rust services that need CVM filings often end up:
- Shelling out to brittle portal scrapers
- Parsing local ZIP dumps that do not expose stable document IDs
- Reimplementing auth and pagination for every language binding
A typed HTTP client against a fixed contract is simpler to keep in CI.
Setup
export APICVM_URL='https://apicvm.dev'
export APICVM_KEY='apicvm_...' # from /signup
Add to Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
Resolve a ticker
use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
use serde_json::Value;
use std::env;
fn auth_headers() -> HeaderMap {
let mut h = HeaderMap::new();
let key = env::var("APICVM_KEY").expect("APICVM_KEY");
h.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {key}")).unwrap(),
);
h
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base = env::var("APICVM_URL")?;
let client = reqwest::Client::new();
let company: Value = client
.get(format!("{base}/v1/companies/resolve"))
.query(&[("query", "PETR4"), ("by", "ticker")])
.headers(auth_headers())
.send()
.await?
.error_for_status()?
.json()
.await?;
println!("{company}");
Ok(())
}
List DFP documents
let docs: Value = client
.get(format!("{base}/v1/documents"))
.query(&[
("ticker", "PETR4"),
("type", "DFP"),
("year", "2024"),
("perPage", "20"),
])
.headers(auth_headers())
.send()
.await?
.error_for_status()?
.json()
.await?;
for row in docs["data"].as_array().unwrap_or(&vec![]) {
println!("{} {}", row["id"], row["name"]);
}
perPage max is 50. Paginate with page until meta.lastPage. See Paginate and filter CVM documents.
Download a PDF
async fn download_file(
client: &reqwest::Client,
base: &str,
doc_id: &str,
path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let bytes = client
.get(format!("{base}/v1/documents/{doc_id}/file"))
.headers(auth_headers())
.send()
.await?
.error_for_status()?
.bytes()
.await?;
std::fs::write(path, &bytes)?;
Ok(())
}
File download does not consume extraction credits.
curl cross-check
curl -H "Authorization: Bearer $APICVM_KEY" \
"$APICVM_URL/v1/companies/resolve?query=PETR4&by=ticker"
Errors and rate limits
Map status codes in your Result: 401 auth, 404 missing document, 429 rate limit. Read X-RateLimit-Remaining when fan-out across tickers. Details: Handle errors and rate limits.
Current limitations
- Text extraction (
POST /v1/document-text-extractions) is async via HTTPS callback — not a sync Rust return value. perPagemax is 50.- Corpus coverage depends on ingestion lag; resolve before hard-coding CNPJ.
Next steps
Ready to integrate?
Get an API key and start querying Brazilian CVM filings programmatically.