10-K 10-Q Financial Data API: XBRL Fundamentals in Python

A 10-K/10-Q financial data API exposes the company facts that SEC filers report in XBRL-tagged filings: income statement, balance sheet and cash flow concepts such as Revenues, Assets or NetIncomeLoss, each with the period it covers, the form it came from and the date it was filed. Because the same concept is often reported multiple times, a good API lets you query point-in-time, returning only the values that were public as of a given date, so backtests and datasets do not leak future information.

What a 10-K/10-Q financial data API actually gives you

Every SEC-reporting company files 10-K annual reports and 10-Q quarterly reports, and the financial statements inside them are tagged with XBRL. The SEC publishes these as 'company facts': a long list of (concept, period, value) observations. A fundamentals API built on this data gives you one row per observation, with fields like cik, entity, concept (e.g. Revenues, Assets), unit, value, start, end, fy, fp, form and filed.

This is different from a price API or a news API. You are getting the actual reported numbers: revenue, net income, total assets, operating cash flow, and hundreds of other GAAP concepts, exactly as the company disclosed them, traceable back to a specific filing.

Pulling income statement, balance sheet and cash flow figures in Python

The three statements map to standard XBRL concepts. Income statement: Revenues (or RevenueFromContractWithCustomerExcludingAssessedTax), NetIncomeLoss, OperatingIncomeLoss, EarningsPerShareDiluted. Balance sheet: Assets, Liabilities, StockholdersEquity, CashAndCashEquivalentsAtCarryingValue. Cash flow: NetCashProvidedByUsedInOperatingActivities, NetCashProvidedByUsedInInvestingActivities, PaymentsToAcquirePropertyPlantAndEquipment.

With FinzData, which parses company facts directly from SEC XBRL filings for 10,435 SEC-reporting companies, one call returns a DataFrame you can filter by concept and period:

import finzdata as yf f = yf.Client().fundamentals(ticker="AAPL", concept="Revenues", as_of="2023-06-30") print(f[["end", "value", "form", "filed"]])

Each row tells you the period end (end), the reported value, which form it came from (10-K or 10-Q) and when it was filed. From there, reshaping into a quarterly or annual time series is a groupby on end or (fy, fp).

Filing-date alignment: the detail that makes or breaks a backtest

A company's Q1 numbers (period ending March 31) are not public on March 31. They become public when the 10-Q is filed, typically weeks later. If you align fundamentals by period end instead of filing date, your model 'knows' earnings before anyone else did. That is look-ahead bias, and it inflates backtest results in a way that disappears in live trading.

The fix is to join on the filed date, not the end date. FinzData's as_of parameter does this server-side: fundamentals(ticker="AAPL", concept="Revenues", as_of="2023-06-30") returns only the observations that were public on June 30, 2023. Anything filed after that date is excluded, so a signal computed on that date uses exactly the information an investor would have had.

If you build your own pipeline from raw filings, apply the same rule: for any simulation date T, keep only rows where filed <= T. Then, among the surviving rows for a given concept and period, decide how to handle duplicates, which is where restatements come in.

Restatements and revisions: which value is 'true'?

Companies revise previously reported figures. A 10-K might restate a prior quarter, or a later 10-Q might report a slightly different value for the same period end. In raw XBRL company facts, you will see multiple rows for the same concept and end date with different filed dates and sometimes different values.

There is no single correct choice, only a choice consistent with your goal. For backtesting, the point-in-time rule applies: on date T, the 'true' value is the most recently filed value with filed <= T. Later restatements must not leak into the past. For research on current fundamentals (e.g. building a clean historical series of what revenue 'really' was), you may prefer the latest filed value per period, accepting that this series is revised and not point-in-time.

Whatever you choose, record it. A dataset built with 'latest value wins' and one built with 'first filed value wins' will produce different training data and different backtest results. The filed column is what makes this auditable.

Practical gotchas when working with XBRL company facts

Concept names vary. Companies use different tags for similar concepts (Revenues vs RevenueFromContractWithCustomerExcludingAssessedTax), and some use company-specific extensions. Check which concepts a company actually reports before assuming a tag exists.

Durations vs instants. Income statement and cash flow concepts cover a duration (start to end); balance sheet concepts are instants (end only, no start). Mixing them up when computing period length leads to off-by-a-quarter errors.

Units and duplicates. Most values are USD or shares, but check the unit field. Also expect the same value to appear in both a 10-Q and the subsequent 10-K; deduplicate on (concept, end, value) or by keeping the row from the form you prefer.

Fiscal calendars differ. fy and fp are the filer's fiscal year and period, which do not always match calendar quarters. Use end dates, not labels, when aligning across companies.

A minimal point-in-time workflow

A sane workflow for a data scientist: (1) pick your concepts per statement; (2) pull the full history per ticker with the filed date attached; (3) for each rebalance or signal date T, filter to filed <= T and take the latest value per period; (4) merge with prices as of T; (5) only then compute ratios like P/E or margins.

Steps 3 and 4 are where most homegrown pipelines quietly go wrong. Using an as_of query pushes that logic into the data layer, which is easier to test and harder to misuse. FinzData offers this through the as_of parameter on its fundamentals endpoint, with the last 12 months free and full history plus point-in-time queries on the look-back plan.

Frequently asked questions

What is the difference between a 10-K and a 10-Q?

A 10-K is the annual report, covering the full fiscal year; a 10-Q is the quarterly report for fiscal quarters one through three. Both contain XBRL-tagged financial statements, and both show up in company facts data with the form field distinguishing them.

Why can't I just align fundamentals by quarter end?

Because the numbers aren't public on the quarter end date. Q1 results ending March 31 are typically filed weeks later in a 10-Q. Aligning by period end instead of filing date introduces look-ahead bias into backtests and training data.

How do restatements affect historical datasets?

The same concept and period can appear multiple times with different filed dates and values. For point-in-time correctness, use the most recent value filed on or before your simulation date. For a revised 'best known' history, use the latest filed value, but document which rule you applied.

Which XBRL concepts should I start with?

Income statement: Revenues, NetIncomeLoss, OperatingIncomeLoss. Balance sheet: Assets, Liabilities, StockholdersEquity. Cash flow: NetCashProvidedByUsedInOperatingActivities, PaymentsToAcquirePropertyPlantAndEquipment. Always verify which tags a given company actually reports, since usage varies.

How many companies can I get 10-K/10-Q data for through FinzData?

FinzData covers 10,435 SEC-reporting companies, sourced from SEC XBRL company facts, with point-in-time queries available through the as_of parameter.

Start free   See all datasets