Extract segment revenue from a 10-K with XBRL
Get revenue and operating income by business segment, product line and geography from a 10-K's XBRL, using Alphabet's fiscal 2025 annual report.
Segment data lives in the notes to the financial statements, not on the face of the income statement. In XBRL it is tagged with dimensions: the same concept (revenue) is reported many times, once per business segment, product line or region, each with a segment qualifier. /v1/xbrl-to-json with notes=true returns those notes, so you can rebuild the segment tables without parsing HTML.
How dimensional facts look
Every fact in the response has a value, a period and, when it is a breakdown, a segment. One dimension comes back as an object, several as a list:
{
"value": "224532000000",
"period": {"startDate": "2025-01-01", "endDate": "2025-12-31"},
"unitRef": "usd",
"segment": [
{"dimension": "srt:ProductOrServiceAxis", "value": "goog:GoogleSearchOtherMember"},
{"dimension": "us-gaap:StatementBusinessSegmentsAxis", "value": "goog:GoogleServicesMember"}
]
}
The axis tells you what the breakdown is (StatementBusinessSegmentsAxis for segments, ProductOrServiceAxis for product lines, StatementGeographicalAxis for regions). The member tells you which one.
The script
import os
import requests
BASE = "https://secfeedapi.com"
HEADERS = {"Authorization": f"Bearer {os.environ['SECFEED_KEY']}"}
# Alphabet's 10-K for fiscal 2025 (find it with /v1/filings?query=ticker:GOOGL AND formType:"10-K")
params = {"accessionNo": "0001652044-26-000018", "cik": "1652044", "notes": "true"}
d = requests.get(f"{BASE}/v1/xbrl-to-json", params=params, headers=HEADERS, timeout=180).json()
def dims(fact):
seg = fact.get("segment") or []
seg = [seg] if isinstance(seg, dict) else seg # one dimension comes as an object, several as a list
return {s["dimension"].split(":")[1]: s["value"].split(":")[1].removesuffix("Member") for s in seg}
def by_member(facts, axis, ignore=("ConsolidationItemsAxis", "StatementBusinessSegmentsAxis")):
"""{(member, fiscal year): $ billions} for full-year facts broken down on `axis` and nothing else."""
out = {}
for f in facts:
dm = dims(f)
if axis in dm and all(k == axis or k in ignore for k in dm) and "startDate" in f["period"]:
out[(dm[axis], f["period"]["endDate"][:4])] = int(f["value"]) / 1e9
return out
def table(title, data, years=("2024", "2025")):
print(title)
print(" " + "".ljust(42) + "".join(f"FY{y}".rjust(9) for y in years) + " change")
for m in sorted({m for m, _ in data}, key=lambda m: -data.get((m, years[-1]), 0)):
a, b = (data.get((m, y)) for y in years)
chg = f"{(b / a - 1) * 100:+.0f}%" if a and b and a > 0 and b > 0 else ""
print(" " + m.ljust(42) + "".join(f"{v:9.1f}" if v else " " * 9 for v in (a, b)) + chg.rjust(9))
print()
revenue = d["RevenuesRevenueByType"]["RevenueFromContractWithCustomerExcludingAssessedTax"]
op_income = d["InformationAboutSegmentsAndGeographicAreasRevenueAndOperatingIncomeLossBySegment"]["OperatingIncomeLoss"]
table("Revenue by segment ($ billions)", by_member(revenue, "StatementBusinessSegmentsAxis", ignore=()))
table("Operating income by segment ($ billions)", by_member(op_income, "StatementBusinessSegmentsAxis", ignore=("ConsolidationItemsAxis",)))
table("Revenue by product or service ($ billions)", by_member(revenue, "ProductOrServiceAxis"))
table("Revenue by geography ($ billions)", by_member(revenue, "StatementGeographicalAxis"))
Output
Revenue by segment ($ billions)
FY2024 FY2025 change
GoogleServices 304.9 342.7 +12%
GoogleCloud 43.2 58.7 +36%
AllOtherSegments 1.6 1.5 -7%
Operating income by segment ($ billions)
FY2024 FY2025 change
GoogleServices 121.3 139.4 +15%
GoogleCloud 6.1 13.9 +128%
AllOtherSegments -4.4 -7.5
Revenue by product or service ($ billions)
FY2024 FY2025 change
GoogleAdvertisingRevenue 264.6 294.7 +11%
GoogleSearchOther 198.1 224.5 +13%
SubscriptionsPlatformsAndDevicesRevenue 40.3 48.0 +19%
YouTubeAdvertisingRevenue 36.1 40.4 +12%
GoogleNetwork 30.4 29.8 -2%
Revenue by geography ($ billions)
FY2024 FY2025 change
US 170.4 194.2 +14%
EMEA 102.1 117.2 +15%
AsiaPacific 56.8 67.7 +19%
AmericasExcludingUnitedStates 20.4 23.9 +17%
Reading it
- Alphabet reports three segments. Google Cloud grew revenue 36% and more than doubled operating income in fiscal 2025.
- The product table mixes levels:
GoogleAdvertisingRevenueis the subtotal ofGoogleSearchOther,YouTubeAdvertisingRevenueandGoogleNetwork. Companies tag subtotals and their parts alike, so check the HTML table once before summing members. - Operating income by segment is tagged with an extra
ConsolidationItemsAxis(operating segments versus corporate costs), which is why the script ignores that axis for that table.
Making it work for other companies
- Note names differ. Sections are named after the company's own notes (
RevenuesRevenueByTypehere). Search the keys forSegmentorRevenueto find them:[k for k in d if "Segment" in k]. - Concepts differ. Some companies tag segment revenue as
Revenues, others asRevenueFromContractWithCustomerExcludingAssessedTax. Try both. - Members are company specific.
goog:GoogleCloudMemberis Alphabet's own extension. Standard members such asus-gaap:OperatingSegmentsMemberare rarer. - Annual versus quarterly. The script keeps duration facts (with a
startDate) and keys them by end year. For 10-Qs, key by the full period to separate the quarter from year to date.
Next steps
- XBRL-to-JSON Converter and XBRL-to-JSON financial statements.
- Compare one concept across companies with frames.
- Download XBRL XSD and instance files to work with the raw taxonomy.
- Form 10-K dataset and the API reference.