Skip to main content
Every item below is measured, not suspected. Where something is planned it says planned and says why it has not shipped; where the answer is “this cannot be fixed”, it says that instead. Conventions & limits is the contract — caps, nulls, freshness, errors. This page is the list of places where the contract is correct and still not enough.
Nothing here is a bug report. A limitation that would be fixed by fabricating a value — a guessed ticker↔CNPJ join, a derived split factor, an English label keyed on a code that means two things — stays a limitation on purpose.

Account codes are not comparable across sectors

CVM’s chart of accounts is sector-specific. The same account_code carries a different line in a bank’s filing than in an industrial company’s, and nothing in the code itself tells you which chart you are reading. Measured against the live API for PETR4 (industrial) and Banco do Brasil (cd_cvm 1023, bank):
Do not compare a ratio built on an account code across sectors. 3.05 is operating profit before financial result for an industrial and pre-tax profit for a bank — two different levels of the income statement. A screen that ranks both on 3.05 is ranking two different quantities and will look plausible while doing it.
Four consequences, all visible in the response:
  • setor and segmento are on every financials and company_financials row. They are there to be used as a partition key, not read as a label: partition every median, rank and percentile by setor, and the comparison above stops being a cross-chart one. This is what makes the limitation navigable — it does not remove it.
  • api.financials returns the as-filed Portuguese account_name. It is the only label in the payload that is correct for every chart, because it is the one the company filed. Read it, not just the code.
  • There is deliberately no canonical English line-item mapping. Keying one on account_code would mislabel one sector or the other — 3.03 cannot be both “gross profit” and “gross financial intermediation result” under one name.
  • Insurers and other regulated sectors are expected to differ too, and have not been characterised. Treat “not a bank and not an industrial” as unknown rather than as one of the two columns above.
Shipped (catalog v29). api.income_statements returns the income statement as one row per period with named fields, and it resolves each field from the as-filed label rather than from the account code. chart names the layout the filing used (industrial, bank, insurer). setor and segmento also ride on every statement row (v28) for peer medians.Keying on the label turned out to be more complete, not just safer: net income sits on 3.11 for two charts, on 3.09 for a third that files no 3.11, and on 3.13 for insurers. Since v36 company_financials.net_income is keyed the same way, so the two surfaces agree.Shipped (catalog v35). api.balance_sheets and api.cash_flow_statements follow the same design. On the balance sheet, equity sits on 2.03, 2.07 or 2.08 depending on the chart, and where one label is filed twice (current and non-current Empréstimos e Financiamentos) the parent line’s label tells them apart. Banks file no current/non-current split and no debt line, so those fields are null for them. The cash flow maps only the section totals and the cash reconciliation: capex and dividends are free text per filer, so read those lines with financials.Still not offered. A canonical English mapping for every line. The named field set above is a bounded, measured subset; a full mapping is not, and a code-keyed one would still mislabel a chart.Planned, and deliberately behind it. A friendlier financials output shape — readable period labels, scaled values, year-over-year change. It is deferred precisely because the sector separation has to land first: a convenient wide shape built on today’s codes would harden the mistake above into an interface.

net_income is keyed on the filed label, never on a code

Since catalog v36, api.company_financials resolves net_income the same way as api.income_statements: from the filed label Lucro/Prejuízo Consolidado do Período (or Lucro ou Prejuízo Líquido Consolidado do Período), wherever the chart puts it. Until v35 it read 3.11 alone. That returned null for the 282 bank-B statements (Itaú Unibanco and BTG Pactual among them) and, for insurers, returned the continuing-operations line under the name net_income. No code is substituted for another. For Banco do Brasil, FY2024, consolidated: 3.09 is profit before statutory profit-sharing on this chart, and matches 3.11 only because 3.10 is zero. A COALESCE(3.11, 3.09) would be wrong for the first filer with a non-zero 3.10; the label match never reads it. This is verified for one bank. It is not a claim about every bank, and the non-bank charts have not been swept for the same pattern.

Not every listed company resolves from its ticker

A ticker resolves to a company only through CVM’s published FCA valores-mobiliários map, and that map covers only the companies that published it. Nothing is name-matched — a guessed ticker↔CNPJ join is exactly what this API refuses to make. So a ticker can be real, traded, and still resolve to nothing. ITUB4 does:
The quote row exists, so the ticker is found; the company arm is empty, so financials resolves nothing and returns 200 []. An empty array here means “unresolvable”, not “this company filed nothing”. Workaround: resolve by company name or cd_cvm with api.lookup, then pass the id it returns. A company row carries cnpj and a tickers array; those ids read fundamentals reliably.

A quarterly filing publishes every account twice

An ITR prints the same account under one ref_date twice — once for the three-month span, once year-to-date — distinguished only by period_start / period_end, surfaced as period_months. Nothing is collapsed for you, because collapsing means choosing, and either choice is wrong for someone.
Filter on period_months before you aggregate, chart or average anything. Adding a 3-month row to a 6-month row double-counts the quarter, and the two rows are otherwise identical in every field you would normally group by.
And you cannot reliably rebuild a cumulative figure by summing quarters:
  • A company with a non-calendar fiscal year breaks the arithmetic — São Martinho (cd_cvm 20516) files April–March, so its nine-month period starts 1 April, not 1 January.
  • A restated earlier quarter breaks it too; only the newest version of each statement is served, so the quarter you would subtract may no longer be the quarter that was originally filed.

Quotes are unadjusted, except one column

close, close_unit and the adjusted flag are as traded, and adjusted is false on every quote row: a statement of fact rather than a default waiting to be flipped. A return computed from them across a split date is wrong, by the split ratio. The one adjusted price is quote_history’s close_adj, its default field (and, with cash reinvested, the selectable close_total_return). It is continuous across splits, groupings and bonus shares, anchored to the ticker’s latest session, and a window SILO cannot stand behind is refused with the cause, never served raw: outside shares and units, before the issuer’s corporate events are proven swept, or at an event this version does not adjust. close_total_return is NULL with a reason where a distribution cannot be valued. Spin-offs, mergers, capital reductions and subscriptions are not adjusted. quotes, the typed views and panel are unchanged. Nothing is interpolated or forward-filled either; see Market coverage.

The B3 lending and flow group is a ratchet

Roughly 21 business days of retention at the source, no archive, and no backfill at any price. It affects short_interest, short_interest_by_sector, investor_flow, lending_trades and lending_participants: a session not captured on the day is gone, not expensive. This is the one limitation on this page that no amount of work here can fix, and it is documented in full — including the silently clamped HTTP 200 — under the B3 BDI ratchet. Read it before you plan a long lending series.

Next

Market coverage

How far back each domain goes, and the ratchet in full.

Conventions & limits

The contract: caps, null semantics, freshness, errors.

Paging without losing rows

Three ways a response gets cut, and only one tells you.