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
qis the query. Wrap a phrase in double quotes for an exact match.formsis an optional comma list of form types, e.g.8-K,10-K.startdtandenddtscope the filing date (withdateRange=custom).ciksrestricts to one or more companies by CIK number.fromis 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.
Comments
No comments yet. Start the discussion.