Dados B3 › Transparency
Transparency
Transparency — how you check the numbers yourself
Data version 2026-09-27-reapresentacao-agreg · page generated 2026-09-27 06:52 UTC. If these do not match /saude, you are reading a cached copy.
What is checked, before every publication — 479 automated tests, collected live
86 are DATA invariants (does the number tie out?) and 120 are product tests — access gate, navigation, language, AI connector. Calling all 479 'invariants' would be inflating it: the split is checkable in the list itself. Not a hand-picked highlight reel: every test below was found by introspecting the actual test files just now, the same way pytest --collect-only would. Grouped by what it protects against, not by which file it lives in.
Level 1 — Integrity: does the data tie out? (82)
I-N100— When a re-filing was captured, the page shows month, field, before, after, difference and versions; when not, it says plainly that the CVM publishes only the version in force and that there is no “before” figure — never a silent empty table.I-N101— In /comparar/A/B each indicator comes from the comparison data in the house format (percent ×100 with one decimal, multiple with two and “x”, F-Score “/9”), FCF yield in %, the multiple year is one when it matches and “A × B” when not; the card carries name and sector; an account that does not exist for banks says whose it is; the JSON-LD lists both in positions 1 and 2.I-N102— /vigia/{T}: for a company or a fund, the ticker’s events — the SAME as the watcher’s, filtered by ticker — with a type filter, the follow form (companies only) and links to the ticker page, the general watcher and the JSON; a ticker with no event is noindex,follow; unknown is 404; not in the sitemap. The company “follow” block and the fund card link here.I-N103— On the real base, the company that publishes only the individual statements and has a restatement with one side zeroed in the CVM file: the restatements page says “individual — company does not consolidate” on each row and “no value in the file” where the side came zeroed, never R$ 0; the JSON at the same URL and /empresas/{T}/reapresentacoes carry the aggregation and the empty side, row for row as the table; the company page does not print 0.0 either. A company that consolidates stays con. PT and EN; no banned term.I-N104— On the real base: the curated sector comparisons (the three most liquid of each sector) never exceed the configured cap nor 60; each opens in PT and EN with the title “X × Y × Z: margin, ROIC, debt, P/E (year)”, the table with the three side by side and the verdict link of each; they are in the sitemap with lastmod (and so is the index), WITHOUT noindex, and the index lists them all; an unknown slug is 404; /comparar/A/B stays noindex; the page of a company in the trio links to “compare with the sector”. No banned term outside the disclaimers.I-N105— For 30 companies, 10 REITs, 5 ETFs and every fact page, the English HTML has the SAME data numbers as the Portuguese one (extracted by regex and normalised: 1.234,5 = 1,234.5; dates in ISO), the same anchors, the same internal links and the same number of rows in each table; the difference is text only. Source text in Portuguese (the subject of a filing with the CVM) is marked lang=pt and left out. A declared exception, with its reason, sits in the test.I-N106— On every parameter-free HTML page in the sitemap and a sample of the parameterised ones (companies, REITs, ETFs, guides, methodologies), in PT and EN: viewport meta; every img with alt; every form field labelled (label, label for or aria-label); every table inside a scroll container or covered by the style’s mobile rule (a loose table scrolls on its own); no fixed width wider than the screen. And the contrast of the style’s colour tokens — text, muted, accent, warning, positive, negative over background, surface and soft accent, in the light and dark themes — is at least 4.5:1. The report comes out per page.I-N20— Any page using the fact-page frame is registered in the panel's explicit list. The panel returns the thirty most-visited pages and its last row had twenty-eight visits, so a fact page born with five simply vanishes from it — and 'did not appear' reads as 'nobody visited' to whoever is looking. The fix was listing each one explicitly, which only works while the list stays complete; whoever adds the twelfth page and forgets to register it finds out here rather than a month later, staring at a zero that was never a zero.I-N21— Every path in the panel's fact-page list actually answers. The counterpart of the invariant above: without it the list can accumulate the path of a page that was removed, and the panel would publish an eternally zeroed row — a zero that does not mean 'nobody visited', it means 'this no longer exists'.I-N22— A company page answers 200 WITHOUT a key and is in the sitemap. That is the whole point of the page: the site was invisible three ways at once — the sitemap held 568 URLs of which 503 were audit samples and none was a company, a search for a well-known company's ROIC returned seven competitors and not us, and an AI asked for a bank's ROE hit a 401 and answered with another site. If anyone puts a gate here all three holes come back silently, because the page still exists and nobody can reach it.I-N23— The index on the stocks hub and the sitemap cover the same companies. Without it the index can list a code that does not open — a broken link on a page built to be indexed — or the sitemap can promise a URL that does not exist, and a crawler that hits a 404 arriving from the sitemap distrusts the whole site.I-N24— A withheld figure shows its reason on the company page instead of a blank. On a shop-window page the temptation to hide is greater, not smaller: a clean dash looks better than a dash with a caveat. The house rule holds here too, and this test pins it against real flagged indicators.I-N25— A fund page answers 200 in HTML WITHOUT a key, on the SAME URL that serves the JSON. The format comes from the Accept header: a browser reads, an agent consumes. Before this the hub linked each of its funds to a gated JSON, so a person who clicked a name got a 401 — explained, but shut — and to a search engine or an AI the fund did not exist. If anyone flips the order, gating the HTML or serving JSON to browsers, those links go back to being a closed door and the funds go invisible again.I-N26— The index on the funds hub and the sitemap cover the same funds. A page no other page links to is the last one a crawler visits, and a sitemap promising a URL that does not exist makes a crawler distrust the whole site, so the three lists — index, hub, sitemap — have to be one and the same.I-N27— A price-to-book with a caveat shows the caveat on the fund page instead of a clean number. A P/BV flagged out-of-range, shown bare, reads as a discount that is not there — and on a shop-window page the pull to tidy it away is strongest. The test picks a fund whose latest P/BV carries a flag and demands that flag in the HTML.I-N28— Without a key, a fund page returns the PAGE under any Accept header. The fourth time in this family, and this one was self-inflicted hours after the page was written: the route first negotiated on an explicit text/html, so Googlebot got the page while */* — the default of curl, GPTBot and ClaudeBot — got a 401. Search Console refused the indexing request for that URL and the reason was exactly this. The rule is the KEY, not the Accept: whoever sends a key wants the feed, and whoever does not has no access to the JSON anyway, so a 401 only hides public content.I-N29— The page and the JSON of an audit sample carry the same cases. Serving one sample in two formats risks them drifting apart, and then a seed would have two versions — which is exactly what TR24 prevents between the two URLs, while nothing prevented it between the two formats of one URL. An earlier draft of this invariant demanded the page be served to */* by analogy with the fund pages; the battery refused it, correctly, because there */* got a 401 with no content while here it gets the whole artefact, and that JSON is the citable proof the published protocol tells auditors to request.I-N30— No URL in the sitemap answers with a noindex tag. The two are opposite instructions, and for months 500 audit-sample URLs carried both: they were listed in the sitemap on the sound reasoning that a tool which only opens indexed URLs stalls otherwise, while the page itself said noindex — so it could never be indexed, the tool stalled anyway, and the entries took up 36% of a sitemap on a site where Google already reported forty pages detected-but-not-indexed. Each half was defensible alone and nothing looked at them together. The fix was to drop the samples from the sitemap rather than drop the noindex: the seed hub is indexable, sits in the sitemap and links all five hundred, so the tool reaches the hub and follows a link, while five hundred generated near-duplicates stay out of the index where they would compete with the real pages.I-N31— The screener table is rendered by the server, with no JavaScript. Building the table in the browser is the natural choice and would hand a crawler or an AI an empty page — and the 820 public pages opened in September are worth something precisely because the content is in the HTML. So sorting is a link and filtering is a GET form: the crawler walks exactly what a person sees, and every slice is an address.I-N32— Filters stack rather than replace, and each one can be removed on its own. A screener whose second filter silently replaces the first looks like it works — it returns rows and raises no error — and does not do the job; only counting catches it. Each active filter also carries a link that drops just that one and keeps the rest.I-N33— A row with no clean figure never leads the ranking. The classic sortable-table bug: a missing value becomes zero and the company we know nothing about shows up as the cheapest on the market. Flagged values are the same story — every negative P/E in the base carries a flag, so without this rule the top of 'cheapest' would be a queue of loss-makers. Those rows stay in the table with their figure visible and marked; they simply do not lead.I-N34— Every company page links to the other companies in its CVM sector, and every fund page to the other funds in its segment — all of them, none of itself. The 820 pages opened in September were born as islands, reachable only from the hub or the sitemap, and a page no other page cites is the last one a crawler visits; Search Console showed forty of them detected but not indexed. The grouping is the regulator's own classification, not ours.I-N35— Company and fund pages carry structured data (schema.org JSON-LD) that parses, names the right type — Corporation or InvestmentFund — and matches the page's ticker and CNPJ, with a breadcrumb trail. It is the format search engines and AI systems use to extract facts without reading prose. It is labelled JSON, not JavaScript: the screener stays script-free and this test does not touch it.I-N36— The about page names the person who builds the site, in both languages, with the credentials that speak to this product. It used to explain the operating company and say nothing about who makes it — and a site with no visible owner is the first thing a search engine or an AI discounts. A reference has a name.I-N37— The what-changed page opens under any Accept header, is in the sitemap and in the panel's explicit list, and declares both its window and how far each dataset reaches. It exists because after 820 open pages and a screener the site was an excellent dictionary with no reason to come back tomorrow; this is the recurring reason, built entirely from our own data. Each source lags differently, so the page shows the lag instead of hiding it.I-N38— Every figure on the what-changed page equals the database count for the same window, and an empty section says so in words. A page that summarises the last thirty days is the easiest place for a hand-written number to age silently; here the headline of each section is compared with the query behind it, and a zero is rendered as an explicit sentence rather than an absent block.I-N39— The what-changed page serves JSON only to a client that asks for application/json explicitly, and keeps serving the page to everything else. The format may follow the Accept header; access never may. That distinction cost this codebase four repetitions: on the fund pages the KEY decides, because there is a commercial gate there and answering 401 to */* hid public content from GPTBot, ClaudeBot and curl. Here there is no gate at all, so the header picks packaging and nothing else, and a missing or */* Accept still gets HTML. The totals in the JSON are compared against the database behind the page, so the two cannot drift apart.I-N40— The methodology index answers in JSON to whoever asks, carrying the title of each page. The MCP connector publishes a methodology() tool that called this index and parsed the result as JSON — and the result was always HTML, so any AI calling that tool got a parsing error instead of an answer. It survived because reading one page always worked; only the index was dead, and nothing on our side exercised that path. The lesson is not that JSON was missing: a published contract with no invariant exercising it is a promise, and the tool sat in the list, fully described, with no one on our side ever calling it.I-N41— A company page carries the quarterly series, every figure equal to the database, with no fourth quarter, in both languages. The annual series only exists once the fiscal year closes, so a company that filed its second quarter in July still showed its last full year, and all 455 pages stayed identical for twelve months. The quarterly data had always been in the database; it had never reached the person reading. A page that changes every quarter is one a search engine and an AI have reason to revisit; one that changes once a year is not. The fourth quarter stays out here for the same reason it stays out of the API: the interim filing does not publish it on its own, and deriving it would put a figure we computed in the same table as the figures the company reported.I-N42— The what-we-got-wrong page is generated from the invariants' own docstrings, not from a hand-kept list, and every dated defect in the code appears on it. Each invariant here was born from a real defect and keeps the story, with its date, in its docstring: sixty-five of them, written over months, none of which had ever left the code. What this test holds is not the page but the impossibility of curating it — a hand-written list would allow choosing what to show, and the temptation would be to omit the ugliest defect exactly when it is the most instructive. The page is a function of the docstrings: if a dated invariant exists in the code, it must be on the page.I-N43— An English page is either in English, or it says why part of it is not. Sweeping all seventy-five pages for words that exist only in Portuguese turned up eight, and seven were false positives of the most instructive kind: a company's legal name and the regulator's own account labels. Proper nouns and primary-source labels are not translated, because translating them would invent data the source does not have. The eighth was real: the audit-history page translated its frame and left seventy findings in Portuguese with no explanation. Translating those would be worse than leaving them — a rewritten audit record stops being a record, and the difference between what the auditor said and what we say they said is the whole point of keeping the table. So the page declares it, which is what this codebase does with every limitation.I-N44— Search finds an asset by ticker, by name with or without accents, and by tax ID; an exact ticker goes straight to the asset; an empty term returns nothing. The site had 824 asset pages and no search box at all: whoever arrived wanting one company had to open a list of 356 and hunt with their eyes, or guess the URL. That cost breaks no test and shows in no log — it shows on the first visit, which is the only one most people make. Accents cannot hide a result, because nobody searching knows how the regulator spelled the name, and requiring them to know turns search into a quiz. An empty term returns empty rather than the whole base, because 824 rows answering a question nobody asked look like a result and are not.I-N45— A comparison of two companies uses the most recent year in which BOTH have the figure and says which year that is, declares what does not exist for that kind of company, and never names a winner. Comparing one company's 2025 against another's 2023 renders cleanly and compares two different economies. Naming the better one would be a recommendation, which this site does not give, and a recommendation disguised as arithmetic, which is the worst kind. What this page has that others do not is trust side by side: each figure's caveat, and how many times each company restated its filings — a return on equity of 20% from a company that restated 113 rows is not the same figure as one that never restated.I-N46— The company page carries charts as SVG generated on the server — no script — one per indicator that applies; a year without a value is a gap in the line, a point with a caveat is drawn hollow, and the card’s last value is the database’s. The choice was deliberate: an AI reads the page as text and would receive a hole where an interactive chart sits; a phone opens it in half a second with no program to run; and a browser error is invisible from our side. What a chart must never do is invent continuity — joining two years across a year with no value draws a number that does not exist, the same defect as interpolation, only visual. And the caveat has to travel to the drawing: a point flagged in the table and clean in the chart is the table saying one thing and the figure another.I-N47— The company and fund pages show two prices with two owners, and say so. Ours is the closing price from the exchange's official daily file, entered every night, stored, served by the API, the connector and the dump, and reproducible by an audit. The other is a 15-minute delayed quote drawn by the TradingView widget: it arrives through their script, we never receive, store or serve it, and the page states that it never enters our API. This is the one exception to the no-script rule, decided by the owner after pricing the alternative (a delayed-data licence from the exchange plus a feed, display-only by contract). The widget is free only with attribution, and removing the credit gets a site banned, so the credit is our own text next to it and this invariant requires it. The page stays readable as text, with the closing price and its date, and no other script is allowed in.I-N48— The home page opens with the working shown for a real company: its return on invested capital recomputed from the raw filing lines, each line naming the regulator's account it came from, and the result is the SAME figure the base publishes. If the sum does not close, the section is not shown. The whole page carries a single script, the structured-data block; the live counts match; search works without JavaScript. The old home spoke to programmers, with a script playground and a headline about scraping, and 95% of visitors left without a click while two thousand people a month landed directly on company pages from AI assistants without ever seeing it. The new one is generated from the database, and instead of SAYING that every figure traces to the regulator's account it SHOWS it. A sum that failed to close there would be the opposite of what the site sells, so it is checked against the published indicator before it reaches the page.I-N49— The company page opens with what the company does and where it is (the registration-form profile, with its source declared), carries an anchor bar in which EVERY entry points to a section that exists, and a server-side SVG price chart whose last point is the last close in the base. The owner compared the site with a competitor and was right about the company page, which is where people land. What they do with 81 scripts and 747 KB can be done with HTML and SVG: the profile in cards, the sticky section bar and the price series. The chart is the close in the BASE, monthly, not split-adjusted and saying so, not a third-party quote: the same figure the API serves.I-N50— The fund page has what the company page has: a profile in cards with its source declared, an anchor bar in which every entry points to a section that exists, three server-side SVG charts (price, point-in-time P/BV, distribution per share) whose last point matches the base, and a trimmed P/BV series (24 reports) with the full one a link away. After the profile, the anchors and the chart entered the company page, the fund page looked like an earlier version of the product: no anchors, no chart, and a P/BV table with one report per row since 2010 on the main screen. First item of the post-review roadmap. What changed is the presentation; the figures are the same, and the test checks the last P/BV and the last distribution on the chart against the base.I-N51— The company page opens a “Size and valuation” block: market value (today's close times the latest public share count), enterprise value, earnings and book value per share, 12-month dividend yield, payout and share count, each card with its year, and the point-in-time multiples now carry the year in their label. The market value follows the SAME rules as the multiple (units, counts in thousands, plausibility band) and the yield is the one the dividends route serves. “P/E 33.1” did not say when, and what every competitor shows at the top was missing although all of it was already in the base. The risk was recomputing market value without the three rules the multiple learned the hard way: the largest bank came out at R$ 458 million (a count in thousands), a unit would come out N times inflated, and a garbage count from the registration form would become trillions. The test pins the three with real and synthetic cases.I-N52— Next to the close, the company and fund pages show the 1-month, 12-month and year-to-date return and the 52-week range, computed from the closing series in the base. A window that crosses a stock split or reverse split gets NO figure: a dash with the reason. A series shorter than 52 weeks says “since X” instead of “52 weeks”. The series is not split-adjusted (the exchange's file is not, and it is what the API serves): a 12-month return across a 10-for-1 split would read as −90% and look like a tragedy that never happened, so every window is checked before it becomes a number.I-N53— Next to each indicator and multiple, the company page shows the median of the OTHER companies in the same regulator sector, each in its latest year, with how many they are. Only figures without a caveat enter; the company itself stays out; below three peers there is no median. The page used to answer the number and not the question “is 32% return on equity a lot?”, while the fund page already said what the peers' median was. Median rather than mean because one company in judicial recovery with a return of minus two hundred percent drags the mean and not the median; clean figures only because a caveat (scale, capital without an average) must not become a reference for the others.I-N54— Below the ratios, the company page shows the history in REAIS — revenue, controlling net income, EBITDA, net debt, cash and investments, equity — year by year with the unit in the label. The values are the base's own (standardised lines and the methodology's derived figures), cash exists only in a year where the cash account was filed, and banks and insurers get “does not apply” where the account does not exist instead of a dash. A ratio alone hides size: forty billion in revenue and forty million have the same margin. Cash adds cash and financial investments because that is what the return-on-capital formula deducts; investments without a cash line do not become “cash and investments”, since a partial sum would read as a whole one.I-N55— The dividend calendar (on the today page, the home and the asset pages) shows only what has a date ahead — fund payments and ex-dates, stock ex-dates — never an impossible date (the exchange writes 9999-12-31 for “to be defined”), and SAYS how far the base can see (the date of the newest announcement it holds). When an asset has nothing scheduled the page says so with that date instead of staying silent. Fund distributions arrive with a payment date and the base held 138 future payments; stock dividends arrive with an ex-date only, and since the exchange source is read on Sunday the newest announcement was 18 days old with no future ex-date. Promising “the next 30 days” on Sunday's data would sell delay as freshness, so the page carries the cut-off date with it.I-N56— The future ex-date of a stock comes from the dividend report the company files with the regulator (the IPE category Relatorio Proventos): the form is read (approval date, last day with rights, ISIN, amount, payment date), enters without a type because the form does not separate dividends from interest on equity and the page says so, does not repeat what the exchange already published (same company, share class, ex-date and amount), keeps only the share class of the main ticker, and every item carries the link to the document. Free-format filings stay recorded as unparsed, with the link. HTML served in place of the PDF is not stored; a new version of a document replaces the old one; what was already read is not downloaded again. The exchange only publishes a stock dividend after its ex-date: a sweep of the whole universe (356 issuers) on 2026-09-11 found zero future ex-dates, while the regulator filing arrives a median of two days before, with a payment date the exchange does not carry.I-N57— The last pages that still ran JavaScript now come ready from the server: the sector race is an SVG drawn in Python with window and scale as links, the foreign-flow chart is an SVG drawn in Python, the transparency page filters by family through a link the server answers, and the methodology title is written into the page instead of rewritten in the browser. Apart from the JSON-LD block and the quote widget the owner allowed, no public page carries a script — an AI agent and a phone read the same page a browser does.I-N58— The company page lists every cash dividend of the main share class as a table: type, record date, ex-date, payment date and amount per share. The ex-date is the first trading session after the record date taken from our own price history, never a guessed business day. The payment date comes from the exchange company file, and when that file has none, from the filing to the regulator, marked as such. The year filter is a link the server answers, the default shows the latest 24 rows, and a summary sentence states the latest dividend, the trailing yield and the 12-month count and sum. The exchange file also fills payment dates on existing rows and adds dividends the list endpoint had not published; a date of 9999 is not a date.I-N59— The price chart on the company and fund pages takes its window from a link (one month, three, six, year to date, one year, five years, everything) and draws the closing price from the base at the granularity that fits: daily up to a year, weekly for five years, monthly for the whole history. The label states the return of the window, first close to last, annualized only when the window is clearly longer than a year. A split inside the window is not averaged away: the series starts after it and the label says so. No script is involved; every window is a page of its own.I-N60— The table in reais gains two columns from the quarterly filing: the latest quarter on its own and the trailing twelve months, built by the same identity as the TTM price-to-earnings (previous year minus its quarters to date plus this year to date); a missing piece leaves the cell empty, never interpolated. Balance lines (cash, equity) take the quarter-end figure; lines without a quarterly series (EBITDA, net debt) stay blank. Every multiple and valuation card carries a one-line definition, and the valuation block adds price to operating cash flow and price to free cash flow, outside banks and insurers, as with enterprise value.I-N61— After the coherence pass on the company page (the price chart sits with the quote, valuation shows its two moments in one section, the sparklines sit with the indicators they summarize, nine anchors instead of eleven), the page gains three things seen at a competitor and built only from what the base already held: indicators grouped by theme (valuation, debt and liquidity, efficiency, profitability, growth), each with a one-line definition and a mark when today market value is divided by an account of the fiscal year, with no multiple over a non-positive denominator and nothing that does not apply to banks and insurers; the three statements in one table (income, balance sheet, cash flow) with derived lines named for what they add up; and the corporate actions with their multiplier and whether they matched the observed price move.I-N62— From a second competitor, and again only from what the base already held: a return table by window (one month to ten years) with the price alone and with dividends reinvested at the close of the ex-date, the starting price adjusted by the corporate actions checked against the price and a jump they do not explain leaving the window blank; yield per year over the close of the year's last session and the five-year average; the months with a record date in the last five years, counted rather than projected; the average daily traded value over the last sixty-three sessions on the company card; and a checklist of nine objective facts with the threshold written in each line, banks and insurers exempt from the ones that do not apply, described as facts and not as advice.I-N63— The today block of the company page carries the twelve-month snapshot other sites call current: price to earnings over the trailing twelve months built by identity from the quarterly filings, price to book on the latest quarterly balance, return on equity and net margin over the same twelve months, each naming the quarter, with a note saying which cards use the last four quarterly filings and which use the annual one. A non-positive denominator yields no number. The ROIC card states the whole formula, and the methodology page explains why our ROIC differs from other sites (window, tax rate, invested capital on the average with goodwill), after an external audit found the largest gap there.I-N64— The company card shows the company logo, and the way it is done is the defensible one: the source is the website the company itself declares in its registration form, never an aggregator; every file carries its origin (URL, source, date) in a versioned manifest; the image is a square 64-pixel PNG of at most forty kilobytes served from our own domain, so no third party is called when a page is viewed; the alt and title say where it came from; without a file there is no image and no generic icon in its place; and a company listed in the withdrawal file disappears at the next build.I-N65— The return table gains a real line: the reinvested return divided by the inflation accumulated in the window, from the monthly IPCA the central bank publishes (series 433), compounded month by month with the edge months pro rata by days. A month missing inside the window leaves the cell blank, never interpolated; a missing latest month, which is only published around the tenth of the following month, stops the count there and the label says up to which month it went. The series is read in the build and every night, and a build that cannot reach the source inherits what the previous base had.I-N66— The company page gains shareholders and free float from the reference form the company files with the regulator, in its most recent version: only direct holdings, with the form's own Others line as the market and the capital distribution as the free float, and the page states the filing date and version; the chain of control is not reproduced and no taxpayer id is stored. Tag along comes neither from the form nor from the exchange, so what is published is the minimum guaranteed by the listing segment or by law, labelled as a minimum. Revenue by segment does not exist in structured public data and is therefore absent.I-N67— The price chart and the one-month, twelve-month and year-to-date line adjust the series by the corporate actions the base checked against the observed price drop, instead of cutting the series short or leaving the window blank. The point of each event carries a dashed mark with its factor, the label says how many events it adjusted and stops claiming the prices are unadjusted when they are. An event that was not checked adjusts nothing: a jump nobody explained still cuts the series, with the reason. The last close shown is always the one actually traded.I-N68— An old trading code redirects with a permanent redirect to the current one and the destination page says where the visitor came from, instead of silently answering a different question. A live share class of the same company never redirects. A mistyped code lands on a not-found page that suggests the closest codes, and a code that resembles nothing gets no invented suggestion.I-N69— The calendar shows the future payment date of a stock dividend, which the exchange company file carries and which until now appeared only in the company table, the way fund distributions already did. The filing to the regulator still covers only what the exchange has not published: the same dividend never appears twice under two sources, and a payment of a share class other than the main ticker's never appears at all.I-N70— Every public page wears the same frame and the same palette. Methodology, comparison, guides and the connector page were born with a header and footer of their own, without search or language, and the frame is now swapped at serving time for the site's. No page may declare a fixed colour outside the token block, because a hard-coded light background turns into unreadable text under the reader's dark system theme, and the owner decided against a theme button, so the system theme is the only one there is. A variable used without a definition, or pointing at itself, is a defect too. The categorical palette of the sector chart is the declared exception: it is colour of data, not colour of frame.I-N71— Three loose ends closed. Next to the sector median comes the rank in the sector, computed from the very values the median used, with the direction declared (high return on equity is good, a low price-to-earnings is good) and no rank invented for an indicator with no obvious direction. On the fund page the median of the segment peers leaves the loose paragraph and joins the price-to-book and yield cards, as it already did for companies. And each line of the table in reais gains the shape of its series as a tiny line, breaking where a year is missing and drawing the zero line when the series changes sign.I-N72— The home page opens with the market as the base sees it: the biggest gains and falls of the last session, the highest trailing twelve-month yields and the sectors by twelve-month return of our own total-return index. Three rules keep a data site from turning into a tip sheet: only stocks that actually traded that session, because below that a gain is the noise of a parked stock; any stock with a corporate action that day is left out, so a split never shows up as a fifty per cent fall; and the section states the date, the criterion and that it is descriptive, with no adjective anywhere.I-N73— What the company filed with the securities regulator outside its financial statements — material facts, notices to shareholders, minutes, market announcements — becomes a list on its page, newest filing first, each with the link to the document at the regulator. It is an index, not content: we do not download, summarise or classify the document, because our own summary of a material fact would be the first interpretation this base ever published. The subject line is the company’s own free text and goes as it came. The source is the same public filing index we already download every night for dividends, one extra reading of the same file, and if it does not answer the previous list stays standing.I-N74— Five things that only show up when you read the whole site at once, and each of them chips at the same impression — that one base sits behind the pages. The home announced seventeen years of filings while the Petrobras page, on the same visit, said sixteen; the extra year came from six companies whose financial year falls outside the calendar. Closed financial years are what count now. The English home showed the dividend type exactly as the source writes it, in Portuguese capitals, and charged in reais while the English subscription page charges in dollars — one plan with two prices in one language. The sector line above the company name stayed in Portuguese on the English page, with the translation two lines away. And the quarterly table printed figures with no unit at all. The version stamp now lives in the footer, once per page.I-N75— The /para-quem page is the summary an AI assistant can quote: who the service is for, who it is NOT for, what it costs and how to start, in Portuguese and English, with the FAQ as schema.org/FAQPage JSON-LD. What it states must be true NOW: the connector's tool count comes from the MCP server, the company/FII/ETF counts from the same indexes that feed the sitemap, the price from llms.txt. The 'when not to use' section names at least four limits, each also present on the limits pages — no limit invented to look honest. Every FAQ question in the JSON-LD appears as text on the page.I-N76— The FII and ETF hubs serve JSON at the same URL. The ETF ranking is open in both formats; the FII ranking follows the key rule of the internal MCP tool: with a key or an explicit Accept: application/json it returns the tool's dictionary, and without a valid key it returns a 401 in JSON with 'como_resolver', never the HTML page. The public connector (dados-b3-mcp) reads exactly these two routes, so this test also guards it against silently receiving HTML.I-N82— Earnings per share, book value per share and the share count come out in the API, and they are the SAME figures the company page shows — one computation, not two. A reader asked for it: he builds a Graham fair-value spreadsheet pulling book value and earnings per share 'from the base', but neither was in the JSON, and he could not derive them either, since the share count is absent from /fatos too. The block carries the page's three guards (units, thousands scale, a plausibility band against equity) and declares that it belongs to a different time than the annual series: today's snapshot, not point-in-time — that series stays what backtests use.I-N83— A testimonial meets the same demands a number does, adapted to what it is: somebody else's words. It has an owner and dates (who said it, since when they use the API, and when they gave permission — no anonymous 'a user says' here); it is verbatim, in the order it was written, with cuts marked; it is labelled as opinion rather than a figure of ours; and it sits OUTSIDE the proof strip, where every item links to the page that backs it. A translation declares itself a translation, because the original is in Portuguese.I-N84— The page about companies that left the exchange measures the hole without pretending to fix it, and publishes no inferred ticker. Every figure on it is counted from the database as it is served — none is typed into the prose. A trading code appears only where the company itself declared it in its registration form; matching by name was measured and errs (one in 28 checked), and a wrong ticker that looks right ruins a backtest silently. And the page states that recovering a fraction does not remove survivorship bias, pointing at the limits page instead of claiming a reconstruction.I-N85— The screener's `as_of` filters by what was PUBLIC on that date, not by the financial year. About 100 days pass between a year ending and its statement existing at the regulator, so asking for 'year 2018' returns an answer nobody had in 2018; `as_of` asks the question research needs — what could have been chosen on that day. Every row declares the date its statement became available, none uses a statement filed after the date asked, and the response states what `as_of` does NOT fix: the universe is still today's active companies, with the size of that hole measured on the page about the ones that left.I-N86— The portfolio-in-a-link page computes value, weight, return and dividends to receive, and hides nothing it could not compute. An unknown ticker is listed as left out instead of silently changing everyone else's weight; the database keeps one price series per company (the main share class), so a holder of the other class gets no borrowed price; every dividend shows its record date, since only holders on that date receive it. Nothing is stored — the address is the portfolio — and there is no recommendation.I-N87— `screener?...&recibo=1` freezes the answer at a permanent address; the same question on the same data version yields the SAME receipt; the published sha256 matches the stored result following the recipe in `como_conferir`; and the receipt stays the same after the database changes, while its page says the data moved on. A screener list is only valid until the next update — a receipt that changes with the data, whose hash does not match, or that duplicates on every request is a cache, not a receipt.I-N88— Every company data response (API and connector) carries a ready-to-copy `citacao` field: source page, data version and the date of the company's last filing received by the regulator. An AI that reads a number without it writes 'according to public data'; with it, it writes the source. There is also a stable address per indicator (/empresas/{ticker}/indicadores/{name}) with the series, the company's own range, the sector median, the universe distribution and the methodology link.I-N89— /empresas/{ticker}/veredito answers without a key: six numbers in three questions (does it make money? is it indebted? is it expensive relative to its sector and its own history?), each with a numeric band and comparisons to the sector median, the company's own range and the whole universe, plus the confidence flag about the SNAPSHOT itself (restatement, staleness, flagged values) with its reasons, a top sentence of at most 25 words, and the ready-to-copy citation. No judgement adjective, no recommendation; the same comes through the AI connector.I-N90— The company page opens with the verdict — the same top sentence, flag and six texts as the JSON — anchored, with a 'how we compute it' link on every number and no forbidden term. Page and JSON cannot diverge.I-N91— /small-caps lists ONLY the companies inside the configured liquidity window (floor ≤ average < ceiling), between 30 and 120 of them, with none inside the window left out; sorts by each of the six numbers via links, sending companies without the number to the end; filters by sector; and states on the page that the verdict exists for every company, with the path. Listed in the sitemap, llms.txt and the home page.I-N92— /acoes/{T}/reapresentacoes answers in its H1 'Has {name} ever restated its financial statements? In which years?', with one row per restated account (the same count as the data layer) and the CVM file of the year that revealed each one, in Portuguese and English; an unknown ticker is a 404; the page is in the sitemap and linked from the company page when there is a restatement.I-N93— Every company page carries a FAQPage with the five questions of the AI-citation script, and every NUMBER in every answer appears in the page's visible HTML (for the restatement question, in that company's restatements page). No FAQ text, Portuguese or English, carries a forbidden term; and two companies get different answers — it is data, not fixed text.I-N94— Every multiple card on the company page and the point-in-time note state 'price of DD/MM/YYYY', and the multiples JSON carries the same text in preco_de, matched to data_preco. A multiple without its date gets quoted as if it were today's.I-N95— The company page <title> is generated from the data — 'WEGE3: ROE 32.2%, P/E 33.1 (2025)' — with the ROE of the last year without a caveat and the P/E of the last filing, each in the page's own number format; the meta description carries the year's figures, not the corporate purpose. With no clean figure, the generic sentence.I-N96— Generated comparisons answer noindex,follow and are not in the sitemap; a company's restatement page is in the sitemap only when the company restated — otherwise it is noindex,follow. Company, REIT, ETF, fact and guide pages stay indexable, and the FAQPage stays on company pages only.I-N97— Every company page in the sitemap carries lastmod = the receipt date of its latest filing at the CVM; restatement pages the filing that revealed the latest one; REITs and ETFs their latest report. Text pages carry no lastmod, and no date is in the future or the build date.I-N98— /acoes lists every company with a page, by sector, each with ticker, name and the ROE of the last year without a caveat (the same figure as its page; a dash when there is none); the footer of every page links /acoes, /fiis and /etfs.I-N99— /fiis/{T}/reapresentacoes: the H1 is the question, the first line answers it, and the table lists every month with version above 1 — the same as the database — with the delivery date and the CVM file; links to FNET and to the fund page, which links back. Without any re-filing the page is noindex,follow; the sitemap has only funds that re-filed, with lastmod = the latest delivery of a version > 1.
Level 2 — Semantics: does the account mean what the calculation assumes? (4)
I-N77— The three pages that lacked JSON-LD in the 'as an AI' test (/hoje, /buscar, /comparar) now carry ONE valid schema.org block in the head: the screeners declare a SearchAction pointing at /procurar?q=, /hoje is dated by the DATA VERSION (not by today), and /comparar/A/B lists both companies with ticker, resolved name and the URL of each company page. Portuguese and English.I-N78— Every public read-only response without a key carries Cache-Control: public, max-age=300 and Vary: Accept-Language (the same URL serves PT or EN by browser header); no response carrying an API key, and no session, billing, protocol or keyed-data route, is ever publicly cacheable; 404 and 503 are not cached.I-N79— `pip install dadosb3` is the first move anyone who writes code makes, so it appears at the three doors that person arrives through: the /api hub, llms.txt (the door AI assistants read) and the /para-quem page, in both languages. The link points at PyPI — the source the reader can check — not at a page of ours.I-N81— When the filing to the regulator declares an instalment payment, the asset's calendar says so and links to the document, instead of going silent about the payment. The form carries the FORM of payment (lump sum or instalments); an instalment payment has no single date, it has several, inside a document we do not parse. Before, such a line produced no payment event and looked exactly like a dividend with no date at all — the reader concluded there was no scheduled payment, when what exists is a payment in instalments we have not opened.
Level 3 — Economic: is the result possible in the real world? (0)
Other product tests (19)
Access gate, navigation and the MCP connector — they test whether the product works, not whether a number is right, so they don't fit the 3 levels above by design. Still counted, still listed:
I-N01— Crawls every page from the home: no internal link may answer an error, and every page must have a way back home.I-N02— Methodology aliases (URLs people GUESS, like /metodologia/pl) redirect to the canonical page — and a name that does not exist still 404s, so the alias does not become a hole that swallows everything.I-N03— Every HTML page carries a visible language switch.I-N04— The methodology — the product's central argument — exists in English.I-N07— Every configured vertical has a door, and the door speaks both languages.I-N05— Every public page declares a viewport and has a responsive rule.I-N08— The main menu, in both languages, has a door to every vertical.I-N06— A route that depends on a NEW table answers 503, never 500, while the table does not yet exist.I-N09— A table mixing two time bases must label each of them.I-N10— Every example the /screener menu offers must be accepted by /screener itself. Two of the three used to return 400 — the menu exists precisely for someone arriving without knowing how to use the endpoint, and handing them a broken example is worse than handing them nothing. The check runs against the same indicator set the filter consults at runtime, so renaming an indicator without updating the examples fails here.I-N11— The /screener menu answers callers who have NO key. The menu exists, in its own words, for 'whoever discovers the endpoint and opens it in a browser' — precisely the person who does not have a key yet, and for months it sat behind the 401. The test calls the endpoint with no key header and demands 200 with the indicator list, while also demanding that filtering stays closed — otherwise the fix would leak the paid product.I-N12— /saude publishes the version of the data being served, and it matches the database actually in use. Born from a sceptical external review: 'I want to say my backtest used Dados B3 version X and recover that dataset later'. The field existing is not enough — a label that drifts from the database is worse than none, because it grants false confidence. The test reads `meta` straight from the database, outside the route, and demands the two agree.I-N13— The restatements page is public while the per-company feed stays behind a key. This is the first fact page, and the rule behind it comes from measurement: over thirty days the AI assistants fetched the home page 209 times, the comparison page 15, the multiples methodology 14 — and the TWENTY conceptual guides added up to 7. An assistant already knows what a price-to-earnings ratio is; it fetches whoever answers what it does NOT know. Restatements are the one product dataset no competitor publishes, because the regulator overwrites the old version of a statement and we keep both. The split mirrors the rankings page: the AGGREGATE is public because it is acquisition, the per-company detail is paid because it is the product. Publishing the whole feed would give away what sustains the paid tier; hiding the aggregate would hide precisely what differentiates us.I-N14— The data-quality page lists what we do NOT publish, and why. Writing it fixed a defect before anything was published: while assembling the breakdown of the eight thousand withheld indicators, over a thousand turned up with a NULL flag, meaning they were withheld with no stated reason. On a page whose whole subject is why a figure is missing, thirteen percent of 'I do not know' is the page contradicting itself. The cause was zero revenue on the income statement, which happens in holdings whose result comes from equity income and in companies with no operations that year: the withholding was correct and the reason was simply not declared. Those became explicit flags and the silent cases fell to fifty-three. Withholding without saying why is half the house rule; the other half is saying it, and that is the half a public page enforces. The test also caps the silent cases, because without a ceiling the next rule that withholds without declaring would go unnoticed exactly as these did.I-N15— The bank chart-of-accounts page proves that an account code does not define an account. The fact it carries: one large state-owned bank reported profit under one code through 2019 and under a different one from 2020 onward, while the largest private bank stayed on the original code for the entire series. Anyone scraping the regulator with a fixed code gets the private bank right, gets the state-owned one wrong from 2020, and receives NO ERROR at all: they get an empty value, or another line's number. The stable case being the most famous bank is what makes this dangerous, because it is the one everybody tests a scraper against, and it passes. Seventeen of twenty-one institutions changed their equity account, most of them in the same year. This is not our theory: an external auditor found the mirror defect in our own output, where we declared one account and used another, and that correction became its own invariant. This page publishes the map that episode showed was missing, and the test attacks by checking the contrast survives.I-N16— The sustainability-reporting page states that the obligation was REPEALED, with the date. The idea arrived with an out-of-date premise, and that is what made it worth publishing: it came as companies being required to report from 2026 with the first reports in 2027, which was true until 29 May 2026, when a new resolution repealed the requirement outright rather than postponing it. Almost everything written in Portuguese on the subject predates that repeal and still says it is mandatory, so a page that corrects information an assistant would otherwise repeat is exactly the kind of page that gets cited. The page also declares what we do NOT have: we ingest annual and quarterly financial filings, not sustainability reports, so this is regulation rather than our own measurement. Without that admission the page would imply we measure environmental and governance data, which would be more useful to us and less true. The test attacks by removing either the word that carries the correction or the admission of the limit, and it also requires the sources to appear on the page, because a rules page without sources is an assertion.I-N17— The page listing the traps of the regulator's CSV counts them from the database rather than asserting them from memory. Every trap on that page bit us, and several were found by external auditors checking what we publish: the extra dimension in the statement of changes in equity came from one of them, who received six rows where our coordinate promised exactly one. The risk with a page like this is that it becomes a theoretical list of precautions for using public data, the kind anyone can write and nobody verifies. What distinguishes it is the COUNT, and the count has to come from the database: written by hand it goes stale in silence, which is precisely the defect another invariant exists to catch. The test attacks by hardcoding a figure into the text instead of querying for it, and it also checks the thousands separator matches the language, since a Brazilian separator reads as a decimal point in English.I-N18— The look-ahead page measures rather than asserts, and it separates a filing delay from a restatement. The theory of look-ahead bias is everywhere; the Brazilian NUMBER is not — how many days pass between a fiscal year ending and its statements existing at the regulator. It is about a hundred days on average, and a hundred and sixty-one filings took more than a year, which is the figure anyone calibrating a lag actually needs. The extreme case requires a caveat the page makes explicitly: the largest gap in the base is a bank showing an eight-year interval, and that is not a delay in the original filing, it is a restatement being republished years later. Selling that as a filing that took eight years to appear would be technically true and materially misleading, the same error nearly made when ranking restatements by percentage. The page also states what lagging the filing does NOT fix — survivorship, historical universe, delisting — because a correction that implies more coverage than it delivers is its own kind of look-ahead.I-N19— Someone arriving without a key gets the PAGE, whatever their Accept header says. This was a discovery defect found in production: the first version negotiated on an explicit text/html header, so a browser got the page while the wildcard header - the default for curl and for several automated fetchers - got a 401, as did a request with no Accept header at all. The page is listed in the sitemap and in the file we publish for AI agents, so anyone arriving through either got a closed door on the most differentiated page we have, and a 401 reads as nothing-here rather than wrong-format. This is the third time in the same family: the connector endpoint answering 406 in a browser, the audit route missing from the AI index, and now this one - the pattern is always a door that only opens for someone who already knows how to knock. The correct rule is not the Accept header but the API KEY: whoever sends a key wants the feed and existing integrations must not break, while whoever sends none has no access to the JSON anyway, so answering 401 merely hides public content.
Reconstruct it yourself — net margin across 5 companies, 4 sectors
We don't ask you to trust the numbers. Here's the net margin of five companies in four different sectors, each rebuilt straight from its annual report (DFP) at the CVM — net income ÷ revenue, matching the published figure exactly. For ANY indicator, of any company, the whole chain down to the line in CVM's file is at /linhagem:
| Company · sector | Net income (CVM acct) | Revenue (CVM acct) | Margin =÷ |
|---|---|---|---|
| WEGE3 · industrial | R$ 6.78 bn DRE:3.11 | R$ 40.80 bn DRE:3.01 | 16.6% |
| VALE3 · mining | R$ 11.81 bn DRE:3.11 | R$ 213.59 bn DRE:3.01 | 5.5% |
| PETR4 · oil & gas | R$ 110.61 bn DRE:3.11 | R$ 497.55 bn DRE:3.01 | 22.2% |
| ITUB4 · bank | R$ 45.85 bn DRE:3.09 | R$ 387.12 bn DRE:3.01 | 11.8% |
| BBAS3 · bank | R$ 16.78 bn DRE:3.11 | R$ 319.46 bn DRE:3.01 | 5.3% |
Download any of these DFPs from the CVM, take the income and revenue accounts, divide — you get the same number. Banks use interest income as revenue (what makes sense for a bank), so the reconciliation is sector-aware; the others use sales revenue. Figures are the latest fiscal year.
Why we may differ from another site (and it's not an error)
A difference between two sites usually isn't one being wrong — it's a method choice. We disclose ours:
- Average vs ending capital: ROE and ROIC use average equity/capital (this year + last ÷ 2), not the ending balance — so they're not a naive single-year division.
- Controlling vs consolidated: we state which one each item uses.
- TTM vs annual: quarterly multiples use trailing-twelve-months earnings.
- IFRS 16, goodwill, cash, exceptional tax: each handled explicitly and flagged when it distorts.
Do the tests bite? Yes — real cases the suite has caught
A test that never fails could mean perfect data — or a weak test. These were born from real errors that slipped through, and now fail — only shown for tests that actually exist in the collected suite above:
- I-B16 · banks' P/E = 0: the CVM reports share count sometimes in units, sometimes in thousands (varying by company and year); market cap came out 1000× too small and P/E was zero. I-B16 (price × shares recomputed) caught it — we fixed 661 annual and 2,027 quarterly multiples.
- I-B24 · the vanishing profit: the profit/equity account varies by bank (Itaú 3.09, BB 3.11); if the label search fails, the number vanishes silently and ROE is born wrong. I-B24 makes that hole fail.
- H-B23 · the 800% dividend yield: old B3 dividends carry a price not adjusted for splits; without H-B23 the yield would look absurdly real. The flag keeps the record and warns.
- I-B20 · the missing COTAHIST 2023: on the first deploy one year of prices failed to download silently; the API shipped with ~50k fewer prices and the suite passed, because nothing checked coverage. We added I-B20 — a year without prices now fails the build loudly.
- I-B33 · Oil & Gas at −89.6% in one week: COTAHIST isn't split-adjusted; a 10:1 split read as a −90% weekly return and stayed in the cumulative return forever, with nothing failing (a blind amplitude filter didn't fix it either — it just flipped which direction was wrong). I-B33 (no sector week beyond ±35%, the ruler is the exchange itself) stops the build.
- I-B31 · 364 of 865 sector-index weeks frozen: extending the series back to 2010, a sector with no company carrying a market cap that week turned into a factor of 1.0 — a flat line that the cumulative return read as 'market stood still', and the 'since 2010' return came out fictional (+527%). I-B31 fails on any week repeated to the cent.
- I-E05 · EMBRAER's 2010 P/E priced with 2025 data: the point-in-time price lookup fell forward with no ceiling whenever the current ticker had no price at balance-sheet time (ticker change, share-class migration); the multiple came out priced 15 years into the future, in 22% of annual and quarterly rows, silently. The 45-day window (and I-E05) blocks it: no session inside the deadline, no multiple computed.
- I-E04 · AZUL at R$ 1.67 quadrillion market cap: the balance sheet carried 54.7 trillion shares from the judicial recovery issuance, but the price used was already on the post-reverse-split (1:150,000) basis — two ends measured on different bases, market cap wrong by orders of magnitude. I-E04 (market cap never above Brazil's GDP) fails the build on that absurdity.
How far back each block of data goes
No asterisks here: this is coverage year by year, counted right now. Indicators (ROE, ROIC, margins, growth) come from the filings alone and cover the whole series. Multiples (P/E, P/BV, EV/EBITDA) need a price and a share count — and the share count comes from CVM's reference form, which the further back you go the fewer companies filed in a usable format. The gap between the two columns is a source limit, not an ingestion hole. If you look for an old P/E and do not find it, it is because nobody has it — not because we hid it. The same numbers as JSON: /cobertura.
| Year | Companies with indicators | Companies with multiples |
|---|---|---|
| 2025 | 438 | 290 |
| 2024 | 444 | 296 |
| 2023 | 442 | 298 |
| 2022 | 429 | 295 |
| 2021 | 423 | 296 |
| 2020 | 410 | 274 |
| 2019 | 371 | 164 |
| 2018 | 326 | 164 |
| 2017 | 317 | 162 |
| 2016 | 311 | 152 |
| 2015 | 303 | 143 |
| 2014 | 301 | 139 |
| 2013 | 292 | 130 |
| 2012 | 291 | 111 |
| 2011 | 284 | 97 |
| 2010 | 277 | 34 |
Live coverage
456 companies · 876,076 price points · 8,654 recorded restatements · last refresh 2026-09-27 00:20:28. Full live counts at /saude.
Sources: CVM (open data, ODbL) and B3 (COTAHIST). Not affiliated with B3 or the CVM. Not investment advice.
Numbers on this page are live. Data version 2026-09-27-reapresentacao-agreg · page generated 2026-09-27 06:52 UTC. If this does not match /saude, you are reading a cached copy.