What is the XBRL API?
XBRL (eXtensible Business Reporting Language) is the structured tagging format the SEC has required public companies to use in their financial filings since 2009. Every time a company reports revenue, net income, total assets, or earnings per share in a 10-K or 10-Q, that number is tagged with a standardized XBRL concept name — making it machine-readable.
The SEC exposes this data through the EDGAR XBRL API at data.sec.gov. The two key endpoints are:
| Endpoint | What It Returns |
|---|---|
| CompanyFacts | All XBRL financial data ever reported by one company |
| CompanyConcept | One financial concept for one company across all filings |
Both endpoints are completely free, require no API key, and return JSON. The only requirement is a proper User-Agent header identifying your application and a contact email.
The CompanyFacts Endpoint
The CompanyFacts endpoint returns every XBRL-tagged data point a company has ever filed with the SEC in a single JSON response:
https://data.sec.gov/api/xbrl/companyfacts/CIK{padded-cik}.json
The CIK (Central Index Key) is the SEC's unique identifier for every company. It must be zero-padded to 10 digits. Apple's CIK is 320193, which becomes CIK0000320193 in the URL:
https://data.sec.gov/api/xbrl/companyfacts/CIK0000320193.json
Finding a company's CIK: Search by name or ticker at sec.gov/cgi-bin/browse-edgar, or use the bulk ticker file:
https://data.sec.gov/submissions/company_tickers.json
This file lists every company's CIK, ticker symbol, and name in one JSON object — useful for building a CIK lookup table. See our SEC CIK Number Lookup Guide for full details.
Python Code: Get Apple's Revenue History
Here is a complete, working Python script to pull Apple's annual revenue history from the CompanyFacts API. No external libraries are required beyond requests:
import requests
import json
# SEC requires a User-Agent header with your app name and contact email
HEADERS = {
"User-Agent": "MyFinanceApp [email protected]"
}
def get_company_facts(cik: int) -> dict:
"""Fetch all XBRL facts for a company by CIK."""
cik_padded = str(cik).zfill(10)
url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{cik_padded}.json"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.json()
def get_annual_revenue(facts: dict) -> list:
"""Extract annual revenue from us-gaap taxonomy."""
us_gaap = facts.get("facts", {}).get("us-gaap", {})
# Apple uses RevenueFromContractWithCustomerExcludingAssessedTax
# Older filings may use SalesRevenueNet or Revenues
revenue_concepts = [
"RevenueFromContractWithCustomerExcludingAssessedTax",
"Revenues",
"SalesRevenueNet",
"SalesRevenueGoodsNet",
]
for concept in revenue_concepts:
if concept in us_gaap:
units = us_gaap[concept].get("units", {})
usd_data = units.get("USD", [])
# Filter for annual (10-K) filings only
annual = [
entry for entry in usd_data
if entry.get("form") == "10-K"
and entry.get("fp") == "FY" # fiscal year
]
# Deduplicate by fiscal year end (keep most recent filing)
seen = {}
for entry in annual:
end_date = entry["end"]
if end_date not in seen or entry["filed"] > seen[end_date]["filed"]:
seen[end_date] = entry
# Sort by date
return sorted(seen.values(), key=lambda x: x["end"])
return []
# Apple's CIK
APPLE_CIK = 320193
print("Fetching Apple's XBRL data from SEC EDGAR...")
facts = get_company_facts(APPLE_CIK)
print(f"Company: {facts['entityName']}")
revenue_data = get_annual_revenue(facts)
print(f"\nAnnual Revenue History ({len(revenue_data)} years):")
print(f"{'Fiscal Year End':<18} {'Revenue (USD)':<20} {'Filed'}")
print("-" * 55)
for entry in revenue_data:
revenue_b = entry['val'] / 1_000_000_000
print(f"{entry['end']:<18} ${revenue_b:>14.2f}B {entry['filed']}")
Sample output:
Company: Apple Inc.
Annual Revenue History (14 years):
Fiscal Year End Revenue (USD) Filed
-------------------------------------------------------
2010-09-25 $65.23B 2010-10-27
2011-09-24 $108.25B 2011-10-26
2012-09-29 $156.51B 2012-10-31
...
2024-09-28 $391.04B 2024-11-01
Understanding the Response Structure
The CompanyFacts JSON has a consistent structure you need to understand before building anything on top of it:
{
"cik": 320193,
"entityName": "Apple Inc.",
"facts": {
"us-gaap": {
"RevenueFromContractWithCustomerExcludingAssessedTax": {
"label": "Revenue from Contract with Customer, Excluding Assessed Tax",
"description": "Amount...",
"units": {
"USD": [
{
"end": "2024-09-28",
"val": 391035000000,
"accn": "0000320193-24-000123",
"fy": 2024,
"fp": "FY",
"form": "10-K",
"filed": "2024-11-01",
"frame": "CY2024"
},
...
]
}
},
...
},
"dei": {
"EntityCommonStockSharesOutstanding": { ... }
}
}
}
Key fields in each data point:
end— The period end date (last day of the quarter or fiscal year)val— The reported value (in USD for monetary values, in shares for equity data)accn— The SEC accession number of the filing where this value appearedfp— Fiscal period:FY(annual),Q1,Q2,Q3form— Filing type:10-K,10-Q,8-Kfiled— Date the filing was submitted to the SECframe— Calendar year frame (e.g.,CY2024Q3Ifor instantaneous values)
The us-gaap taxonomy contains the standard US GAAP concepts. Some companies also report under ifrs-full (foreign private issuers) or dei (disclosure and entity information).
Python Code: Compare Revenue Across Companies
The real power of the XBRL API is comparing the same metric across multiple companies. Here is how to pull the most recent annual revenue for several tech giants and compare them:
import requests
import time
HEADERS = {"User-Agent": "MyFinanceApp [email protected]"}
COMPANIES = {
"Apple": 320193,
"Microsoft": 789019,
"Alphabet": 1652044,
"Amazon": 1018724,
"Meta": 1326801,
}
REVENUE_CONCEPTS = [
"RevenueFromContractWithCustomerExcludingAssessedTax",
"Revenues",
"SalesRevenueNet",
]
def get_latest_annual_revenue(cik: int) -> float | None:
"""Get the most recent annual revenue for a company."""
cik_padded = str(cik).zfill(10)
url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{cik_padded}.json"
try:
response = requests.get(url, headers=HEADERS, timeout=30)
response.raise_for_status()
data = response.json()
except Exception as e:
print(f" Error fetching CIK {cik}: {e}")
return None
us_gaap = data.get("facts", {}).get("us-gaap", {})
for concept in REVENUE_CONCEPTS:
if concept not in us_gaap:
continue
usd_entries = us_gaap[concept].get("units", {}).get("USD", [])
annual = [e for e in usd_entries if e.get("form") == "10-K" and e.get("fp") == "FY"]
if annual:
latest = max(annual, key=lambda x: x["filed"])
return latest["val"], latest["end"], concept
return None, None, None
print(f"{'Company':<12} {'Revenue':<18} {'Fiscal Year End':<18} {'Concept Used'}")
print("-" * 80)
for company, cik in COMPANIES.items():
val, period, concept = get_latest_annual_revenue(cik)
if val:
revenue_b = val / 1_000_000_000
print(f"{company:<12} ${revenue_b:>12.2f}B {period:<18} {concept}")
else:
print(f"{company:<12} No data found")
time.sleep(0.15) # Stay well under 10 req/sec
Common XBRL Concepts Reference
Companies use different concept names for the same metric depending on their industry and accounting policies. Here are the most important us-gaap concepts and their common variations:
| Metric | Primary Concept Name | Alternatives |
|---|---|---|
| Revenue | RevenueFromContractWithCustomerExcludingAssessedTax | Revenues, SalesRevenueNet |
| Net Income | NetIncomeLoss | ProfitLoss, NetIncome |
| Total Assets | Assets | (consistent across companies) |
| EPS (Basic) | EarningsPerShareBasic | (consistent) |
| EPS (Diluted) | EarningsPerShareDiluted | (consistent) |
| Total Liabilities | Liabilities | (consistent) |
| Cash & Equivalents | CashAndCashEquivalentsAtCarryingValue | Cash |
| Shares Outstanding | CommonStockSharesOutstanding | (consistent) |
| Operating Income | OperatingIncomeLoss | (consistent) |
| R&D Expenses | ResearchAndDevelopmentExpense | (consistent) |
Pro tip: When a concept is missing for a company, check the facts JSON manually to see what concept names they actually use. Companies are allowed to choose from approved synonyms in the us-gaap taxonomy.
Rate Limits and Best Practices
The SEC enforces the following rules for all EDGAR API access. Violating them results in a temporary IP block:
- 10 requests per second maximum across all EDGAR domains (data.sec.gov, efts.sec.gov, www.sec.gov). Target 8 req/sec in practice to stay safe.
- User-Agent header required. Format:
"CompanyName [email protected]". Requests without a proper User-Agent return 403. - No daily limit — you can make unlimited requests as long as you stay under 10/sec.
- Cache responses. The CompanyFacts JSON for a company changes only when they file a new document. Cache it for at least 24 hours.
- Use bulk data for large jobs. If you need data for 1,000+ companies, download
companyfacts.zipfromhttps://data.sec.gov/archives/edgar/daily-index/xbrl/companyfacts.zipinstead of making individual API calls. - Add
time.sleep()between requests in loops. Eventime.sleep(0.1)(100ms between requests) keeps you safely under the limit.
See our complete guide to SEC EDGAR API rate limits for full details on avoiding blocks and handling errors.
The CompanyConcept Endpoint
While CompanyFacts returns everything for one company, the CompanyConcept endpoint returns one specific metric for one company across all time periods. It is faster for targeted lookups:
https://data.sec.gov/api/xbrl/companyconcept/CIK{cik}/{taxonomy}/{concept}.json
Example — Apple's EPS history:
https://data.sec.gov/api/xbrl/companyconcept/CIK0000320193/us-gaap/EarningsPerShareBasic.json
Python example:
import requests
HEADERS = {"User-Agent": "MyFinanceApp [email protected]"}
def get_company_concept(cik: int, taxonomy: str, concept: str) -> dict:
"""Fetch a single XBRL concept for a company."""
cik_padded = str(cik).zfill(10)
url = f"https://data.sec.gov/api/xbrl/companyconcept/CIK{cik_padded}/{taxonomy}/{concept}.json"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.json()
# Get Apple's diluted EPS history
eps_data = get_company_concept(320193, "us-gaap", "EarningsPerShareDiluted")
# Annual data only
annual_eps = [
e for e in eps_data["units"]["USD/shares"]
if e.get("form") == "10-K" and e.get("fp") == "FY"
]
# Deduplicate and sort
seen = {}
for entry in annual_eps:
if entry["end"] not in seen or entry["filed"] > seen[entry["end"]]["filed"]:
seen[entry["end"]] = entry
print("Apple Diluted EPS — Annual")
for entry in sorted(seen.values(), key=lambda x: x["end"]):
print(f" {entry['end']} ${entry['val']:.2f}")
Real Example: Building a Revenue Dashboard
Here is a practical pattern for pulling and displaying a revenue dashboard for any stock ticker, combining a CIK lookup with the CompanyFacts API:
import requests
import time
HEADERS = {"User-Agent": "MyFinanceApp [email protected]"}
def ticker_to_cik(ticker: str) -> int | None:
"""Look up a CIK from a ticker symbol using the SEC bulk ticker file."""
url = "https://data.sec.gov/submissions/company_tickers.json"
response = requests.get(url, headers=HEADERS)
tickers = response.json()
ticker_upper = ticker.upper()
for cik_str, info in tickers.items():
if info.get("ticker", "").upper() == ticker_upper:
return int(info["cik_str"])
return None
def revenue_dashboard(ticker: str) -> None:
"""Print a revenue and net income dashboard for any ticker."""
print(f"Looking up CIK for {ticker}...")
cik = ticker_to_cik(ticker)
if not cik:
print(f"Ticker {ticker} not found.")
return
print(f"Fetching SEC XBRL data for CIK {cik}...")
cik_padded = str(cik).zfill(10)
url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{cik_padded}.json"
data = requests.get(url, headers=HEADERS).json()
us_gaap = data["facts"].get("us-gaap", {})
entity = data["entityName"]
def get_annual(concept_list, unit="USD"):
for concept in concept_list:
if concept not in us_gaap:
continue
entries = us_gaap[concept]["units"].get(unit, [])
annual = [e for e in entries if e.get("form") == "10-K" and e.get("fp") == "FY"]
if annual:
seen = {}
for e in annual:
if e["end"] not in seen or e["filed"] > seen[e["end"]]["filed"]:
seen[e["end"]] = e
return sorted(seen.values(), key=lambda x: x["end"])[-5:] # last 5 years
return []
revenue = get_annual(["RevenueFromContractWithCustomerExcludingAssessedTax", "Revenues", "SalesRevenueNet"])
net_income = get_annual(["NetIncomeLoss"])
print(f"\n{'='*60}")
print(f" {entity} ({ticker.upper()}) — SEC Financial Dashboard")
print(f"{'='*60}")
if revenue:
print("\nAnnual Revenue (last 5 FY):")
for e in revenue:
b = e['val'] / 1e9
print(f" {e['end']} ${b:.2f}B")
if net_income:
print("\nNet Income (last 5 FY):")
for e in net_income:
b = e['val'] / 1e9
sign = "+" if b >= 0 else ""
print(f" {e['end']} {sign}${b:.2f}B")
# Example usage
revenue_dashboard("MSFT")
time.sleep(0.15)
revenue_dashboard("NVDA")
This pattern is the foundation for building stock screeners, financial comparison tools, and earnings trackers. Once you have revenue and net income, add EPS, assets, and operating income using the same pattern.
For a more advanced screener that filters across thousands of companies by financial metrics, see our guide: Build a Free Stock Screener with the SEC EDGAR API.
Explore TL;DR Filing Stock Pages
The SEC XBRL API gives you the raw data — but parsing, normalizing, and displaying it for 10,000+ companies is a significant engineering effort. TL;DR Filing does this work for you: every public company page shows revenue trends, EPS history, and AI-generated summaries of their latest 10-K and 10-Q filings.
Use the XBRL API for your own custom analysis and automation, and use TL;DR Filing when you need fast, human-readable insights from SEC filings without writing a line of code.
FAQ
What is the SEC EDGAR XBRL API?
The XBRL API at data.sec.gov provides machine-readable financial data from SEC filings. The CompanyFacts endpoint returns every financial data point a company has ever filed — free, no API key required.
How do I find a company's CIK?
Search at sec.gov/cgi-bin/browse-edgar, or use the bulk file at data.sec.gov/submissions/company_tickers.json which lists all CIKs, tickers, and company names in one JSON object.
Why do different companies use different concept names for revenue?
The us-gaap taxonomy allows companies to choose from approved synonyms based on their industry and revenue recognition method. Always check 2-3 revenue concept names and use the first one that returns data for that company.
What are the SEC EDGAR API rate limits?
Maximum 10 requests per second per IP. A User-Agent header with your company name and email is required. There are no daily limits, but exceeding 10 req/sec results in a ~10-minute IP block.
Can I get quarterly data from the XBRL API?
Yes. Filter the units array for "form": "10-Q" and "fp": "Q1", "Q2", or "Q3". Note that 10-Q values are typically year-to-date, not individual quarters — you may need to subtract consecutive periods to get true quarterly figures.
Related Guides
- Free SEC EDGAR API Guide — Complete overview of all 7 EDGAR API endpoints
- SEC EDGAR API Guide 2026 — Authentication, endpoints, and code examples
- CompanyFacts API Documentation — Full parameter reference
- Build a Stock Screener with SEC EDGAR — Filter companies by financial metrics
- Rate Limits & Best Practices — Avoid getting blocked
- Download 10-K Filings Programmatically — Get full filing text with Python