SEC EDGAR API Guide

2026-02-19

SEC EDGAR API Guide: XBRL CompanyFacts, Rate Limits & Python Examples

The SEC's EDGAR system exposes a set of free, public APIs for programmatic access to financial data. The most powerful of these is the XBRL CompanyFacts endpoint, which returns every structured fact a company has ever filed — revenues, total assets, shares outstanding, and hundreds more — in a single JSON response.

This guide covers the EDGAR XBRL API endpoints, the 10 requests-per-second rate limit, the mandatory User-Agent requirement, and practical Python examples for fetching and parsing CompanyFacts data. Whether you're building a financial model, a data pipeline, or training an LLM on SEC data, these are the constraints and patterns you need to know. Platforms like DealCharts build on these same EDGAR data sources to provide structured, citable charts for structured finance deals.

EDGAR API Quick Reference

Before diving into details, here's a quick-reference table for the primary EDGAR data endpoints and access rules:

ResourceURL PatternReturns
CompanyFactsdata.sec.gov/api/xbrl/companyfacts/CIK{cik}.jsonAll XBRL facts for a company
CompanyConceptdata.sec.gov/api/xbrl/companyconcept/CIK{cik}/{taxonomy}/{tag}.jsonSingle concept time series
Submissionsdata.sec.gov/submissions/CIK{cik}.jsonFiling history and metadata
Company Tickerssec.gov/files/company_tickers.jsonTicker-to-CIK lookup table
Rate Limit10 requests/second per IP
User-AgentRequired: CompanyName admin@email.com

All endpoints are free and require no API key. The only access requirements are a compliant User-Agent header and staying within the rate limit.

CompanyFacts Deep Dive

The CompanyFacts endpoint is the single most useful EDGAR API for bulk financial data. A single request returns every XBRL-tagged fact across all filings for a given CIK.

Example: Apple Inc. (CIK 0000320193)

The URL for Apple's CompanyFacts is:

https://data.sec.gov/api/xbrl/companyfacts/CIK0000320193.json

The response is a JSON object with this top-level structure:

{
"cik": 320193,
"entityName": "Apple Inc.",
"facts": {
"dei": { ... },
"us-gaap": {
"Revenues": {
"label": "Revenues",
"description": "Amount of revenue recognized...",
"units": {
"USD": [
{
"end": "2023-09-30",
"val": 383285000000,
"accn": "0000320193-23-000106",
"fy": 2023,
"fp": "FY",
"form": "10-K",
"filed": "2023-11-03"
}
]
}
},
"Assets": { ... },
"StockholdersEquity": { ... }
}
}
}

Key things to notice:

  • facts contains two namespaces: dei (document and entity info) and us-gaap (financial data)
  • Each concept (e.g., Revenues) has a units object — usually USD for monetary values or shares for share counts
  • Each data point includes the filing accession number (accn), fiscal year (fy), period (fp), and the form type
  • Values (val) are in raw units — Apple's FY2023 revenue is 383,285,000,000 (not thousands or millions)

CompanyConcept: Fetching a Single Metric

If you only need one concept (e.g., revenue over time), the CompanyConcept endpoint is more efficient:

https://data.sec.gov/api/xbrl/companyconcept/CIK0000320193/us-gaap/Revenues.json

This returns the same data structure as the Revenues section of CompanyFacts, without the overhead of every other concept.

Example: Amazon.com (CIK 0001018724)

Amazon is one of the most-searched CIKs on EDGAR. The CompanyFacts URL is:

https://data.sec.gov/api/xbrl/companyfacts/CIK0001018724.json

The response follows the same structure as Apple above. To pull Amazon's annual revenue:

amazon = get_company_facts("1018724")
revenues = extract_annual_values(amazon, "Revenues")
for r in revenues:
print(f"FY{r['fy']}: ${r['val']:,.0f}")

Note the CIK zero-padding:

1018724
becomes
CIK0001018724
in the URL. The
get_company_facts
function below handles this automatically. You can also fetch a single concept directly at
data.sec.gov/api/xbrl/companyconcept/CIK0001018724/us-gaap/Revenues.json
.

Python Examples

Setting Up Compliant Headers

Every request to EDGAR must include a User-Agent header identifying your application and providing a contact email. Requests without this header receive a 403 Forbidden response.

import requests
import time
HEADERS = {
"User-Agent": "MyCompany Analytics admin@mycompany.com"
}
SEC_BASE = "https://data.sec.gov"

Fetching CompanyFacts

def get_company_facts(cik: str) -> dict:
"""Fetch all XBRL facts for a company by CIK (zero-padded to 10 digits)."""
cik_padded = cik.zfill(10)
url = f"{SEC_BASE}/api/xbrl/companyfacts/CIK{cik_padded}.json"
resp = requests.get(url, headers=HEADERS, timeout=30)
resp.raise_for_status()
return resp.json()
# Apple's CIK
facts = get_company_facts("320193")
print(facts["entityName"]) # "Apple Inc."

Extracting a Specific Concept

Once you have the CompanyFacts response, extracting a time series for a specific concept is straightforward:

def extract_annual_values(facts: dict, concept: str, taxonomy: str = "us-gaap") -> list:
"""Extract annual (10-K) values for a concept from CompanyFacts data."""
try:
concept_data = facts["facts"][taxonomy][concept]
except KeyError:
return []
# Get USD values, fall back to first available unit
units = concept_data.get("units", {})
values = units.get("USD", units.get("shares", []))
# Filter to annual filings only
annual = [v for v in values if v.get("form") == "10-K" and v.get("fp") == "FY"]
# Sort by fiscal year
annual.sort(key=lambda x: x.get("fy", 0))
return annual
revenues = extract_annual_values(facts, "Revenues")
for r in revenues:
print(f"FY{r['fy']}: ${r['val']:,.0f}")

Rate-Limited Batch Fetching

When fetching data for multiple companies, you must stay under the 10 requests-per-second limit:

def batch_fetch_facts(ciks: list) -> dict:
"""Fetch CompanyFacts for multiple CIKs with rate limiting."""
results = {}
for cik in ciks:
try:
results[cik] = get_company_facts(cik)
print(f"Fetched {results[cik]['entityName']}")
except requests.exceptions.HTTPError as e:
print(f"Error fetching CIK {cik}: {e}")
results[cik] = None
# Stay under 10 req/s — 0.12s buffer is safer than exact 0.1s
time.sleep(0.12)
return results
# Example: fetch facts for a few companies
company_ciks = ["320193", "789019", "1018724"] # Apple, Microsoft, Amazon
all_facts = batch_fetch_facts(company_ciks)

CIK Lookup from Ticker

The SEC publishes a ticker-to-CIK mapping file that you can use to convert ticker symbols to CIK numbers:

def get_cik_from_ticker(ticker: str) -> str:
"""Look up a CIK number from a ticker symbol."""
url = "https://www.sec.gov/files/company_tickers.json"
resp = requests.get(url, headers=HEADERS, timeout=30)
resp.raise_for_status()
tickers = resp.json()
ticker_upper = ticker.upper()
for entry in tickers.values():
if entry["ticker"].upper() == ticker_upper:
return str(entry["cik_str"])
raise ValueError(f"Ticker '{ticker}' not found in SEC data")
# Example
apple_cik = get_cik_from_ticker("AAPL")
print(f"AAPL CIK: {apple_cik}") # "320193"

Note that CIK numbers must be zero-padded to 10 digits when used in API URLs. The get_company_facts function above handles this automatically with zfill(10).

For a deeper dive on mapping CUSIPs to CIKs — especially important in structured finance where a single issuer may have hundreds of securities — see our CUSIP to CIK Mapping Guide.

Common Errors and How to Fix Them

403 Forbidden — Missing or Invalid User-Agent

The most common error developers encounter. EDGAR blocks requests that don't include a User-Agent header identifying the caller.

Fix: Set the User-Agent header to a string containing your organization name and a contact email:

headers = {"User-Agent": "YourCompany analytics@yourcompany.com"}

429 Too Many Requests — Rate Limit Exceeded

You've exceeded the 10 requests-per-second limit. The SEC will temporarily block your IP.

Fix: Add a minimum delay of 0.1 seconds between requests. Use 0.12 seconds for safety. For burst-heavy workloads, implement exponential backoff:

def fetch_with_backoff(url, retries=3):
for attempt in range(retries):
resp = requests.get(url, headers=HEADERS, timeout=30)
if resp.status_code == 429:
wait = 2 ** attempt
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
continue
resp.raise_for_status()
return resp.json()
raise Exception(f"Failed after {retries} retries")

For a detailed walkthrough of building resilient EDGAR scrapers, see our guide on EDGAR Scraping Rate Limits Explained.

Connection Reset — Sustained Rate Limit Violation

If you sustain a high request rate, EDGAR may reset the TCP connection entirely rather than returning a 429. This manifests as a ConnectionResetError or requests.exceptions.ConnectionError.

Fix: Back off immediately and reduce your request rate. Wait at least 10 seconds before retrying.

Empty or Unexpected Response — CIK Zero-Padding

EDGAR is strict about CIK formatting in URLs. The CIK must be zero-padded to exactly 10 digits.

URLResult
CIK0000320193.jsonWorks (Apple)
CIK320193.jsonMay fail or return unexpected data
CIK0001018724.jsonWorks (Amazon)

Fix: Always use str(cik).zfill(10) when constructing API URLs.

Related EDGAR Resources

For an example of how EDGAR CompanyFacts data connects to real deals, explore a structured finance deal like BBCMS 2023-C21 — where filing data, tranches, and collateral are linked with provenance.


Explore DealCharts
EDGAR data, structured and linked — deals, tranches, filings, and counterparties with provenance. No scraping required.
Explore DealCharts
Charts shown here come from Dealcharts (open context with provenance).For short-horizon, explainable outcomes built on the same discipline, try CMD+RVL Signals (free).

PLATFORM

ToolsIntegrationsContributors

LEARN

OnboardingBlogAboutOntologyRoadmap

PLATFORM

ToolsIntegrationsContributors

FOR DEVELOPERS

API & Data AccessDatasets

MARKETS

Capital MarketsCMBSAuto ABSBDCsFund HoldingsAsset Backed Securities

SOLUTIONS

IssuersServicersTrusteesRatings AgenciesFundsResearchersVendors

LEARN

BlogAboutOntology

CONNECT

Contact UsX (Twitter)Substack
Powered by CMD+RVL
CMD+RVL makes decisions under uncertainty explainable, defensible, and survivable over time.
© 2026 CMD+RVL. All rights reserved.
Not investment advice. For informational purposes only.
Disclosures · Privacy · Security · License
(Built 2026-02-25)