Back to Blog

SEC EDGAR XBRL API Tutorial: Get Financial Data with Python [2026]

The SEC gives every developer free access to machine-readable financial data for 10,000+ public companies. Here is how to use the XBRL CompanyFacts API with Python — no API key, no subscription, no limits on how much data you pull.

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:

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:

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