All Posts
TutorialEPS APIShares OutstandingXBRLArelleMulti-Class SharesDimensional FactsSEC EDGARVisa

Extracting EPS and Share Count From XBRL With Arelle (and the Multi-Class Filers That Break It)

Most companies tag EPS and weighted-average shares as plain XBRL facts. Multi-class filers like Visa (V) tag them only per share class, with no consolidated total, so a naive Arelle extractor returns nothing. Here is the as-converted formula that fixes it.

Published June 10, 202612 min readStockFit Engineering
Extracting EPS and Share Count From XBRL With Arelle (and the Multi-Class Filers That Break It)

Earnings per share and shares outstanding look like the two easiest numbers to pull from a financial filing. For most companies they are: the issuer tags us-gaap:EarningsPerShareBasic and us-gaap:WeightedAverageNumberOfSharesOutstandingBasic as plain XBRL facts, you read them with Arelle, and you are done. Then you hit Visa (V) and your pipeline returns nothing. No EPS. No share count. The numbers are in the 10-K, they are just tagged in a format that slips straight through a naive extractor. This is the story of why roughly 1,100 multi-class filers break the easy path, and the as-converted formula that puts a single correct EPS and share count back together.

Extracting EPS and share count from XBRL with Arelle

Every US 10-K, 10-Q, and foreign-issuer 20-F ships its financial statements in XBRL alongside the human-readable HTML. Each XBRL fact carries five things: a concept name (us-gaap:EarningsPerShareBasic), a value, a unit (USD/shares), a period, and an optional dimensional context. Arelle, the open-source XBRL processor, parses the filing and hands you that fact list. For the vast majority of issuers, the income-statement EPS and weighted-average shares come back as dimensionless facts, no dimensional context at all, so reading them is a one-liner:

json
// A normal filer (AAPL) — EPS and shares as plain, dimensionless facts
{ "concept": "us-gaap:EarningsPerShareBasic",                       "value": 6.11,            "unit": "USD/shares" }
{ "concept": "us-gaap:WeightedAverageNumberOfSharesOutstandingBasic", "value": 15022620000,    "unit": "shares" }

You keep the fact with no dimensions, classify its fiscal period, map it onto a normalized field, and serve it back. That is exactly what powers /api/earnings/eps-history for thousands of companies. The trouble starts the moment a company decides not to report a single, blended EPS.

The Visa problem: EPS and shares that vanish

We extract EPS and weighted-average shares the dimensionless way, fold them into the normalization layer, and it works across the market. Then a coverage check flagged a hole: Visa was returning no EPS and no share count at all. Not wrong values, no values. A multi-billion-dollar Dow component, blank.

Running Visa's 10-K back through Arelle and grepping for EarningsPerShareBasic explained it instantly. The concept was in the filing, several times over, but not once as a dimensionless fact. Every EPS and every weighted-average share count was tagged against a share-class dimension, and there was no consolidated total anywhere. Our extractor, which deliberately keeps the no-dimension fact, found exactly zero of them and moved on. The format had slipped through.

Multi-class shares in XBRL: one concept, many classes, no total

Visa has four share classes. Class A is the one that trades as V; Class B-1, Class B-2, and Class C are held by member banks and convert into Class A over time. Under US GAAP a company with multiple classes that participate differently in earnings has to report EPS per class (this is the two-class method in ASC 260). So Visa tags EarningsPerShareBasic and WeightedAverageNumberOfSharesOutstandingBasic once for each class, on the us-gaap:StatementClassOfStockAxis dimension, and never tags a blended company-wide figure, because, strictly, one does not exist in the statements.

json
// VISA FY2025 — EPS and weighted-avg basic shares, tagged per class (axis: StatementClassOfStock)
{ "concept": "us-gaap:EarningsPerShareBasic", "member": "us-gaap:CommonClassAMember", "value": 10.22 }
{ "concept": "us-gaap:EarningsPerShareBasic", "member": "v:CommonClassB2Member",       "value": 15.72 }
{ "concept": "us-gaap:EarningsPerShareBasic", "member": "v:CommonClassB1Member",       "value": 15.97 }
{ "concept": "us-gaap:EarningsPerShareBasic", "member": "us-gaap:CommonClassCMember",  "value": 40.87 }

{ "concept": "us-gaap:WeightedAverageNumberOfSharesOutstandingBasic", "member": "us-gaap:CommonClassAMember", "value": 1714000000 }
{ "concept": "us-gaap:WeightedAverageNumberOfSharesOutstandingBasic", "member": "v:CommonClassB2Member",       "value": 120000000 }
{ "concept": "us-gaap:WeightedAverageNumberOfSharesOutstandingBasic", "member": "us-gaap:CommonClassCMember",  "value": 9000000 }
{ "concept": "us-gaap:WeightedAverageNumberOfSharesOutstandingBasic", "member": "v:CommonClassB1Member",       "value": 5000000 }

This is the same XBRL mechanism behind revenue-by-geography and revenue-by-product, a concept reported against a member on an axis, which we covered in the Revenue Segmentation API post. There the axis is geography or product; here it is class of stock. The difference is what you are expected to do with it. Segment revenue is meant to be read per member. Per-class EPS is meant to be collapsed back into one headline number, and the filer leaves that step to you.

Visa is not a special case. Roughly 1,100 filers in the corpus tag EPS and share count only on the class-of-stock axis: Alphabet (Class A GOOGL / Class C GOOG), Berkshire Hathaway (BRK.A / BRK.B), Fox, News Corp, and a long tail of dual-class founders' companies. Any pipeline that only reads dimensionless EPS facts is silently blank for all of them.

Why you can't just sum the shares or average the EPS

The instinct is to grab the four per-class rows and combine them. Both obvious combinations are wrong.

Summing the share counts gives 1,714 + 120 + 9 + 5 = 1,848M shares. Divide Visa's ~$19.9B net income by that and you get $10.74 a share, overstated, because it treats a Class C share as economically identical to a Class A share. It is not: Class C earned $40.87 per share for the year versus Class A's $10.22. One Class C share is worth about four Class A shares in earnings power. Counting it as one undercounts the denominator.

Averaging the per-class EPS is worse: (10.22 + 15.72 + 15.97 + 40.87) / 4 = $20.69, a number that describes no security anyone can buy. EPS is a ratio, and you cannot average ratios that sit on different denominators.

The as-converted formula

The fix is to express every class in terms of the primary class, the one that actually trades, and the elegant part is that the conversion factor is already in the data. The ratio of a class's EPS to the primary class's EPS is its as-converted weight. Class C earns 40.87 / 10.22 = 4.00× Class A's EPS, so each Class C share counts as four Class A-equivalent shares. Put formally:

text
as_converted_shares = Σ over classes  ( shares_class × EPS_class / EPS_primary )

consolidated_EPS     = net_income / as_converted_shares    ( ≈ EPS_primary )

Apply it to Visa's FY2025 basic figures and the denominator reconstructs itself, class by class, into a single as-converted share count, and the consolidated EPS lands exactly on the listed Class A value:

VISA — as-converted basic shares (FY2025)weight = EPS ÷ Class A EPS
ClassReported sharesClass EPSWeightAs-converted
Class A · primary1,714M$10.221.00×1714.0M
Class C9M$40.874.00×36.0M
Class B-2120M$15.721.54×184.6M
Class B-15M$15.971.56×7.8M
Total as-converted basic shares1942.4M

Consolidated basic EPS = net income (~$19.9B) ÷ 1942.4M = $10.22, which reconciles to the listed Class A.

1,942.4M as-converted basic shares, and $10.22 basic EPS, the number a Visa shareholder actually experiences. The same method on the diluted facts yields 2,194.5M diluted shares and $10.20 diluted EPS. No blended figure was ever filed; both are computed from the per-class parts.

Where the formula gets hard

The one-line formula hides the real work, which is everything around it:

  • Pick the right primary class. It is the publicly listed, economically smallest-per-share class, not the one with the largest EPS. Choose wrong and every weight inverts.
  • Exclude the non-participating members. The same axis carries preferred, treasury, warrant, and escrow members that must not enter the share sum, even though they are tagged with the identical concept.
  • Handle classes with no EPS. A class can report shares but no per-class EPS in a given period; it needs a sensible conversion weight rather than being dropped or counted at 1×.
  • Do it for basic and diluted, every period type. Annual, the four quarters, and trailing-twelve-month, where 10-Q filings report cumulative six-month and nine-month figures that have to be unwound into standalone quarters first, the same period-reconstruction problem covered in converting XBRL financials to JSON.

Validating the reconstruction

A reconstruction you cannot check is a guess. Because the consolidated EPS must reconcile to the primary class's reported EPS, the formula carries its own internal test: if net income divided by the as-converted denominator does not match the filed Class A EPS to the cent, a member was misclassified or the wrong primary was chosen. Visa FY2025 closes the loop, $19.9B over 1,942.4M is $10.22, the filed Class A figure.

We also reconciled the computed Visa totals against an independent third-party source for annual, quarterly, and trailing-twelve-month periods, and they match the headline EPS and share counts those providers publish. So the API returns the same $10.22 basic / $10.20 diluted EPS and ~1.94B / ~2.19B share counts you would see on a Visa quote page, except here every input traces back to a specific per-class fact in a specific SEC filing.

Getting EPS and share count from the API

The whole point is that you never have to know whether a company is single- or multi-class. Whether the filer pre-computed a blended EPS or left four per-class rows for you to assemble, the same endpoints return one consolidated, correct number:

bash
curl "https://api.stockfit.io/api/earnings/eps-history?symbol=V&period=annual&limit=1" \
  -H "Authorization: Bearer YOUR_TOKEN"
json
[
  {
    "fiscalYear": 2025,
    "fiscalPeriod": "FY",
    "epsBasic": 10.22,
    "epsDiluted": 10.20,
    "weightedAverageShares": 1942383562,
    "weightedAverageSharesDiluted": 2194542157
  }
]

The same consolidated EPS and share count flow through /api/financials/income-statement and the per-quarter /api/earnings/snapshot, so the figure you get from the earnings history reconciles to the one on the income statement. If you are pulling EPS for a screen or a backtest, the earnings API walkthrough covers the full endpoint family, and the reconstruction is point-in-time safe: an amendment that restates a per-class figure inserts a new fact rather than silently overwriting the old one.

FAQ

How do you extract EPS and share count from SEC XBRL?

Parse the 10-K, 10-Q, or 20-F with an XBRL processor like Arelle to get the fact list, then read us-gaap:EarningsPerShareBasic, us-gaap:EarningsPerShareDiluted, and the matching WeightedAverageNumberOfSharesOutstanding concepts. For most companies these are dimensionless facts you can use directly. For multi-class companies they are tagged per share class and have to be combined into a consolidated figure first.

Why does Visa (V) return no EPS or share count from a naive XBRL parser?

Because Visa never tags a blended, company-wide EPS or weighted-average share count. It reports those numbers only per share class (Class A, Class B-1, Class B-2, Class C) on the StatementClassOfStockAxis dimension. A parser that keeps only the dimensionless fact finds none for Visa and returns nothing. The values exist; they are split across four dimensional facts that need to be reassembled.

What is a multi-class share structure in XBRL?

It is a company with two or more classes of common stock that participate differently in earnings (different voting rights, conversion ratios, or dividends). US GAAP requires EPS to be reported per class under the two-class method, so in XBRL each EPS and share-count concept is tagged once per class against the class-of-stock axis. Roughly 1,100 SEC filers report this way, including Alphabet, Berkshire Hathaway, and Fox.

How do you compute consolidated EPS for a multi-class company?

Use the as-converted method. Weight each class's share count by the ratio of its EPS to the primary (listed) class's EPS, sum to get the as-converted share count, then divide net income by that total. The result reconciles to the primary class's reported EPS. For Visa FY2025 the as-converted basic share count is 1,942.4M and the consolidated basic EPS is $10.22, the listed Class A value. You cannot simply sum the share counts or average the per-class EPS, both give wrong answers.

What is Arelle and why use it for XBRL?

Arelle is the leading open-source XBRL processor. It parses an SEC filing's XBRL into a structured fact list, each fact carrying its concept, value, unit, period, and dimensional context, and resolves the taxonomy so concepts and dimensions are unambiguous. It is the standard way to read raw SEC XBRL rather than scraping the HTML, and it surfaces the per-class dimensional facts that a multi-class EPS reconstruction depends on.

Which other companies tag EPS only per share class?

Any dual- or multi-class issuer: Alphabet (Class A GOOGL, Class C GOOG), Berkshire Hathaway (BRK.A, BRK.B), Fox, News Corp, Under Armour, and roughly 1,100 filers in total. For all of them, a single consolidated EPS and share count has to be computed from the per-class facts rather than read directly.

Which StockFit endpoints return EPS and share count?

/api/earnings/eps-history returns basic and diluted EPS with weighted-average basic and diluted share counts per period; /api/earnings/snapshot gives the latest quarter; and /api/financials/income-statement carries the same consolidated figures on the statement. For multi-class filers like Visa, all three return the computed as-converted number, so you never handle the per-class reconstruction yourself. The free tier includes EPS history for prototyping.

Ready to build?

Free API key, no credit card. Every endpoint mentioned in this post is available on the free tier.

Get Your Free API Key