Every source for income statements, balance sheets, SEC filings, institutional ownership, and insider transactions. From the 100% free SEC EDGAR to premium 13F trackers.
Price data tells you what happened. Fundamentals tell you why. Revenue growth, profit margins, debt levels, insider buying, institutional positioning — these are the building blocks of value investing, factor models, and every earnings-based trading strategy.
The good news: unlike real-time price data, most fundamental data is publicly available for free. The SEC mandates that every public company file standardized financial reports, and all of this data is accessible through EDGAR. The paid APIs add convenience — they parse, standardize, and deliver it through clean REST endpoints.
Every public company files three core financial statements:
Income, balance sheet, cash flow. Quarterly (10-Q) and annual (10-K). The core of fundamental analysis.
10-K, 10-Q, 8-K, proxy statements, registration statements. Full text + structured XBRL data.
13F filings: what hedge funds and institutions own. Updated quarterly, 45-day delay.
Form 4 filings: CEO/CFO/director buys and sells. Strong signal — insiders buy for one reason only.
EDGAR is the SEC's filing system. Every document that every public company has ever filed with the SEC lives here — 10-Ks, 10-Qs, 8-Ks, proxy statements, insider transaction forms, 13F filings. It's the authoritative source of truth for all US public company data. The new EDGAR API provides structured JSON access to company facts, filings, and XBRL data.
In 2022, the SEC launched a modern JSON API for structured company data. The /api/xbrl/companyfacts/ endpoint provides every XBRL fact ever filed by a company — revenue, net income, EPS, assets, and hundreds more metrics, all in clean JSON.
import requests import pandas as pd # EDGAR requires a User-Agent header with your email headers = {"User-Agent": "YourApp/1.0 ([email protected])"} # Get all XBRL facts for Apple (CIK = 0000320193) url = "https://data.sec.gov/api/xbrl/companyfacts/CIK0000320193.json" data = requests.get(url, headers=headers).json() # Extract revenue (us-gaap:Revenues or RevenueFromContractWithCustomerExcludingAssessedTax) revenue_facts = data["facts"]["us-gaap"]["RevenueFromContractWithCustomerExcludingAssessedTax"] units = revenue_facts["units"]["USD"] # Filter for 10-K annual filings only annual = [u for u in units if u["form"] == "10-K"] for a in annual[-5:]: print(f" {a['end']}: ${a['val']/1e9:.1f}B") # Output: # 2021-09-25: $365.8B # 2022-09-24: $394.3B # 2023-09-30: $383.3B # 2024-09-28: $391.0B # 2025-09-27: $410.2B
# Search all filings for a keyword search_url = "https://efts.sec.gov/LATEST/search-index?q=\"artificial intelligence\"&dateRange=custom&startdt=2025-01-01&enddt=2026-03-06&forms=10-K" results = requests.get(search_url, headers=headers).json() print(f"Found {results['hits']['total']['value']} 10-K filings mentioning 'artificial intelligence'") for hit in results["hits"]["hits"][:5]: print(f" {hit['_source']['display_names'][0]} — {hit['_source']['file_date']}")
# Get recent insider filings for Apple filings_url = "https://data.sec.gov/submissions/CIK0000320193.json" company = requests.get(filings_url, headers=headers).json() # Filter for Form 4 (insider transactions) recent = company["filings"]["recent"] form4_indices = [i for i, f in enumerate(recent["form"]) if f == "4"] for i in form4_indices[:5]: print(f" {recent['filingDate'][i]} — {recent['primaryDocument'][i]}")
EDGAR uses CIK (Central Index Key) numbers, not ticker symbols. To look up a CIK: https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&company=apple&CIK=&type=&dateb=&owner=include&count=10&search_text=&action=getcompany. Or use the company tickers JSON: https://www.sec.gov/files/company_tickers.json which maps every ticker to its CIK.
FMP takes the raw SEC data and standardizes it into clean, consistent JSON. Their free tier gives you 250 API calls per day — enough to build a serious screener. They cover income statements, balance sheets, cash flows, ratios, DCF models, and more for 13,000+ stocks.
import requests FMP_KEY = "YOUR_FMP_KEY" base = "https://financialmodelingprep.com/api/v3" # Income Statement (quarterly) income = requests.get(f"{base}/income-statement/AAPL?period=quarter&limit=8&apikey={FMP_KEY}").json() for q in income[:4]: print(f" {q['date']} — Revenue: ${q['revenue']/1e9:.1f}B, Net Income: ${q['netIncome']/1e9:.1f}B") # Balance Sheet (annual) balance = requests.get(f"{base}/balance-sheet-statement/AAPL?limit=5&apikey={FMP_KEY}").json() # Key Financial Ratios ratios = requests.get(f"{base}/ratios/AAPL?limit=5&apikey={FMP_KEY}").json() for r in ratios[:3]: print(f" {r['date']} — P/E: {r['priceEarningsRatio']:.1f}, ROE: {r['returnOnEquity']:.1%}") # DCF (Discounted Cash Flow) valuation dcf = requests.get(f"{base}/discounted-cash-flow/AAPL?apikey={FMP_KEY}").json() print(f" DCF value: ${dcf[0]['dcf']:.2f} vs Market: ${dcf[0]['Stock Price']:.2f}")
SimFin takes a unique approach: they offer free bulk CSV downloads of standardized financials for 5,000+ US stocks. No API key needed — just download the files. They also have a Python library (simfin) that handles the downloading and caching automatically. The paid API tier adds real-time access and more history.
import simfin as sf # Set your API key (free) and data directory sf.set_api_key("free") sf.set_data_dir("~/simfin_data/") # Download and cache income statements (annual) df_income = sf.load_income(variant="annual", market="us") # Filter for Apple aapl = df_income.loc["AAPL"] print(aapl[["Revenue", "Net Income", "Shares (Diluted)"]].tail()) # Download balance sheets df_balance = sf.load_balance(variant="quarterly", market="us") # Download cash flow statements df_cashflow = sf.load_cashflow(variant="annual", market="us") # Calculate Free Cash Flow for all companies df_cashflow["FCF"] = df_cashflow["Net Cash from Operating Activities"] + df_cashflow["Net Cash from Investing Activities"]
Macrotrends.net isn't an API — it's a website with 15+ years of historical fundamentals for every major stock. The site is scraping-friendly (simple HTML tables) and provides data that would cost $100+/month from a commercial API: revenue, EPS, margins, P/E ratios, book value, and more. Many quantitative traders use Macrotrends as their historical fundamental data source.
Fintel is the premium source for institutional ownership data. They parse every 13F filing and provide searchable, sortable data on what hedge funds, mutual funds, and institutions own. They also track short interest, insider transactions, and unusual institutional activity. Their "Insider Score" aggregates insider buying signals.
WhaleWisdom focuses specifically on 13F tracking. Their free tier lets you view the top 50 holders of any stock and the latest portfolio of any institution. The paid tiers add historical comparisons, portfolio change alerts, and the ability to clone any fund's portfolio. Their "WhaleScore" ranks stocks by hedge fund consensus.
13F filings are due 45 days after the end of each quarter. This means that when you see a Q4 filing in mid-February, the data reflects positions as of December 31st — 45 days stale. Hedge funds know this and sometimes use "13F window dressing" to manipulate their reported positions. Always treat 13F data as directional (trend of positions), not as a real-time snapshot.
QuiverQuant is an incredible free resource that tracks alternative data no one else aggregates: Congressional stock trades (STOCK Act disclosures), lobbying expenditures, government contracts, patent filings, and Wikipedia page views. The "Congress Trading" dataset alone has generated multiple viral research papers showing that members of Congress consistently outperform the S&P 500.
import requests # QuiverQuant — Congressional Trading headers = {"Authorization": f"Bearer {QUIVER_KEY}"} # Recent congressional trades congress = requests.get("https://api.quiverquant.com/beta/historical/congresstrading/AAPL", headers=headers).json() for t in congress[:5]: print(f" {t['TransactionDate']} — {t['Representative']} ({t['Party']}) — {t['Transaction']} ${t['Range']}") # Government contracts contracts = requests.get("https://api.quiverquant.com/beta/historical/govcontracts/MSFT", headers=headers).json() # Patent filings patents = requests.get("https://api.quiverquant.com/beta/historical/patents/GOOGL", headers=headers).json()
The SEC's newest API provides structured company data in clean JSON format. The /submissions/ endpoint gives you filing history, and the /companyfacts/ endpoint provides standardized XBRL data. Unlike the old EDGAR full-text search, this API returns machine-readable data ready for analysis.
import requests headers = {"User-Agent": "YourApp/1.0 ([email protected])"} # Company search by ticker -> CIK mapping tickers = requests.get("https://www.sec.gov/files/company_tickers.json", headers=headers).json() # Find CIK for any ticker ticker_map = {v["ticker"]: v["cik_str"] for v in tickers.values()} aapl_cik = ticker_map["AAPL"] # 320193 # Get all submissions/filings submissions = requests.get(f"https://data.sec.gov/submissions/CIK{str(aapl_cik).zfill(10)}.json", headers=headers).json() print(f"Company: {submissions['name']}") print(f"SIC: {submissions['sic']} — {submissions['sicDescription']}") # Recent filings recent = submissions["filings"]["recent"] for i in range(5): print(f" {recent['filingDate'][i]} — {recent['form'][i]} — {recent['primaryDocument'][i]}")
| Source | Price | Financials | 13F / Ownership | Insiders | Alt Data | API Quality |
|---|---|---|---|---|---|---|
| SEC EDGAR | Free | Raw XBRL | Yes (raw) | Yes (Form 4) | No | Good (new API) |
| Financial Modeling Prep | Free / $15+ | Standardized | Limited | Limited | No | Good |
| SimFin | Free / $10 | Standardized | No | No | No | Excellent (Python) |
| Fintel | $40+ | No | Best-in-class | Yes + Score | Short Interest | Good |
| WhaleWisdom | Free / $15+ | No | Excellent | Limited | WhaleScore | Web-only free |
| QuiverQuant | Free | No | Limited | No | Congress, Patents | Good |
| Macrotrends | Free (web) | 15+ years | No | No | No | Scraping only |
| Use Case | Best Choice | Why |
|---|---|---|
| Building a stock screener | FMP (free tier) | 250 req/day, standardized financials + ratios + DCF |
| Backtesting value strategies | SimFin (free bulk) | Download all financials as CSV, consistent schema |
| Tracking smart money | Fintel ($40/mo) | Best 13F database + insider scores + short interest |
| Following Congress trades | QuiverQuant (free) | Only source that aggregates STOCK Act disclosures |
| Full-text filing search | SEC EDGAR (free) | EFTS search across all filings, mentions of keywords |
| Quick fundamental lookup | Macrotrends (free) | No API key, 15+ years history, simple HTML tables |
The smartest approach is to download fundamental data once and store it locally. Here's how to build a SQLite-based financial database using free sources:
import sqlite3 import requests import json from datetime import datetime class FinancialDB: """Local SQLite database for fundamental data.""" def __init__(self, db_path="financials.db"): self.conn = sqlite3.connect(db_path) self._create_tables() def _create_tables(self): self.conn.executescript(""" CREATE TABLE IF NOT EXISTS income_statements ( ticker TEXT, date TEXT, period TEXT, revenue REAL, gross_profit REAL, operating_income REAL, net_income REAL, eps REAL, shares_outstanding REAL, PRIMARY KEY (ticker, date, period) ); CREATE TABLE IF NOT EXISTS balance_sheets ( ticker TEXT, date TEXT, total_assets REAL, total_liabilities REAL, total_equity REAL, cash REAL, total_debt REAL, PRIMARY KEY (ticker, date) ); CREATE TABLE IF NOT EXISTS insider_trades ( ticker TEXT, date TEXT, insider_name TEXT, transaction_type TEXT, shares REAL, price REAL, value REAL ); """) def load_from_fmp(self, ticker, api_key): """Load financials from Financial Modeling Prep.""" base = "https://financialmodelingprep.com/api/v3" # Income statements income = requests.get( f"{base}/income-statement/{ticker}?limit=20&apikey={api_key}" ).json() for q in income: self.conn.execute( "INSERT OR REPLACE INTO income_statements VALUES (?,?,?,?,?,?,?,?,?)", (ticker, q["date"], q["period"], q["revenue"], q["grossProfit"], q["operatingIncome"], q["netIncome"], q["eps"], q["weightedAverageShsOut"]) ) self.conn.commit() def load_from_edgar(self, ticker, cik): """Load from SEC EDGAR Company Facts (free, no key).""" headers = {"User-Agent": "FinDB/1.0 ([email protected])"} url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{str(cik).zfill(10)}.json" data = requests.get(url, headers=headers).json() # Extract revenue for key in ["Revenues", "RevenueFromContractWithCustomerExcludingAssessedTax"]: if key in data["facts"].get("us-gaap", {}): units = data["facts"]["us-gaap"][key]["units"]["USD"] for u in units: if u["form"] in ("10-K", "10-Q"): self.conn.execute( "INSERT OR REPLACE INTO income_statements (ticker, date, period, revenue) VALUES (?,?,?,?)", (ticker, u["end"], "FY" if u["form"] == "10-K" else "Q", u["val"]) ) break self.conn.commit() def query(self, sql): return self.conn.execute(sql).fetchall() # Usage db = FinancialDB() db.load_from_fmp("AAPL", "YOUR_FMP_KEY") db.load_from_fmp("MSFT", "YOUR_FMP_KEY") # Compare revenue growth results = db.query(""" SELECT ticker, date, revenue/1e9 as revenue_bn FROM income_statements WHERE period = 'FY' AND ticker IN ('AAPL', 'MSFT') ORDER BY ticker, date DESC LIMIT 10 """) for r in results: print(f" {r[0]} — {r[1]} — ${r[2]:.1f}B")
This is the biggest pain point with EDGAR data. Revenue might be filed under "Revenues", "RevenueFromContractWithCustomerExcludingAssessedTax", or "SalesRevenueNet" depending on the company and the year. The solution is to maintain a mapping table of equivalent XBRL tags. SimFin and FMP handle this for you — they've already built the mapping. If you're using EDGAR directly, start with the most common tags and add fallbacks.
Most free sources are US-only. For international fundamentals: FMP covers 13,000+ stocks globally including European and Asian markets. EODHD ($20/mo) provides fundamentals for 60+ exchanges. For EU specifically, the European Single Electronic Format (ESEF) is the equivalent of EDGAR XBRL, but it's much harder to work with.