Balance Sheets for Every SEC Filer: Frames API, Financial Statement Data Sets or companyfacts.zip?

The SEC does not publish a file of every company's balance sheet. It publishes three free things you can build one from, and each fails in a different way. We pulled the Frames API and the latest Data Set on 6 October 2026, read the size of the third, and tested what came out.

The three routes

RouteOne download gives you Which value you get
Frames APIone tag, one period, every filer, as JSON the last filed value that fits the period
Financial Statement Data Sets the numbers on the face statements of a quarter's filings. The 2026q1 file is 85 MB zipped, 559 MB unzipped what each filing reported, once per filing
companyfacts.zip every fact of every filer, 1.41 GB, rebuilt nightly all of them, from every filing

The SEC describes the first as returning "one fact for each reporting entity that is last filed that most closely fits the calendrical period requested", and the second as "without change from the 'as filed' financial reports". Those two sentences are most of the difference. The rest of this page is what they cost you in practice.

Frames: one request, but the value is the latest, not the filed one

A frame is the fastest way to a cross-section. One request per tag gives you every filer that reported it for a period, as {cik, entityName, end, val, accn}:

import requests

# the SEC requires a real contact address in the User-Agent
H = {"User-Agent": "Your Name you@example.com"}

def frame(tag, period="CY2025Q4I"):
    base = "https://data.sec.gov/api/xbrl/frames/us-gaap"
    url = f"{base}/{tag}/USD/{period}.json"
    rows = requests.get(url, headers=H, timeout=60).json()["data"]
    return {r["cik"]: r["val"] for r in rows}

A = frame("Assets")
print(len(A))   # 6157 filers on 6 October 2026

What it gives you is the value from the last filing that mentioned the period. Nike's balance sheet at 31 May 2013 is a worked example. Its 10-K, filed 23 July 2013, reported total assets of $17,584 million. The frame for that date, CY2013Q2I, returns $17,545 million, which is the figure in the 10-K filed a year later. Both are Nike's numbers. Only one was public in July 2013, and a screen or backtest built on frames cannot tell you which. The Frames API has no way to ask for the earlier value.

Frames: a frame is not a single date

Asking for CY2025Q4I returned 6,157 filers that reported Assets. They did not all report it for the same day:

Balance sheet dateFilers
2025-12-315,490
2026-01-31253
2025-11-30137
2025-12-2773
2025-12-2858
2026-01-03 and other dates146

The SEC's own documentation says to "be mindful different reporting start and end dates for facts contained in a frame". The retailers with a January year-end and the 52-week filers ending on a Saturday land in the same frame as December filers. Keep the end field and treat it as the balance sheet date, never the frame label.

Add the tags up and a quarter of them fail

The reason to pull Assets, Liabilities and equity together is to check one against the others. Run that on the raw tags and it fails more often than anything about a filer's arithmetic would explain:

L  = frame("Liabilities")
E  = frame("StockholdersEquity")
EN = frame("StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest")
LE = frame("LiabilitiesAndStockholdersEquity")

# within half a percent of assets
def close(x, y, tol=0.005):
    return abs(x - y) <= tol * max(abs(x), 1)

naive = [c for c in A if c in L and c in E]
print(sum(close(A[c], L[c] + E[c]) for c in naive), "of", len(naive))

better = [c for c in A if c in L and (c in EN or c in E)]
print(sum(close(A[c], L[c] + EN.get(c, E.get(c, 0))) for c in better), "of", len(better))

stated = [c for c in A if c in LE]
print(sum(close(A[c], LE[c]) for c in stated), "of", len(stated))
Test on the raw tags (CY2025Q4I) Pass
Assets = Liabilities + StockholdersEquity 3,906 of 5,147, about three in four
same, using the equity tag that includes noncontrolling interests where the filer has one 4,693 of 5,364, about seven in eight
Assets = LiabilitiesAndStockholdersEquity, the filer's own stated total 6,029 of 6,029, every one

The third line is the one to look at. Every filer that stated its own total agreed with its own assets, to within half a percent, so the filings are fine and the sum is the problem. StockholdersEquity is the parent's equity only. Of the 1,241 filers that failed the first test, 614 were fixed by switching to the equity tag that includes noncontrolling interests. (The second test also admits 217 filers that tag only the inclusive figure, which is why its population is larger.) 671 filers still fail it. Two more tags, minority interest where there is no inclusive tag and temporary equity (which is mezzanine), account for 306 of those. For the other 365 we did not find a cause among the tags we tested.

It is not rounding. At a tolerance of zero 4,517 pass; at five percent of assets, 4,874. 490 still fail. And 638 of the 6,157 filers have no Liabilities fact at all, because their balance sheet never prints the subtotal (that problem has its own note). The practical rule: when a filer states LiabilitiesAndStockholdersEquity, use it as the referee, and do not assume a failure means a bad filing. The five reasons a balance sheet fails the check are all in that residue.

Data Sets: as filed, with many rows per company

The Financial Statement Data Sets are the opposite trade. Nothing is replaced: the 2026q1 file holds 6,169 submissions (4,262 10-Ks, 972 10-Qs, 372 20-Fs, 112 40-Fs, and S-1s, 6-Ks and other forms) and 3,690,955 numeric rows. The SEC says the sets are "as filed" and also that it "cannot guarantee the accuracy of the data sets", which is worth reading as written.

For consolidated Assets in US dollars at 31 December 2025 the file has 5,027 rows, from 4,948 filers. The gap between those numbers, and what you have to filter before the count is right:

  • 72 filers appear in more than one filing for that date (a 10-K and a later 10-K/A, for instance). You choose.
  • 177 are tagged on the IFRS taxonomy (116 20-Fs, 50 40-Fs, 11 6-Ks). A request to the us-gaap Frames endpoint cannot see them.
  • 62 come from forms that are not periodic reports (S-1, POS AM, 6-K and similar).
  • Dimensional rows have to be dropped first. Only rows with empty segments and coreg are the consolidated figure. JPMorgan's FY2025 10-K has 23 Assets rows at one date and the first one in the file is EMEA's, $641.19 billion, not the bank's $4,424.9 billion (the long version).

companyfacts.zip: everything, and the choosing is yours

The bulk file is 1.41 GB, was rebuilt at 04:23 GMT on the day we measured, and contains every fact from every filing, comparatives and restatements included. Nothing is wrong with it. It simply hands you the selection problem: Apple's total assets at 28 September 2024 appear under five different filings, and the field labels are not what they look like (how to pick the right value). The per-company alternative is one request per filer: 6,157 of them is 10.3 minutes at the SEC's limit of ten requests a second, before you have parsed any of it.

None of the three carries a ticker. They are keyed by CIK, so you need the SEC's ticker file as well, and it lists current tickers only.

Which one to use

  • A screen as of the latest filings. Frames, with the stated total as the referee. Keep end, and accept that restated values replace originals.
  • A backtest, or anything that needs the original number and the day it was filed. The Data Sets (filing date is in sub.txt), or companyfacts with its accn and filed fields. Not frames.
  • Every period for every company, and you want to own the logic. companyfacts.zip, with a selection rule written and tested before you trust a number.

If you would rather not build this

BalanceProof is built on the Financial Statement Data Sets, as reported, and runs the check above on every filing: the filer's stated total is the referee, noncontrolling interests and four kinds of mezzanine equity are read when the filing uses them, and a filing that still does not reconcile is flagged with the reason instead of adjusted (how the check works). It covers 6,000+ US companies' periodic reports, not every filer. Every company page is free to read, the API gives a free key 1,000 calls a month, and the full dataset is one as-reported CSV of 1.3M rows, each dated by the filing that reported it, $79.99 once, built from the database each time you download it.

Questions

Does the SEC publish every company's balance sheet in one file?
No. It publishes three things to build one from: the XBRL Frames API (one tag for one period across all filers), the quarterly Financial Statement Data Sets (the numbers on the face statements of that quarter's filings), and companyfacts.zip (every fact of every filer, rebuilt nightly, about 1.4 GB). None of them is a table of balance sheets, and none carries a ticker.
Which SEC bulk source has the figure as originally filed?
The Financial Statement Data Sets report each filing's figures as filed, and companyfacts keeps every filing's value with its filing date. The Frames API returns one fact per company, the last one filed that fits the period, so a restated figure replaces the original.
Why does Assets not equal Liabilities plus StockholdersEquity in XBRL data?
StockholdersEquity is the parent's equity only. Noncontrolling interests and mezzanine equity sit outside it, and some filers do not tag a Liabilities total at all. In our pull of 5,147 filers with all three tags, 1,241 failed on the raw tags, while the filer's own LiabilitiesAndStockholdersEquity matched assets for all 6,029 that reported it.

See it on a real filing

Every figure on these pages is as reported, drawn at true proportion. No account needed.