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.

SEC EDGAR endpoints from the browser, checked October 2026
EndpointReturnsSizeBrowser CORSThrough cors.dev
data.sec.gov/submissions/CIK{cik}.jsonCompany profile and recent filings164 KBYes, *200, not needed
submissions/CIK{cik}-submissions-001.jsonOlder filings206 KBYes, *200, not needed
api/xbrl/companyconcept/CIK{cik}/us-gaap/{tag}.jsonOne concept, one company19 KBNo200
api/xbrl/companyfacts/CIK{cik}.jsonEvery concept, one company3.8 MBNo200
companyfacts for JPMorgan ChaseEvery concept, one company8.0 MBNo502, over 6 MiB
api/xbrl/frames/us-gaap/{tag}/USD/{period}.jsonOne concept, all companies443 KBNo200
www.sec.gov/files/company_tickers.jsonTicker to CIK map798 KBNo200
files/company_tickers_exchange.jsonTicker, CIK and exchange523 KBNo200
Archives/edgar/data/{cik}/{accession}/index.jsonFiles in one filing9 KBNo200
Filing document (.htm)10-K HTML1.5 MBNo200
cgi-bin/browse-edgar, action=getcompany, output=atomCompany filings feed48 KBNo200
cgi-bin/browse-edgar, action=getcurrent, output=atomLatest filings feed23 to 29 KBNo200
efts.sec.gov/LATEST/search-indexFull-text search39 KBNo200

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.

Ticker to CIK, then the latest 10-K filings
// 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.json at 19 KB.
  • companyfacts: every concept for one company, /api/xbrl/companyfacts/CIK0000320193.json at 3.8 MB.
  • frames: one fact per company for one concept and period, /api/xbrl/frames/us-gaap/AccountsPayableCurrent/USD/CY2019Q1I.json at 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:

Annual revenue from the XBRL companyconcept API
// 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 undeclared User-Agent, such as curl or an empty one, got 403 "Your Request Originates from an Undeclared Automated Tool". In a web app the browser sends its own User-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.zip and submissions.zip, rebuilt nightly around 3:00 a.m. ET, on your server with your own declared User-Agent instead 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.