Most "fundamentals" data you can pull into Python arrives as bare numbers. Revenue was 109.4 billion. According to whom, from which filing, under which accounting line? If you are building anything that someone else will read, that question comes back to you eventually.
This tutorial pulls Apple's revenue history with the netnest-fundamentals package, which wraps the NetNest Design Fundamentals API (I run it). The point of the API is that every value carries the SEC filing it came from, so at the end we will open the filing and check the number ourselves.
The output below is from a run on 2026-11-04. Yours will have newer rows once companies file again.
Install and get a key
pip install netnest-fundamentals The shared key demo works for trying things (500 calls a day across everyone). For your own key, 100 calls a day once you verify your email:
from netnest_fundamentals import NetNestClient print(NetNestClient.signup("you@example.com")) The welcome email carries a verification code. Verify it with a POST to /v1/signup/verify as described at https://api.netnestdesign.com/docs, then set NETNEST_API_KEY in your environment; the client reads it by default.
Annual revenue, newest first
from netnest_fundamentals import NetNestClient nn = NetNestClient("demo") # or your key; defaults to NETNEST_API_KEY r = nn.history("AAPL", "revenue", period="annual", limit=10) for v in r["data"]: print(v["fiscal_year"], v["period_end"], v["value"], v["form"], v["accession_number"]) 2026 2026-09-26 478834000000 10-K 0000320193-26-000026 2025 2025-09-27 416161000000 10-K 0000320193-25-000079 2024 2024-09-28 391035000000 10-K 0000320193-24-000123 2023 2023-09-30 383285000000 10-K 0000320193-23-000106 2022 2022-09-24 394328000000 10-K 0000320193-22-000108 2021 2021-09-25 365817000000 10-K 0000320193-21-000105 2020 2020-09-26 274515000000 10-K 0000320193-20-000096 2019 2019-09-28 260174000000 10-K 0000320193-19-000119 2018 2018-09-29 265595000000 10-K 0000320193-19-000119 2017 2017-09-30 229234000000 10-K 0000320193-19-000119 Each row is one fiscal year, newest first. value is in USD (unit says so). form is the 10-K that reported it and accession_number identifies that filing at the SEC. The three oldest rows share a filing because Apple's fiscal 2019 10-K reports all three years.
The 13 metrics you can ask for are revenue, net_income, eps_basic, eps_diluted, operating_income, total_assets, total_liabilities, equity, cash, operating_cash_flow, capex, free_cash_flow and shares_outstanding, plus revenue_contract if you want ASC 606 contract revenue specifically. period is annual, quarterly or ttm, and limit goes up to 200.
Quarterly, and what derived means
q = nn.history("AAPL", "revenue", period="quarterly", limit=8) for v in q["data"]: flag = " (derived)" if v["derived"] else "" print(v["fiscal_period"], v["fiscal_year"], v["period_end"], v["value"], flag) Q4 2026 2026-09-26 114477000000 (derived) Q3 2026 2026-06-27 109417000000 Q2 2026 2026-03-28 111184000000 Q1 2026 2025-12-27 143756000000 Q4 2025 2025-09-27 102466000000 (derived) Q3 2025 2025-06-28 94036000000 Q2 2025 2025-03-29 95359000000 Q1 2025 2024-12-28 124300000000 Companies report Q4 only as part of the annual figure, so a Q4 value is the annual minus the nine month year to date. The API does that subtraction and marks the result derived: true, with the inputs it used in derivation, so you know which numbers are reported and which are arithmetic. It never estimates: a metric a company did not report comes back as None with a reason string.
Two flags worth reading before you compute growth
Revenue is the one metric where filers change their mind about which line is "revenue". Older filings use Revenues or SalesRevenueNet; after ASC 606 most use RevenueFromContractWithCustomerExcludingAssessedTax. The API picks one concept per company where it can, and tags each value:
for v in r["data"]: print(v["period_end"], v["concept"], v.get("revenue_standard"), v.get("concept_changed", False)) 2026-09-26 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2025-09-27 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2024-09-28 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2023-09-30 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2022-09-24 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2021-09-25 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2020-09-26 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2019-09-28 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2018-09-29 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False 2017-09-30 us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax ASC 606 False -
revenue_standardisASC 605,ASC 606, orNonewithrevenue_standard_reason. -
concept_changedturnsTruewhen the company switched concepts and the periods reported under both differ by more than 1 percent.concept_changethen holds the old concept, the new one and the difference. If you are computing growth across that boundary, you are comparing two definitions, and now you know.
A growth table with pandas
import pandas as pd df = pd.DataFrame(r["data"])[["fiscal_year", "period_end", "value", "form", "filed_date", "source_url"]] df = df.sort_values("fiscal_year") df["yoy"] = df["value"].pct_change() print(df.to_string(index=False)) fiscal_year period_end value form filed_date source_url yoy 2017 2017-09-30 229234000000 10-K 2019-10-31 https://www.sec.gov/Archives/edgar/data/320193/000032019319000119/0000320193-19-000119-index.htm NaN 2018 2018-09-29 265595000000 10-K 2019-10-31 https://www.sec.gov/Archives/edgar/data/320193/000032019319000119/0000320193-19-000119-index.htm 0.158620 2019 2019-09-28 260174000000 10-K 2019-10-31 https://www.sec.gov/Archives/edgar/data/320193/000032019319000119/0000320193-19-000119-index.htm -0.020411 2020 2020-09-26 274515000000 10-K 2020-10-30 https://www.sec.gov/Archives/edgar/data/320193/000032019320000096/0000320193-20-000096-index.htm 0.055121 2021 2021-09-25 365817000000 10-K 2021-10-29 https://www.sec.gov/Archives/edgar/data/320193/000032019321000105/0000320193-21-000105-index.htm 0.332594 2022 2022-09-24 394328000000 10-K 2022-10-28 https://www.sec.gov/Archives/edgar/data/320193/000032019322000108/0000320193-22-000108-index.htm 0.077938 2023 2023-09-30 383285000000 10-K 2023-11-03 https://www.sec.gov/Archives/edgar/data/320193/000032019323000106/0000320193-23-000106-index.htm -0.028005 2024 2024-09-28 391035000000 10-K 2024-11-01 https://www.sec.gov/Archives/edgar/data/320193/000032019324000123/0000320193-24-000123-index.htm 0.020220 2025 2025-09-27 416161000000 10-K 2025-10-31 https://www.sec.gov/Archives/edgar/data/320193/000032019325000079/0000320193-25-000079-index.htm 0.064255 2026 2026-09-26 478834000000 10-K 2026-11-03 https://www.sec.gov/Archives/edgar/data/320193/000032019326000026/0000320193-26-000026-index.htm 0.150598 Every row still carries source_url, which is the part the rest of this post is about.
Check a number against the filing
Take the newest annual row:
latest = r["data"][0] print(latest["value"], latest["form"], latest["filed_date"]) print(latest["source_url"]) 478834000000 10-K 2026-11-03 https://www.sec.gov/Archives/edgar/data/320193/000032019326000026/0000320193-26-000026-index.htm source_url opens the filing index on sec.gov. From there, open the 10-K document and find the income statement; the total net sales line for that fiscal year is the number the API returned. If it is not, the API is wrong, and I want to know: there is a daily public check at https://api.netnestdesign.com/v1/accuracy that does exactly this comparison for 25 companies against their own filings, and the gate is 25 of 25.
A number you can trace is a number you can defend.
Errors you will meet
-
RateLimitError(HTTP 429) carriesretry_afterin seconds when the server sends one. The free key is 100 calls a day. -
PaymentRequiredError(HTTP 402) only appears on paid routes with no key; the API also accepts x402 payments from agents, which is a different post. -
NetNestErrorfor the rest, withstatus,codeandmessage.
What the API does not have: foreign filers on 20-F, prices, estimates. Terms allow commercial use and display with attribution to the filing; bulk redistribution is not allowed. Docs and plans: https://api.netnestdesign.com/docs.
Data from SEC filings. Not investment advice.
