GuidesFINANCE APIS
SEC EDGAR API from browser JavaScript
The SEC EDGAR APIs need no key, and on 28 October 2026 the submissions endpoint on data.sec.gov sent Access-Control-Allow-Origin: *, so fetch() works with no proxy. 10 of the 12 endpoints we tested send no CORS header, including the XBRL financial data APIs, company_tickers.json, the filing archive and full-text search, so the browser blocks them. Prefix those URLs with https://proxy.cors.dev/: the free tier needs no signup, handles GET and HEAD at about 600 requests a minute per IP and returns up to 6 MiB, which fits everything except companyfacts for the largest banks.
Which EDGAR endpoints work from the browser
The data.sec.gov APIs need no API key and no authentication. SEC's API documentation says data.sec.gov does not support CORS, yet on 28 October 2026 the submissions endpoint sent Access-Control-Allow-Origin: * to both https://example.com and http://localhost:5173, so the browser can call it directly with no proxy.
Everything else sent no CORS header: the XBRL endpoints on the same host, the files and filing archive on www.sec.gov, and full-text search on efts.sec.gov. fetch() from a web page fails for those until you prefix the URL with https://proxy.cors.dev/. Sizes are for Apple Inc. (CIK 0000320193) unless another company is named.
| Endpoint | Returns | Size | Browser CORS | Through cors.dev |
|---|---|---|---|---|
| data.sec.gov/submissions/CIK{cik}.json | Company profile and recent filings | 164 KB | Yes, * | 200, not needed |
| submissions/CIK{cik}-submissions-001.json | Older filings | 206 KB | Yes, * | 200, not needed |
| api/xbrl/companyconcept/CIK{cik}/us-gaap/{tag}.json | One concept, one company | 19 KB | No | 200 |
| api/xbrl/companyfacts/CIK{cik}.json | Every concept, one company | 3.8 MB | No | 200 |
| companyfacts for JPMorgan Chase | Every concept, one company | 8.0 MB | No | 502, over 6 MiB |
| api/xbrl/frames/us-gaap/{tag}/USD/{period}.json | One concept, all companies | 443 KB | No | 200 |
| www.sec.gov/files/company_tickers.json | Ticker to CIK map | 798 KB | No | 200 |
| files/company_tickers_exchange.json | Ticker, CIK and exchange | 523 KB | No | 200 |
| Archives/edgar/data/{cik}/{accession}/index.json | Files in one filing | 9 KB | No | 200 |
| Filing document (.htm) | 10-K HTML | 1.5 MB | No | 200 |
| cgi-bin/browse-edgar, action=getcompany, output=atom | Company filings feed | 48 KB | No | 200 |
| cgi-bin/browse-edgar, action=getcurrent, output=atom | Latest filings feed | 23 to 29 KB | No | 200 |
| efts.sec.gov/LATEST/search-index | Full-text search | 39 KB | No | 200 |
Look up a CIK from a ticker, then list filings
data.sec.gov URLs need the 10-digit CIK with leading zeros. company_tickers.json maps tickers to CIKs: 798 KB, 10,430 entries with cik_str, ticker and title. It sends no CORS header, so the code below loads it through the proxy and then calls the submissions API directly.
// Runs in a module script or the DevTools console
const proxy = 'https://proxy.cors.dev/';
let tickerMap;
async function getJson(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`${response.headers.get('X-Cors-Error') ?? response.status} from ${url}`);
return response.json();
}
async function latestFilings(ticker, formType = '10-K', limit = 5) {
// company_tickers.json sends no CORS header: load it through the proxy, once per page
tickerMap ??= getJson(proxy + 'https://www.sec.gov/files/company_tickers.json');
const company = Object.values(await tickerMap).find((entry) => entry.ticker === ticker.toUpperCase());
if (!company) throw new Error(`Unknown ticker: ${ticker}`);
// The submissions API sends Access-Control-Allow-Origin: *, so no proxy here
const cik = String(company.cik_str).padStart(10, '0');
const { recent } = (await getJson(`https://data.sec.gov/submissions/CIK${cik}.json`)).filings;
return recent.form
.map((form, i) => ({
form,
filed: recent.filingDate[i],
url: `https://www.sec.gov/Archives/edgar/data/${company.cik_str}/${recent.accessionNumber[i].replaceAll('-', '')}/${recent.primaryDocument[i]}`,
}))
.filter((filing) => filing.form === formType)
.slice(0, limit);
}
console.log(await latestFilings('AAPL'));filings.recent holds at least one year or the 1,000 most recent filings, whichever is more (Apple: 1,002), as parallel arrays such as accessionNumber, filingDate, form and primaryDocument: index i in each array is the same filing. Older filings sit in the files listed in filings.files; Apple's CIK0000320193-submissions-001.json covers 1,268 filings from 1994 to 2015.
Archive URLs drop the leading zeros from the CIK and the dashes from the accession number: https://www.sec.gov/Archives/edgar/data/320193/000032019325000079/aapl-20250927.htm is Apple's 10-K filed 31 October 2025. A plain link to it needs no proxy; reading the document with fetch() does. The free proxy does not cache, so keep the ticker file in memory instead of loading it for every lookup.
Financial data from the XBRL APIs
There are three XBRL APIs, all on data.sec.gov, none with a CORS header, all 200 through cors.dev:
companyconcept: one concept (taxonomy plus tag) for one company, such as/api/xbrl/companyconcept/CIK0000320193/us-gaap/AccountsPayableCurrent.jsonat 19 KB.companyfacts: every concept for one company,/api/xbrl/companyfacts/CIK0000320193.jsonat 3.8 MB.frames: one fact per company for one concept and period,/api/xbrl/frames/us-gaap/AccountsPayableCurrent/USD/CY2019Q1I.jsonat 443 KB, covering 3,390 companies.
Frame periods are CY2024 for a year (365 days plus or minus 30), CY2024Q1 for a quarter (91 days plus or minus 30) and CY2024Q1I for an instant such as a balance sheet date. Units with a denominator use -per-, as in USD-per-shares.
Tags differ between companies, so check which one a filer uses before you compare. Apple reports revenue as RevenueFromContractWithCustomerExcludingAssessedTax: 416,161,000,000 USD for the fiscal year ending 27 September 2025, tagged CY2025. JPMorgan Chase (CIK 19617) reports Revenues: 182,447,000,000 USD for 2025. Keeping only facts with a CY#### frame gives one value per year:
// Runs in a module script or the DevTools console
const proxy = 'https://proxy.cors.dev/';
async function annualValues(cik, tag, unit = 'USD') {
const paddedCik = String(cik).padStart(10, '0');
const url = `https://data.sec.gov/api/xbrl/companyconcept/CIK${paddedCik}/us-gaap/${tag}.json`;
const response = await fetch(proxy + url);
if (!response.ok) throw new Error(`XBRL request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
// Facts with a CY#### frame give one value per calendar year
const concept = await response.json();
return (concept.units[unit] ?? [])
.filter((fact) => /^CY\d{4}$/.test(fact.frame ?? ''))
.map((fact) => ({ year: fact.frame.slice(2), end: fact.end, value: fact.val, form: fact.form }));
}
console.table(await annualValues(320193, 'RevenueFromContractWithCustomerExcludingAssessedTax'));
console.table((await annualValues(19617, 'Revenues')).slice(-3));The proxy returns responses up to 6 MiB (6,291,456 bytes, about 6.3 MB), on the free tier and on cors.dev Pro. companyfacts is bigger than that for the largest banks: JPMorgan Chase 8.0 MB, Citigroup 9.0 MB, Wells Fargo 7.9 MB and Bank of America 6.9 MB returned 502 with X-Cors-Error: response_too_large, while General Electric (6.17 MB), Goldman Sachs (6.01 MB), Microsoft (4.9 MB) and Amazon (4.5 MB) fit. For big filers, request the concepts you need with companyconcept; JPMorgan's Revenues is 19 KB.
SEC fair access rules
- Rate: no more than 10 requests per second per user, regardless of how many machines send them. Above that, requests from the IP address may be limited for a brief period, until the rate has stayed below the threshold for 10 minutes.
- User-Agent: SEC asks automated tools to declare a company name and admin email, like
Sample Company Name AdminContact@<sample company domain>.com. Server scripts with an undeclaredUser-Agent, such as curl or an empty one, got403"Your Request Originates from an Undeclared Automated Tool". In a web app the browser sends its ownUser-Agent. - Freshness: the submissions API typically updates less than a second after a filing is disseminated, the XBRL APIs in under a minute.
- Bulk: to cover every company, download
companyfacts.zipandsubmissions.zip, rebuilt nightly around 3:00 a.m. ET, on your server with your own declaredUser-Agentinstead of making thousands of API calls.
A web app that looks up a few companies per visit fits these rules. A crawler over every filer belongs on a server.
Free proxy or cors.dev Pro for EDGAR data
EDGAR needs only GET and no key, so the free tier covers every endpoint on this page, apart from companyfacts files over 6 MiB: prefix the URL with https://proxy.cors.dev/, with no signup, about 600 requests a minute per IP, no daily quota, responses up to 6 MiB and no caching.
cors.dev Pro is $5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card. You sign in, create a Connection with your site's origin and the SEC hosts, and send its key as X-Cors-Key. It allows 3,000 requests a minute per account and 100 in flight, and adds an optional GET cache with X-Cors-Cache: 60 (1 to 300 seconds; cache hits still count as requests). The data.sec.gov JSON files can be cached; the 798 KB ticker file cannot, because www.sec.gov sends it with cookies, so keep it in memory.
Good questions.
Does the SEC EDGAR API need an API key?
No. The data.sec.gov APIs need no key and no authentication. SEC asks automated tools to stay under 10 requests per second and to declare a User-Agent with a company name and contact email.
Why does fetch() to data.sec.gov fail with a CORS error?
Only the submissions endpoint sends Access-Control-Allow-Origin: *. The XBRL endpoints companyfacts, companyconcept and frames send no CORS header, and SEC's documentation says data.sec.gov does not support CORS. Call submissions directly and prefix the XBRL URLs with https://proxy.cors.dev/.
How do I get a CIK from a stock ticker?
Load https://www.sec.gov/files/company_tickers.json (798 KB, 10,430 entries) through a CORS proxy and find the entry whose ticker matches. Pad its cik_str with zeros to 10 digits for data.sec.gov URLs: AAPL is 320193, so CIK0000320193.
Why does companyfacts fail for JPMorgan through a CORS proxy?
JPMorgan's file is 8.0 MB and cors.dev returns at most 6 MiB, so the proxy answers 502 with X-Cors-Error: response_too_large. Citigroup, Wells Fargo and Bank of America are also too big. Request single concepts with companyconcept instead.
How quickly does EDGAR data update after a filing?
The submissions API typically updates less than a second after a filing is disseminated, and the XBRL APIs in under a minute. The bulk ZIP files are rebuilt nightly around 3:00 a.m. ET.
Can I download every 10-K from the browser?
Not within SEC's rules: the limit is 10 requests per second per user, and SEC wants bulk tools to declare who they are in the User-Agent. Run that job on a server, or start from the nightly submissions.zip and companyfacts.zip.