Search SEC EDGAR full-text filings

Site sec.govTask search-edgar-full-text-filingsVersion v2Updated Sep 16, 2026Category research

Search EDGAR full-text filings with form and filing-date filters, detect the 10,000-result cap, recursively partition date ranges, paginate each leaf, and return deduplicated accession records. This skill was captured from a live agent session on sec.gov and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Search the SEC EDGAR full-text index through its JSON endpoint, test whether the requested range reaches the 10,000-result cap, recursively split capped date ranges, paginate uncapped leaves, and return deduplicated accession records with canonical SEC URLs.

Use Cases

  • Find filings containing a term across a custom filing-date range.
  • Search without a form restriction or restrict results to selected forms.
  • Enumerate every distinct accession number across capped result sets.
  • Return matching-document and filing-index URLs.

Automation Flow

  1. Build https://efts.sec.gov/LATEST/search-index?q={query}&forms={forms}&dateRange=custom&startdt={start-date}&enddt={end-date}&from=0; omit forms for unrestricted searches.
  2. Goto the URL with waitUntil: domContentLoaded. The JSON endpoint is the primary data source; the human UI is only a fallback.
  3. Run this evaluate() on the loaded JSON endpoint; it recursively splits ranges reporting gte or at least 10,000 hits, paginates each leaf by its actual returned raw hit count, retries transient failures, deduplicates by accession, and reports incomplete coverage when a window remains saturated or ends early:
(async()=>{
const p=new URLSearchParams(location.search),query=p.get('q')||'',forms=(p.get('forms')||'').split(',').filter(Boolean),start=p.get('startdt'),end=p.get('enddt'),cap=10000,day=86400000;
const iso=t=>new Date(t).toISOString().slice(0,10),ms=s=>new Date(s+'T00:00:00Z').getTime(),next=s=>iso(ms(s)+day),total=x=>typeof x==='object'?(x?.value??0):(x??0),sleep=t=>new Promise(r=>setTimeout(r,t));
const u=(a,b,o)=>{const x=new URL(location.href);x.searchParams.set('dateRange','custom');x.searchParams.set('startdt',a);x.searchParams.set('enddt',b);x.searchParams.set('from',String(o));return x.href};
const get=async(a,b,o)=>{let e;for(let i=0;i<5;i++)try{await sleep(150);const r=await fetch(u(a,b,o),{cache:'no-store'});if(!r.ok)throw Error('EDGAR HTTP '+r.status);const data=await r.json();if(!Array.isArray(data?.hits?.hits))throw Error('EDGAR response has no hits array');return data}catch(x){e=x;await sleep(700*(i+1))}throw e};
const seen=new Set(),records=[],windows=[],saturated=[];
const consume=hs=>{for(const h of hs||[]){const s=h?._source||{},adsh=s.adsh||'';if(!adsh||seen.has(adsh)||(forms.length&&!forms.includes(s.form)))continue;seen.add(adsh);const raw=(s.ciks||[])[0]||null,cik=String(raw||'').replace(/^0+(?=\d)/,'')||null,id=String(h?._id||''),file=id.includes(':')?id.slice(id.indexOf(':')+1):null;records.push({accession_number:adsh,filer_name:String((s.display_names||[])[0]||'').split('  (')[0],all_filers:s.display_names||[],filer_cik:raw,form_type:s.form||null,filing_date:s.file_date||null,period_of_report:s.period_ending||null,matching_file:file,url:cik&&file?'https://www.sec.gov/Archives/edgar/data/'+cik+'/'+adsh.replace(/-/g,'')+'/'+file:null,filing_index_url:cik?'https://www.sec.gov/Archives/edgar/data/'+cik+'/'+adsh.replace(/-/g,'')+'/'+adsh+'-index.htm':null})}};
const leaf=async(a,b,first,depth)=>{const t=total(first?.hits?.total),rel=typeof first?.hits?.total==='object'?(first.hits.total.relation||null):null;let hs=first.hits.hits,n=hs.length;consume(hs);while(hs.length&&n<Math.min(t,cap)){const q=await get(a,b,n);hs=q.hits.hits;consume(hs);n+=hs.length}windows.push({start:a,end:b,depth,total_results:t,total_relation:rel,fetched_hits:n,complete:rel!=='gte'&&t<cap&&n>=t})};
const walk=async(a,b,depth)=>{const first=await get(a,b,0),t=total(first?.hits?.total),rel=typeof first?.hits?.total==='object'?(first.hits.total.relation||null):null;if((rel==='gte'||t>=cap)&&a<b){const mid=iso(ms(a)+Math.floor((ms(b)-ms(a))/2));await walk(a,mid,depth+1);await walk(next(mid),b,depth+1)}else{if(rel==='gte'||t>=cap)saturated.push({start:a,end:b,depth,total_results:t,total_relation:rel});await leaf(a,b,first,depth)}};
await walk(start,end,0);records.sort((a,b)=>String(a.filing_date||'').localeCompare(String(b.filing_date||''))||a.accession_number.localeCompare(b.accession_number));return{status:saturated.length||windows.some(w=>!w.complete)?'partial':'complete',query,forms,date_range:{start,end},initial_test:{cap:cap,reached:saturated.length>0||windows.some(w=>w.total_results>=cap)},windows_checked:windows.length,enumerated_windows:windows.filter(w=>w.complete).length,saturated_windows:saturated,distinct_accessions:records.length,results:records};
})()

Params

ParamWhat it doesExample value
queryFull-text EDGAR search phraserevenue
formsOptional comma-separated exact form filters10-K,10-Q,8-K
start-dateInclusive filing-date lower bound2001-01-01
end-dateInclusive filing-date upper bound2025-12-31
fromZero-based API offset; extractor manages pagination0

Possible Friction Points

TriggerAction
Unbounded or broad range reports total_results: 10000 with relation gteUse the extractor's recursive, non-overlapping date splitting and merge records by accession number.
A pagination request such as offset 900 returns HTTP 500Retry with backoff; if it persists, rerun that date partition as smaller ranges, such as months, and union accessions.
Long recursive in-page enumeration raises Failed to fetch or resets the browser sessionRun separate monthly or yearly date-range navigations and merge their returned accession numbers instead of one full-span evaluate().
Matching-file identifier is absent from result _idKeep matching_file and document URL null while retaining the filing-index URL.
A single-day window still reaches the cap, or a page ends before the reported totalReturn status: partial and the affected window; narrow by additional filters. Never claim full enumeration.
  • from is a raw hit offset, not a page number. Advance by hits.hits.length before filtering forms or deduplicating accessions. Never assume a fixed 10- or 100-hit batch; a request or server default can return fewer hits.
  • For direct HTTP clients, identify the requester with a descriptive User-Agent such as Your Company contact@example.com. Browser JavaScript cannot set that header; configure it in the HTTP client or browser session. Keep aggregate traffic within SEC's fair-access guidance of 10 requests per second; the sequential extractors pause between requests.
  • forms=10-K may include amendments. These extractors filter exact _source.form values after pagination; raw hit totals can exceed returned distinct filings.
  • Additional filters use ciks, plural locationCodes, and locationType=incorporated when applicable. Preserve them on pagination URLs. SIC is available in response metadata; do not assume a SIC query parameter is supported.
  • Matching document filenames come from the suffix of _id after :. The search response does not supply matched-text snippets; fetch the document separately when needed. Preserve co-registrant arrays instead of treating every filing as a single filer.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=sec.gov&task=search-edgar-full-text-filings