SEC EDGAR offers free access to every public company's earnings per share data. Python's requests library pulls it directly — no paid APIs, no manual downloads. The catch: XBRL parsing requires precision. Here's how to automate it.
Key Takeaways
- SEC EDGAR provides free public access to XBRL financial data containing EPS figures from 10-Q and 10-K filings
- Python's requests library retrieves filing metadata and XBRL documents directly from EDGAR's public URLs
- XBRL tags like
us-gaap:EarningsPerShareBasicextract quarterly and annual EPS programmatically
Before You Start
You need basic Python syntax, HTTP request fundamentals, and an understanding of what EPS represents. You should know how to install packages with pip and run scripts from the command line. XBRL experience is not required — the parsing structure is covered below.
Critical requirement: EDGAR blocks generic Python requests. The SEC requires a User-Agent header with your contact information. Without it, you get HTTP 403 errors immediately.
What You Need
- Python 3.8 or newer
- requests library —
pip install requests - pandas library (optional but recommended) —
pip install pandas - A valid email address for your User-Agent header (SEC requirement)
- The CIK number (Central Index Key) for the company you want to track — available on the SEC EDGAR search page
Step 1: Configure Your User-Agent (Do This First)
The SEC tracks User-Agent headers to enforce fair use. Generic identifiers like "Python-requests/2.28.0" get blocked. Your email lets the SEC contact you if your script causes problems — which prevents automatic bans.
Create a new Python script and add this setup block with your real email:
import requests
import json
headers = {
'User-Agent': 'YourName your.email@example.com'
}
CIK = '0000789019' # Example: Microsoft CIK with leading zeros
That header is not optional. Skip it and your script fails at the first request.
Step 2: Retrieve the Company's Filing Index
EDGAR provides a JSON endpoint listing all filings for a given CIK. Each filing includes form type, filing date, and accession number — the unique identifier you need to build XBRL URLs.
url = f'https://data.sec.gov/submissions/CIK{CIK}.json'
response = requests.get(url, headers=headers)
data = response.json()
Store the recent filings array for the next step.
Step 3: Filter for 10-Q and 10-K Filings
EPS data appears in quarterly 10-Q filings and annual 10-K filings. Loop through the filing list and extract accession numbers for these form types:
filings = data['filings']['recent']
eps_filings = []
for i, form in enumerate(filings['form']):
if form in ['10-Q', '10-K']:
eps_filings.append({
'form': form,
'filingDate': filings['filingDate'][i],
'accessionNumber': filings['accessionNumber'][i].replace('-', '')
})
The accession number in the JSON includes hyphens. EDGAR XBRL URLs use the hyphen-free version. The .replace('-', '') step ensures your URLs work when you fetch the actual financial data.
Step 4: Construct the XBRL Instance Document URL
Each filing has an XBRL folder containing structured financial statements. The instance document — typically ending in _htm.xml — holds the EPS tags you need.
accession = eps_filings[0]['accessionNumber'] # Most recent filing
instance_url = f'https://www.sec.gov/Archives/edgar/data/{CIK}/{accession}/{CIK}-{accession}.xml'
That URL pattern works for most filings. Some companies use different naming conventions — if you get a 404, fetch the FilingSummary.xml file first to locate the correct instance document path.
Step 5: Parse the XBRL Document for EPS Tags
Download the XBRL instance document and search for the standardized EPS tags: us-gaap:EarningsPerShareBasic and us-gaap:EarningsPerShareDiluted.
import xml.etree.ElementTree as ET
response = requests.get(instance_url, headers=headers)
root = ET.fromstring(response.content)
namespaces = {'us-gaap': 'http://fasb.org/us-gaap/2023'}
eps_basic = root.find('.//us-gaap:EarningsPerShareBasic', namespaces)
if eps_basic is not None:
print(f"EPS (Basic): {eps_basic.text}")
XBRL uses XML namespaces. The year in the namespace URL varies by filing date. If the tag is not found, inspect the raw XML to confirm the exact namespace the company uses.
Step 6: Loop Through Multiple Filings and Build a DataFrame
Repeat the fetch-and-parse process for each filing. Store results in a pandas DataFrame:
import pandas as pd
eps_data = []
for filing in eps_filings[:8]: # Last 8 quarters
accession = filing['accessionNumber']
instance_url = f'https://www.sec.gov/Archives/edgar/data/{CIK}/{accession}/{CIK}-{accession}.xml'
response = requests.get(instance_url, headers=headers)
root = ET.fromstring(response.content)
eps_basic = root.find('.//us-gaap:EarningsPerShareBasic', namespaces)
if eps_basic is not None:
eps_data.append({
'date': filing['filingDate'],
'form': filing['form'],
'eps_basic': float(eps_basic.text)
})
df = pd.DataFrame(eps_data)
df.to_csv('company_eps_history.csv', index=False)
You now have a CSV with historical EPS and filing dates. Extend this to track multiple companies by looping through a list of CIKs, or schedule it to run weekly for new filings.
Common Problems and How to Fix Them
HTTP 403 Forbidden errors: Your User-Agent header is missing or generic. Verify it includes your name and email in plain text. The SEC rejects automated requests that do not identify the requester.
EPS tag not found: Not all companies use us-gaap:EarningsPerShareBasic exactly. Some use custom extensions. Download the raw XML from EDGAR and search for "EarningsPerShare" to locate the correct tag. The SEC EDGAR search page lets you view filings in both HTML and raw XBRL format.
Namespace errors: XBRL namespaces change with accounting standards updates. If your script worked last year but breaks now, check the namespace URL in the root element of the XBRL document and update your namespaces dictionary.
Best Practices for Production Use
- Add a 0.1-second delay between requests using
time.sleep(0.1)— EDGAR allows roughly 10 requests per second per IP - Cache the CIK submissions JSON locally — it changes infrequently and reduces redundant queries
- Log failed requests and parsing errors to a file for later diagnosis
- Use context elements in XBRL to distinguish quarterly from annual EPS when both appear in the same filing
- For production pipelines tracking hundreds of companies, consider the SEC's bulk data feeds
When This Approach Does Not Work
This method works for U.S. public companies filing XBRL-formatted financials with the SEC. It does not work for private companies, foreign companies not listed in the U.S., or filings predating XBRL adoption (roughly pre-2009 for most filers).
EDGAR provides reported historical results only. If you need real-time EPS estimates or analyst consensus figures, you need a paid financial data service. If you are building a commercial product requiring sub-second latency or access to thousands of filings per minute, SEC rate limits make this impractical — use a licensed data provider.
FAQ
How do I find a company's CIK number?
Visit the SEC EDGAR search page and search for the company name or ticker symbol. The CIK appears in the search results and in the URL of the company's filing page. CIKs are zero-padded to 10 digits — store them as strings, not integers, to preserve leading zeros.
Can I automate EPS collection for all S&P 500 companies?
Yes. With a 0.1-second delay between requests, you can process roughly 500 companies in about 10 minutes per filing period. Build a loop that iterates through a list of CIKs, fetches the most recent 10-Q or 10-K, parses EPS, and writes results to a master CSV. Run the script overnight or on a schedule if tracking hundreds of tickers.
What is the difference between basic and diluted EPS in XBRL?
XBRL filings contain separate tags for us-gaap:EarningsPerShareBasic and us-gaap:EarningsPerShareDiluted. Basic EPS divides net income by outstanding common shares. Diluted EPS assumes all convertible securities (stock options, warrants, convertible bonds) are exercised, increasing the share count and typically lowering the EPS figure. Most analysts focus on diluted EPS because it represents the worst-case ownership dilution scenario.
How do I handle companies that report EPS in different currencies?
XBRL tags include a unitRef attribute specifying currency and scale (e.g., USD, shares in millions). Parse the unitRef from the XBRL context elements and store it alongside the EPS value. If comparing companies across countries, apply exchange rates manually — EDGAR does not normalize currency.
The next practical step: extend your script to compare EPS quarter-over-quarter, flag companies with accelerating or decelerating earnings trends, and export summary statistics for portfolio screening. That turns raw EDGAR data into actionable investment research — without spending a dollar on APIs.