Free tools Windows power users keep installed
One-click scans. No signup required.
For U.S. public companies, the most reliable starting point is the SEC’s machine-readable EDGAR data—not a script that copies numbers from a rendered web page. Use Python’s requests library to fetch the issuer’s submissions and Company Facts JSON, then use pandas to filter and reshape the XBRL facts. Keep each value’s filing, period, unit, and source context so you can explain which number you selected and trace it back to the filing.
Company Facts is useful for broad historical analysis; when you need the exact presentation, dimensional detail, or a company-specific extension, examine the individual filing’s XBRL data instead. The two approaches answer related but different questions.
What you can retrieve from EDGAR
The SEC’s free disclosure APIs expose submission history and XBRL data drawn from financial statements. Covered filings include annual and quarterly reports and certain other forms, including Forms 8-K, 20-F, 40-F, and 6-K. An issuer’s Company Facts JSON aggregates reported facts; its submissions JSON provides filing metadata such as form, filing date, and accession number.
For many companies, facts corresponding to familiar statement items—revenue, assets, liabilities, equity, and cash flows—can be selected from the aggregated data and turned into a pandas table. This is not the same as downloading a perfectly formatted income statement: the JSON contains facts reported in multiple contexts, units, and periods. Your code must decide which fact belongs in each row.
#1 Best Overall
The SEC also makes bulk data available as ZIP files updated nightly. That can suit large historical loads better than fetching many issuers individually; the JSON APIs are convenient for targeted and incremental requests.
Choose Company Facts or a filing-level parse
| Approach | Best for | Important limitation |
|---|---|---|
| Company Facts JSON | Broad historical trends across standard concepts, such as revenue or assets. | Aggregated facts can contain multiple forms, periods, units, and contexts. Company-specific extension concepts may not align with standard concepts. |
| Filing-level XBRL or financials parse | Reproducing one report’s presentation, examining dimensions, or finding company-specific extension tags. | It is a parse of an individual filing rather than an aggregated many-year history. |
| SEC Financial Statement and Notes Data Sets | Bulk or research-oriented analysis of downloaded quarterly datasets with pandas. | Requires working with the dataset’s files and structure rather than treating it as a single issuer’s ready-made statement. |
EdgarTools describes a similar distinction: Company Facts for multi-year history, and a filing-level Financials interface for a single filing’s latest-period snapshot. Choose based on whether you need a standardized historical series or filing-specific detail.
Set up Python and identify the issuer
Install the packages used in this example:
python -m pip install requests pandas
Resolve the company’s ticker to its SEC Central Index Key (CIK), a permanent filer identifier. The SEC maintains a ticker-to-CIK JSON mapping; obtain the issuer’s zero-padded, 10-digit CIK from that mapping, or supply one you have verified. Do not assume a ticker is itself a CIK, or that a ticker uniquely identifies the legal entity you intend to analyze.
The example below uses Apple’s CIK as an example identifier. Replace it with the issuer you have verified. Set a descriptive User-Agent that identifies your application and provides a contact email; the SEC’s documented Python client example also supplies a name and email. Requests should be paced responsibly, cached where practical, and checked for HTTP errors.
Rank #2
Fetch submissions and Company Facts into pandas
This runnable script fetches the issuer’s submissions metadata and facts, selects USD facts for a few standard concepts, and retains filing provenance. It deliberately does not pretend that a quick filter produces a definitive statement: the selection function below deduplicates only identical concept/period/form/accession combinations and leaves different filings visible for review.
import requests
import pandas as pd
CIK = "0000320193" # Example: replace with a verified 10-digit CIK
USER_AGENT = "FinancialFactsExample [email protected]"
BASE = "https://data.sec.gov"
HEADERS = {"User-Agent": USER_AGENT, "Accept-Encoding": "gzip, deflate"}
session = requests.Session()
session.headers.update(HEADERS)
def get_json(url):
response = session.get(url, timeout=30)
response.raise_for_status()
return response.json()
submissions = get_json(f"{BASE}/submissions/CIK{CIK}.json")
facts = get_json(f"{BASE}/api/xbrl/companyfacts/CIK{CIK}.json")
recent = pd.DataFrame(submissions["filings"]["recent"])
filings = recent[recent["form"].isin(["10-K", "10-Q"])][
["form", "filingDate", "reportDate", "accessionNumber", "primaryDocument"]
].copy()
print("Recent 10-K/10-Q filings:")
print(filings.head(10).to_string(index=False))
wanted = {
"Revenues": "revenue",
"Assets": "assets",
"Liabilities": "liabilities",
"StockholdersEquity": "equity",
"NetCashProvidedByUsedInOperatingActivities": "operating_cash_flow",
}
rows = []
us_gaap = facts.get("facts", {}).get("us-gaap", {})
for concept, label in wanted.items():
item = us_gaap.get(concept)
if not item:
continue
for unit, observations in item.get("units", {}).items():
for observation in observations:
if observation.get("form") not in {"10-K", "10-Q"}:
continue
# This example keeps monetary values denominated in USD.
if unit != "USD":
continue
rows.append({
"metric": label,
"concept": concept,
"value": observation.get("val"),
"unit": unit,
"form": observation.get("form"),
"fy": observation.get("fy"),
"fp": observation.get("fp"),
"start": observation.get("start"),
"end": observation.get("end"),
"filed": observation.get("filed"),
"accn": observation.get("accn"),
"frame": observation.get("frame"),
"cik": CIK,
})
df = pd.DataFrame(rows)
if not df.empty:
# Keep the filing date and accession: different filings for the same
# period may reflect amendments or later restatements.
df = df.drop_duplicates(
subset=["concept", "form", "start", "end", "unit", "accn"]
).sort_values(["metric", "end", "filed"])
print("nSelected facts (review periods and accessions before analysis):")
print(df.to_string(index=False))
else:
print("No matching USD facts were found for these concepts and forms.")
The SEC JSON field names and available concepts vary by fact. The example checks that a concept exists and records its unit and period fields, but it does not infer a missing revenue concept or translate company-specific tags automatically. For a reproducible dataset, save the original JSON response or its retrieval metadata alongside the transformed table.
Turn raw facts into statement rows carefully
Separate annual and quarterly observations
Company Facts can include annual and quarterly facts in the same arrays. Filter using the form and fiscal period, and inspect start, end, fy, fp, and, where available, frame. A fact with a duration from the beginning to the end of a fiscal year is not interchangeable with a three-month quarter. Balance-sheet values are often point-in-time facts with an end date and no start; income and cash-flow values typically cover a duration. Never sum annual and quarterly values together as though they were the same type.
Respect units and scale
The example retains only facts whose unit is USD. Other concepts may be reported in shares, per-share units, or other units; preserve the unit and do not combine unlike values. XBRL values may also involve signs and presentation scaling. Use the reported numeric value and unit as the starting point, then verify how the filing presents the line item before applying any scale or sign transformation.
Recommended Free Tools
Keep accession numbers and filing dates
More than one fact can exist for what looks like the same period, including values associated with amended filings or restatements. Keep the accession number and filing date, and make the rule for choosing among filings explicit—such as selecting the latest eligible filing as of a defined date. Dropping duplicates on period alone can silently discard meaningful differences.
Check extensions and dimensions
A company may report a company-specific extension concept that does not map cleanly to the standard US-GAAP concept you expected. A filing can also include dimensional or contextual detail that is not apparent in a simple aggregated row. When an expected number is missing, does not reconcile, or is presented with segment detail, inspect the filing-level XBRL and the relevant statement rather than forcing a nearby concept into the series.
Validate against the report before using the numbers
Before analysis or publication, compare representative rows with the corresponding statement headings in the original filing. Check that the value matches the intended metric, period, unit, sign, and reporting entity. Store provenance columns with every observation: at minimum CIK, form, filing date, period dates, unit, accession number, and a source reference to the filing or response used.
Rendered HTML tables can be useful when the disclosure you need is not available as structured facts, but parsing them is more fragile: layouts can change, labels may wrap or repeat, and table positions are not stable identifiers. Prefer structured XBRL for core statement values, and use HTML extraction only for a specific disclosure that structured data does not provide.
Use filing-level data when Company Facts is not enough
For a single 10-K or 10-Q, use the submissions response to identify its accession number and primary document, then retrieve the filing’s inline XBRL or structured filing data. Remove dashes from the accession number when constructing an EDGAR archive path, and remove the leading zeros from the CIK in the archive directory; inspect the filing rather than assuming every document uses the same layout. Preserve contexts and dimensions during parsing if segment, geography, or other dimension-specific values matter.
The SEC DERA Python examples demonstrate using pandas to read and analyze the Financial Statement and Notes Data Sets, including quarterly ZIP downloads and notebook examples. Their documented environment includes Python 3.x, Jupyter, pandas, numpy, matplotlib, seaborn, IPython, and requests. This route is useful when the task is dataset-scale analysis rather than retrieving a single issuer’s JSON.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Handle failures, reliability, and cost
- HTTP error: call
raise_for_status()or handle status codes explicitly. Check the URL, CIK, network response, and request headers; do not treat an error body as valid JSON data. - Missing concept: the issuer may use another standard tag or an extension. Inspect the filing’s statement and XBRL concepts instead of substituting a guessed tag.
- Multiple rows for a period: compare form, start/end dates, unit, frame, filing date, and accession. They can represent different periods or filings, not duplicates.
- Unexpected quarterly totals: verify that a duration covers the intended quarter and that annual observations have not entered the same aggregation.
- Values do not match the rendered statement: review context, dimensions, units, signs, scale, and amended filings against the filing itself.
- Slow or repeated pulls: cache responses, fetch only issuers and filings needed for the analysis, and use the SEC bulk ZIP data for broad historical work. Include timeouts and avoid uncontrolled request loops.
The SEC disclosure APIs are free. Your practical costs are compute, storage, and engineering time for parsing, validation, and updates. Cache by issuer and response type; for an incremental workflow, use submissions metadata to detect new filings rather than rebuilding every issuer’s history on each run. The SEC says the bulk ZIP is updated nightly, so account for that cadence when freshness matters.
Or skip the browser setup
For extracting statement data, use EDGAR’s structured data above. If you also need a clean screenshot or PDF of a filing page for review, documentation, or an AI workflow, ScreenshotNeo can capture a URL with one GET request. It removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and the free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See the ScreenshotNeo API documentation.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.sec.gov/Archives/edgar/data/320193/000032019324000123/aapl-20240928.htm -o filing.webp
For the exact capture URL and available output options, see the API docs. Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month with no card.
Best Value
Frequently asked questions
Can I get every company’s full statement from one standard Company Facts concept?
No. Standard concepts cover many common facts, but issuers can use extensions and filing contexts that require examining the individual report.
Does the data arrive as a pandas DataFrame?
No. The API returns JSON. You select and reshape its nested facts into a DataFrame, preserving the distinctions needed for your analysis.
Can I use this workflow for non-U.S. issuers?
The SEC data includes some forms filed by foreign issuers, including 20-F, 40-F, and 6-K. Coverage and reporting concepts differ by issuer and form, so verify the particular filing and facts you need.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

