Omar EldeebYes, EDGAR has a free API with no key: map a ticker to a CIK with company_tickers.json, then list every filing from data.sec.gov/submissions. Tested Python.
Yes, EDGAR has an API, and it's free with no API key. Map a ticker to a CIK with https://www.sec.gov/files/company_tickers.json, then fetch https://data.sec.gov/submissions/CIK##########.json, with the CIK zero-padded to 10 digits, to get the company's details and full filing history. The one requirement is a User-Agent header with your name and email.
This page covers the two building blocks every EDGAR project starts with: ticker-to-CIK and the submissions API. The code was run on 2026-10-14.
| What | URL | Key? |
|---|---|---|
| Ticker → CIK map | https://www.sec.gov/files/company_tickers.json |
No |
| Ticker → CIK + exchange | https://www.sec.gov/files/company_tickers_exchange.json |
No |
| Filing history + metadata | https://data.sec.gov/submissions/CIK##########.json |
No |
| XBRL financials |
https://data.sec.gov/api/xbrl/companyfacts/CIK##########.json (and companyconcept, frames) |
No |
| Full-text search | https://efts.sec.gov/LATEST/search-index?q=... |
No |
| Filing documents | https://www.sec.gov/Archives/edgar/data/{cik}/{accession}/... |
No |
The SEC's EDGAR APIs page says data.sec.gov's APIs "do not require any authentication or API keys to access." What is required is a declared User-Agent and a rate of no more than 10 requests per second; see the SEC rate limit and User-Agent guide. A search for "SEC API key" often leads to filer API tokens. Those exist, but they are for submitting filings to EDGAR, not for reading data.
EDGAR identifies every filer by a Central Index Key (CIK). The SEC says CIKs "remain unique to the filer; they are not recycled." company_tickers.json maps tickers to CIKs:
curl -s -A "Data Tooly research contact@example.com" "https://www.sec.gov/files/company_tickers.json"
{"0":{"cik_str":1045810,"ticker":"NVDA","title":"NVIDIA CORP"},"1":{"cik_str":320193,"ticker":"AAPL","title":"Apple Inc."},"2":{"cik_str":1652044,"ticker":"GOOGL","title":"Alphabet Inc."}, ...}
What to know about the file (checked on 2026-10-14):
"0", "1", ...) are just row numbers. Build your own index by ticker.cik_str is an integer despite its name. Pad it yourself: f"CIK{cik:010d}".BRK-B, not BRK.B. Normalize user input.company_tickers_exchange.json has the same mapping in a columnar layout with the exchange added: {"fields": ["cik", "name", "ticker", "exchange"], "data": [[1045810, "NVIDIA CORP", "NVDA", "Nasdaq"], ...]}.
curl -s -A "Data Tooly research contact@example.com" "https://data.sec.gov/submissions/CIK0000320193.json"
The top-level fields for Apple: cik, entityType, sic, sicDescription, ownerOrg, insiderTransactionForOwnerExists, insiderTransactionForIssuerExists, name, tickers, exchanges, ein, lei, description, website, investorWebsite, category, fiscalYearEnd, stateOfIncorporation, stateOfIncorporationDescription, addresses, phone, flags, formerNames, filings.
filings has two parts:
filings.recent: per the SEC, "at least one year's of filing or to 1,000 (whichever is more) of the most recent filings in a compact columnar data array". Apple's had 1,001 rows.filings.files: extra JSON files for older filings, each with name, filingCount, filingFrom and filingTo. Apple had one, CIK0000320193-submissions-001.json, with 1,264 filings from 1994-01-26 to 2015-09-15.recent is column-oriented: one array per field (accessionNumber, filingDate, reportDate, acceptanceDateTime, act, form, fileNumber, filmNumber, items, core_type, size, isXBRL, isInlineXBRL, isXBRLNumeric, primaryDocument, primaryDocDescription). Row i is the ith element of every array.
The SEC says the submissions API updates in real time "with a typical processing delay of less than a second".
import time
import requests
HEADERS = {"User-Agent": "Data Tooly research contact@example.com"} # use your own name + email
def get(url):
r = requests.get(url, headers=HEADERS, timeout=60)
r.raise_for_status()
time.sleep(0.15) # stay under 10 requests/second
return r.json()
# 1. Ticker -> CIK, from the SEC's own mapping file.
tickers = get("https://www.sec.gov/files/company_tickers.json")
by_ticker = {row["ticker"]: row for row in tickers.values()}
print(len(by_ticker), "tickers;", by_ticker["AAPL"], by_ticker["BRK-B"])
def cik_for(ticker):
return by_ticker[ticker.upper()]["cik_str"]
# 2. Submissions: company metadata plus filing history.
cik = cik_for("aapl")
sub = get(f"https://data.sec.gov/submissions/CIK{cik:010d}.json")
print(sub["name"], sub["tickers"], sub["exchanges"], "| SIC", sub["sic"], sub["sicDescription"],
"| FY ends", sub["fiscalYearEnd"])
# `recent` is column-oriented: one list per field. Zip it into rows.
recent = sub["filings"]["recent"]
rows = [dict(zip(recent, values)) for values in zip(*recent.values())]
print(len(rows), "recent filings; older pages:", [f["name"] for f in sub["filings"]["files"]])
def filing_urls(cik, row):
folder = f"https://www.sec.gov/Archives/edgar/data/{cik}/{row['accessionNumber'].replace('-', '')}"
return f"{folder}/{row['primaryDocument']}", f"{folder}/{row['accessionNumber']}-index.htm"
for row in [r for r in rows if r["form"] in ("10-K", "10-Q")][:3]:
doc, index = filing_urls(cik, row)
print(row["form"], row["filingDate"], "period", row["reportDate"], doc)
# 3. Older filings live in extra files listed under filings.files.
older = get(f"https://data.sec.gov/submissions/{sub['filings']['files'][0]['name']}")
print("oldest filing on record:", min(older["filingDate"]), "| rows in older file:", len(older["accessionNumber"]))
Output on 2026-10-14:
10447 tickers; {'cik_str': 320193, 'ticker': 'AAPL', 'title': 'Apple Inc.'} {'cik_str': 1067983, 'ticker': 'BRK-B', 'title': 'BERKSHIRE HATHAWAY INC'}
Apple Inc. ['AAPL'] ['Nasdaq'] | SIC 3571 Electronic Computers | FY ends 0926
1001 recent filings; older pages: ['CIK0000320193-submissions-001.json']
10-Q 2026-07-31 period 2026-06-27 https://www.sec.gov/Archives/edgar/data/320193/000032019326000020/aapl-20260627.htm
10-Q 2026-05-01 period 2026-03-28 https://www.sec.gov/Archives/edgar/data/320193/000032019326000013/aapl-20260328.htm
10-Q 2026-01-30 period 2025-12-27 https://www.sec.gov/Archives/edgar/data/320193/000032019326000006/aapl-20251227.htm
The older file is a bare column dictionary, with no filings.recent wrapper, which is why the last step reads older["filingDate"] directly.
submissions/CIK320193.json returned HTTP 404 with an XML body. Archive paths (/Archives/edgar/data/320193/...) take the unpadded number.cik comes back as a string here. The submissions JSON has "cik": "0000320193" (padded string), while company_tickers.json has an integer. Normalize with int() before you compare.0000320193-25-000079. Folder paths drop the dashes: 000032019325000079.fiscalYearEnd is MMDD. Apple's 0926 means its fiscal year ends around September 26.filings.files.https://www.sec.gov/Archives/edgar/daily-index/bulkdata/submissions.zip.The free SEC EDGAR query builder takes plain tickers, so you don't have to look up or pad CIKs, plus a mode (filings, financials, full-text search or insider trades) and form types. It shows the output shape. It builds the query rather than fetching live, because browsers can't send the User-Agent the SEC requires.
The SEC EDGAR Scraper actor on Apify resolves tickers to CIKs and pulls filings, financials, Form 4 insider trades, 13F holdings, 13D/G stakes and parsed 8-K items, with the User-Agent and pacing handled. It exports JSON, CSV or Excel. It's free to start, then pay-as-you-go.
Disclosure: I build the datatooly query builder and the Apify actor. The endpoints above work without either.
Sources: SEC: EDGAR Application Programming Interfaces, SEC: Accessing EDGAR Data, SEC webmaster FAQ, and live responses on 2026-10-14.