Load Brazil CVM Filings into Google Sheets
Need Google Sheets CVM filings inventory without scraping the CVM portal? Use Apps Script + apicvm: resolve a ticker, list DFP/ITR/FRE rows, and write document UUIDs into a sheet your team can share.
The problem
Analysts often keep filing trackers in Sheets. Manual portal downloads do not scale across a watchlist, and copy-pasted filenames are hard to audit. You want document IDs that stay stable when someone refreshes the sheet.
Setup
- Create a Google Sheet with headers:
ticker,type,year,document_id,name,date_ref. - Extensions → Apps Script.
- Store the API key in Script Properties (
APICVM_KEY), not in cell formulas.
# Same key you use elsewhere
export APICVM_KEY='apicvm_...'
Base URL in script: https://apicvm.dev.
Apps Script: list DFP rows
function listDfpToSheet() {
var props = PropertiesService.getScriptProperties();
var key = props.getProperty('APICVM_KEY');
var base = 'https://apicvm.dev';
var ticker = 'PETR4';
var url = base + '/v1/documents?ticker=' + encodeURIComponent(ticker)
+ '&type=DFP&year=2024&perPage=20';
var res = UrlFetchApp.fetch(url, {
method: 'get',
headers: { Authorization: 'Bearer ' + key },
muteHttpExceptions: true,
});
if (res.getResponseCode() >= 400) {
throw new Error(res.getResponseCode() + ' ' + res.getContentText());
}
var data = JSON.parse(res.getContentText()).data || [];
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.clearContents();
sheet.appendRow(['ticker', 'type', 'year', 'document_id', 'name', 'date_ref']);
data.forEach(function (row) {
sheet.appendRow([
ticker,
'DFP',
2024,
row.id,
row.name,
row.dateRef || '',
]);
});
}
perPage max is 50. For larger inventories, loop page until meta.lastPage. See Paginate and filter.
Resolve before hard-coding
function resolveTicker(ticker) {
var key = PropertiesService.getScriptProperties().getProperty('APICVM_KEY');
var url = 'https://apicvm.dev/v1/companies/resolve?query='
+ encodeURIComponent(ticker) + '&by=ticker';
var res = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + key },
muteHttpExceptions: true,
});
if (res.getResponseCode() === 404) {
throw new Error(ticker + ' not in corpus');
}
return JSON.parse(res.getContentText());
}
What Sheets is good for (and not)
| Fit | Not a fit |
|---|---|
| Shared inventory of document UUIDs | Binary PDF storage inside the sheet |
| Light watchlists for IR / research | High-volume ETL (use a backend + Airflow) |
| Manual review queues | Sync markdown extraction (extraction is async via callback) |
For desktop Excel workflows, see Excel + Power Query. For BI models, see Power BI.
Rate limits
Apps Script triggers can fan out quickly. Stay under the API key window (X-RateLimit-*) and avoid parallel UrlFetchApp storms across dozens of tickers. Details: Handle errors and rate limits.
Current limitations
- Sheets is not a document store — download PDFs with curl or a backend using
GET /v1/documents/:id/file. - Text extraction is Pro-only, async via callback; Student keys get
403on extract. - Corpus coverage varies by company and year — empty lists are not “company never filed”.
Next steps
Ready to integrate?
Get an API key and start querying Brazilian CVM filings programmatically.