sec-filings-api
v0.3.0
Published
SEC EDGAR filings, XBRL financials, full-text search and Form 4 insider transactions as clean JSON. Works with no API key and no hosted service: talks straight to EDGAR. Form 4 transaction codes are classified, so tax withholdings (F) and option exercises
Maintainers
Readme
sec-filings-api
SEC EDGAR filings, XBRL financials, full-text search, and Form 4 insider transactions with the transaction codes actually classified — as clean JSON.
- No API key. No account. No hosted service required. By default it talks straight to SEC EDGAR from your process.
- Zero dependencies. Nothing enters your lockfile.
- Node 18+, browsers, Cloudflare Workers, Deno, Bun. Full TypeScript types.
npm install sec-filings-apiimport { FilingsClient } from "sec-filings-api";
// The SEC requires a declared User-Agent. Use your own app name and contact.
const sec = new FilingsClient({
userAgent: "MyApp (contact: [email protected])",
});
const co = await sec.company("AAPL");
console.log(co.name, co.cik); // Apple Inc. 0000320193
const fin = await sec.financials("MSFT");
console.log(fin.financials.revenue.value); // 331839000000
const ins = await sec.insiders("MSFT", { limit: 10, openMarketOnly: true });
console.log(ins.summary.openMarketSellValueUsd);Command line
The package ships a sec-filings binary, so it is usable without writing any
code. No install needed to try it:
export SEC_USER_AGENT="MyApp (contact: [email protected])"
npx sec-filings company AAPL
npx sec-filings filings NVDA --form 10-K --limit 3
npx sec-filings financials MSFT
npx sec-filings insiders MSFT --limit 10 --open-market
npx sec-filings search "material weakness" --forms 8-K --limit 5MICROSOFT CORP (MSFT) 3 filing(s), 0 unreadable
open-market BUY (P): $0
open-market SELL (S): $43,388,532.40
non-discretionary (F/M/...): $12,609,373.27
DATE OWNER CODE MEANING SHARES
---------- ---------------- ---- ------------------------------------------------- ----------
2026-09-01 Nadella Satya S * Open-market or private sale 24,723
2026-08-31 SMITH BRADFORD L A Grant, award or other acquisition from the issuer 27,688
2026-08-31 SMITH BRADFORD L F Shares withheld by issuer to pay tax on vesting 17,036
* = open-market (discretionary) trade. Unmarked rows are not decisions to buy or sell.Add --json to any command for raw JSON instead of the table, so the same
binary works interactively and in a pipe:
npx sec-filings insiders TSLA --limit 25 --json | jq '.summary'--ua overrides $SEC_USER_AGENT for a single call. Errors go to stderr with
a non-zero exit code; missing or generic User-Agents fail with an explanation
rather than a stack trace.
The part that matters: Form 4 transaction codes
EDGAR publishes Form 4 only as raw XML. There is no JSON representation anywhere in EDGAR, so every consumer either pays a vendor or writes the parser.
But parsing is the easy half. Classifying the transaction code is the half that is usually wrong. "Insider sold shares" is the headline most free trackers show, and it frequently is not a sale at all:
| Code | Meaning | Discretionary trade? |
|------|---------|----------------------|
| P | Open-market or private purchase | ✅ yes |
| S | Open-market or private sale | ✅ yes |
| F | Shares withheld by the issuer to pay tax on vesting | ❌ no |
| M | Exercise or conversion of a derivative held | ❌ no |
| A | Grant or award from the issuer | ❌ no |
| G | Bona fide gift | ❌ no |
F is a payroll mechanic. M is an option exercise. Neither is a decision to
sell into the market, and adding them to "insider selling" inflates it.
Every transaction carries openMarket, which is true for P and S only.
const { summary } = await sec.insiders("MSFT", { limit: 25 });
summary.openMarketBuyValueUsd; // code P only
summary.openMarketSellValueUsd; // code S only
summary.nonDiscretionaryValueUsd; // F, M, A, G, ...How much does the distinction matter?
Measured across 476 real transactions from 8 large issuers:
| | USD |
|---|---|
| Genuine open-market selling (S) | $512.18M |
| Non-discretionary disposals (F, M, …) | $160.61M |
| Naive total reported as "insider selling" | $672.79M |
Conflating them overstates open-market selling by 31% in aggregate, and by 2.35× for a single issuer in that sample. All 8 codes observed were recognised; none fell through unclassified.
Every row also exposes rule10b5_1Plan, so trades pre-scheduled under a
10b5-1 plan can be separated from fresh discretionary decisions.
API
All methods return promises. Both modes return identical shapes.
company(ticker)
Registrant profile — CIK, SIC, entity type, exchanges, state of incorporation, fiscal year end — plus the 5 most recent filings with resolved document URLs.
filings(ticker, { form, limit })
Recent filings, optionally filtered by form type. limit caps at 100.
await sec.filings("NVDA", { form: "10-K", limit: 3 });financials(ticker)
Latest annual XBRL values for 8 headline concepts: revenue, netIncome,
assets, liabilities, stockholdersEquity, cashAndEquivalents,
operatingCashFlow, eps.
Several concepts have historical tag aliases. Each result reports which
us-gaap tag actually answered, or an explicit reason when nothing did —
so a missing value never silently reads as zero.
const f = await sec.financials("AAPL");
f.financials.revenue.value; // number
f.financials.revenue.tag; // "RevenueFromContractWithCustomerExcludingAssessedTax"
f.financials.revenue.end; // period end dateinsiders(ticker, { limit, openMarketOnly })
Parsed Form 4 filings. limit caps at 25. See above.
A filing that cannot be read appears as { accessionNumber, unavailable }
and is counted in unavailableCount — visible, rather than silently dropped
from the totals.
search(q, { forms, from, to, limit })
Full-text search across filing documents.
await sec.search('"climate risk"', { forms: "10-K", limit: 10 });Coverage caveat, stated in every response: EDGAR's full-text index covers 2001 onward, not the full 1993+ archive. Earlier filings exist but are not full-text searchable.
Direct mode vs hosted mode
// default: straight to EDGAR. No key, no service, no cost.
new FilingsClient({ userAgent: "MyApp (contact: [email protected])" });
// optional: through a Filings API deployment.
new FilingsClient({ mode: "hosted", baseUrl: "https://...", apiKey: "..." });Identical response shapes, and the parsing/classification code is literally the same module in both cases — so switching is a constructor change and nothing else.
When direct mode is enough: scripts, notebooks, batch jobs, side projects, anything where you control request volume.
When hosted mode earns its keep: the SEC's fair-access limit is roughly 10 requests/second, and breaching it IP-bans you for 10 minutes. There is also no uptime guarantee on the government endpoint. Hosted mode puts an edge cache in front of both problems, which matters once real users are hitting your product rather than you hitting a script.
About the User-Agent requirement
Direct mode requires userAgent, and there is deliberately no default.
The SEC's fair-access policy requires a declared User-Agent identifying the requester; a generic one is rejected outright (verified live — this is not a documentation nicety). A shared default baked into this package would make every installation look like one identity to the SEC and get the whole user base throttled together. So you declare your own.
Format: "YourAppName (contact: [email protected])".
Data, licensing and honesty
The underlying data is SEC EDGAR, public domain (17 U.S.C. §105). This package claims no ownership of it and does not pretend it is proprietary — you can fetch all of it yourself for free.
What this package provides is the part that costs time: CIK resolution, the Form 4 XML parser, transaction-code classification, XBRL tag-alias handling, URL resolution, and honest error semantics.
Not affiliated with or endorsed by the U.S. Securities and Exchange Commission.
Not investment advice. Insider transaction data is filed by registrants and
may be amended; 4/A amendments appear as separate filings.
MIT licensed.
Changelog
0.3.0
- New:
sec-filingsCLI. Every method is reachable from the command line, with a table view by default and--jsonfor pipes.npx sec-filings ...needs no install. - New:
formTypeon filing objects. It is an alias offormand always identical to it.insiders().filings[].formTypealready used that name for the same concept, and the inconsistency was a trap.formremains the primary field and is not deprecated; nothing was renamed, so this is additive and breaks nothing.
0.2.0
- Dual mode:
direct(straight to EDGAR, no hosted service) andhosted. - Form 4 parsing with transaction-code classification.
- Full-text search, XBRL financials, filings, company profile.
