DEV Community

Search Every SEC Filing by Keyword With the Keyless EDGAR Full Text API

Endpoint

The endpoint is a plain JSON API, no key and no login:

GET https://efts.sec.gov/LATEST/search-index?q=%22material+weakness%22&forms=10-K&startdt=2026-01-01&enddt=2026-06-30

Parameters

  • q is the query. Wrap a phrase in double quotes for an exact match.
  • forms is an optional comma list of form types, e.g. 8-K,10-K.
  • startdt and enddt scope the filing date (with dateRange=custom).
  • ciks restricts to one or more companies by CIK number.
  • from is the paging offset.

EDGAR asks for a descriptive User-Agent with contact info. Send one and you are fine:

const res = await fetch(url, {
  headers: {
    Accept: 'application/json',
    'User-Agent': 'YourApp contact@example.com',
  },
});

Response Shape

Results come back Elasticsearch style under hits.hits. Each hit has an _id and a _source:

{
  "_id": "0001493152-26-015257:form10-ka.htm",
  "_source": {
    "display_names": ["Where Food Comes From, Inc. (WFCF) (CIK 0001360565)"],
    "ciks": ["0001360565"],
    "form": "10-K/A",
    "file_date": "2026-04-06",
    "adsh": "0001493152-26-015257"
  }
}

Two Useful Tricks

The display_names string packs the company, ticker, and CIK together. A small regex pulls them apart:

const m = name.match(/^(.*?)\s*\(([^)]+)\)\s*\(CIK\s*\d+\)\s*$/);
const companyName = m ? m[1].trim() : name;
const ticker = m ? m[2].trim() : null;

The _id is accession:filename, and adsh is the accession number. Combine them into a direct link to the document:

const filename = id.slice(id.indexOf(':') + 1);
const nodash = adsh.replace(/-/g, '');
const cik = String(Number(ciks[0])); // strip leading zeros
const url = `https://www.sec.gov/Archives/edgar/data/${cik}/${nodash}/${filename}`;

Paging

Each request returns up to 100 hits. Advance from in steps of 100 until you have what you need. EDGAR caps deep paging at a 10,000 result window per query, so read the reported hits.total.value and stop there.

let from = 0;
while (from < 10000) {
  const data = await getJson(buildUrl(from));
  const hits = data.hits.hits;
  if (hits.length === 0) break;
  // ...process hits...
  if (hits.length < 100) break;
  from += 100;
}

Why It Is Handy

Full text search across every filer is a different job from watching one company or one form. You can catch risk language like "going concern" or "material weakness" wherever it lands, follow an executive across filers, or track a product mention over time. And because it is a light JSON GET, a run costs almost nothing.

If you want this packaged instead of maintained by hand, I run it as a keyless pay per use actor on Apify. Give it a query and it returns one JSON row per filing with a direct link. The first rows of every run are free so you can check the output.

https://apify.com/scrapemint/sec-filing-fulltext-scraper

Comments

No comments yet. Start the discussion.