sec-data-fetcher
v2.0.0
Published
SEC EDGAR filings and financial-data toolkit for Node.js: company facts, submissions, XML parsing and HTML tables.
Maintainers
Readme
A SEC EDGAR filings and financial-data toolkit for Node.js. Find companies by ticker, retrieve submission history and standardized XBRL facts, download filings, parse XML and extract HTML tables. Use it in research scripts, financial-data pipelines and backend applications.
No API key or paid service is required for the supported public SEC endpoints. Supply your application's name and a real contact email in the User-Agent.
Install
npm install sec-data-fetcherRequires Node.js 22+. CI tests Node 22, 24 and 26, including CommonJS, ESM and TypeScript consumers installed from the npm tarball. Upgrading from 1.x? Read the migration notes.
From ticker to filings
Save as filings.mjs:
import { SECClient } from 'sec-data-fetcher';
const userAgent = process.env.SEC_USER_AGENT;
if (!userAgent)
throw new Error('Set SEC_USER_AGENT to your app name and contact email.');
const client = new SECClient({ userAgent, maxRequestsPerSecond: 2 });
const cik = await client.cikLookup('AAPL');
if (!cik) throw new Error('Ticker not found.');
const company = await client.getCompanyData(cik);
const recent = company.filings.recent;
console.log(company.name, company.cik);
console.table(
recent.form.slice(0, 5).map((form, index) => ({
form,
filed: recent.filingDate[index],
accession: recent.accessionNumber[index],
})),
);SEC_USER_AGENT='YourApp [email protected]' node filings.mjsUse your own contact information. This example fetches metadata only; it does not download every filing. In CommonJS, use const { SECClient } = require('sec-data-fetcher') inside your script.
Financial facts
Using the same client and cik:
const facts = await client.getCompanyFacts(cik);
const assets = facts.facts['us-gaap']?.Assets?.units.USD;
if (!assets)
throw new Error('No US-GAAP Assets facts in USD for this company.');
console.table(
assets.slice(-5).map(({ val, end, filed, form, accn }) => ({
value: val,
periodEnd: end,
filed,
form,
accession: accn,
})),
);These are the final five entries in the SEC response, not a deduplicated time series. The same reporting period can appear in multiple filings or amendments. Keep units, period dates and accession numbers when selecting facts. See the SEC XBRL API documentation for coverage and context.
Download a filing and extract tables
const index = recent.form.findIndex(
(form) => form === '10-K' || form === '10-Q',
);
if (index < 0) throw new Error('No recent annual or quarterly report.');
const accession = recent.accessionNumber[index].replaceAll('-', '');
const url = `https://www.sec.gov/Archives/edgar/data/${Number(cik)}/${accession}/${recent.primaryDocument[index]}`;
const html = await client.fetchFiling(url);
const tables = client.extractTablesFromContent(html);
console.log(`${tables.length} tables extracted from ${url}`);Tables are arrays of rows of cell text (string[][][]). Nested tables are returned separately; a parent cell's text still includes its nested content. Rowspan/colspan, financial units and statement semantics are not reconstructed. For XML documents, getObjectFromString(xml) returns a generic XML object; it is not an iXBRL or SGML financial-statement parser.
API
const client = new SECClient({
userAgent: 'YourApp [email protected]', // required; use your real contact
maxRequestsPerSecond: 2, // optional; integer 1–10, default 10
});| Method | Result | Behavior |
| --------------------------------- | ----------------------------- | ---------------------------------------------------------------------------- |
| cikLookup(ticker) | Promise<string \| null> | Case-insensitive ticker lookup; padded CIK or null. |
| getCompanyData(cik) | Promise<CompanySubmissions> | SEC submissions metadata with a normalized string CIK. |
| getCompanyFacts(cik) | Promise<CompanyFacts> | SEC XBRL company facts; retains SEC's numeric CIK. |
| getReports(cik, after?, forms?) | Promise<Filing[]> | Filters recent filings and downloads every matching document into content. |
| fetchFiling(url) | Promise<string> | Raw document text. |
| getObjectFromString(xml) | FilingObject | Generic XML parsing; document-defined entities are not expanded. |
| getObjectFromUrl(url) | Promise<FilingObject> | Downloads text and applies the XML parser. |
| extractTablesFromContent(html) | string[][][] | Extracts HTML table cell text. |
| extractTablesFromFilingUrl(url) | Promise<string[][][]> | Downloads a document and extracts its tables. |
CIKs are strings containing 1–10 digits; leading zeros are added automatically. Core response types are exported from sec-data-fetcher. Additional submissions fields are preserved as unknown.
getReports defaults to after = new Date('2024-01-01') and forms ['10-Q', '10-K', '8-K'] for compatibility. The date comparison is strictly after. Pass a narrow date range and explicit forms, including amendments such as 10-K/A if needed. It reads only filings.recent; it does not traverse historical files in filings.files. All matching content is held in memory. For larger workloads, use metadata and download individual documents as needed.
SEC access and errors
- Run on a Node.js backend. SEC data APIs do not support browser CORS.
- The SEC limit is 10 requests/second per user across all machines. The library's limiter is per client instance; share a client and coordinate budgets across workers.
- Requests have a 30-second timeout. HTTP errors propagate with Axios status information. There are no automatic retries or alternate data sources.
403may reflect User-Agent, network/IP or SEC access restrictions;429means the request rate needs attention. A passed CI run does not guarantee access from every deployment network.- Invalid CIKs, empty User-Agents and rates outside 1–10 fail before requests. Parsing HTML as generic XML does not produce reliable financial statements.
See the SEC's fair-access guidance. For full-history bulk ingestion, use the SEC's official bulk archives instead of issuing one request per company.
Contribute and maintain
Bug reports with a public accession number or reproducible fixture are especially useful. See CONTRIBUTING.md, the security policy and changelog.
npm ci
npm run check # lint, deterministic tests, packed-package consumer checks
SEC_USER_AGENT='YourApp [email protected]' npm run test:liveThe live smoke makes four requests: ticker mapping, submissions, company facts and one annual/quarterly filing. It checks table extraction and reports time/memory for that run. It is separate from the deterministic PR checks because SEC access can vary by network. Dependabot proposes dependency updates; a weekly live workflow checks upstream compatibility.
License
MIT © 2024–2026 Sebastian Boehler. Independent open-source software; not affiliated with or endorsed by the SEC.
