TypeScript SEC EDGAR client for filing discovery, company data, XBRL financials, and full-text search.
npm install edgar-ts
# or
pnpm add edgar-ts
# or
yarn add edgar-ts- Node.js 20.0.0+ or Bun 1.0+
- Zero runtime dependencies
- Filing discovery — Date-bounded search with optional CIK and form-type filtering
- Index file discovery — Bulk discovery across all filers via SEC quarterly index files
- Company metadata — Name, tickers, SIC code, entity type, state of incorporation
- Ticker/name lookup — Resolve tickers or company names to CIKs
- Exhibit enumeration — Normalized exhibit metadata from filing indices
- Contract filtering — Built-in
EX-10*contract exhibit isolation - Raw download — Exhibit bytes with MIME hints and SHA-256 integrity hash
- Bulk data — Download SEC nightly archives (submissions.zip, companyfacts.zip)
- XBRL financials — Company Facts, Company Concept, and Frames API access
- Full-text search — Keyword search across all SEC filings via EFTS
- SEC-compliant — Mandatory user-agent, rate limiting (8 req/s default), bounded retries
- Deterministic — Canonical normalization, stable sort, deduplication
- Zero dependencies — No runtime dependencies
- Dual runtime — Node.js and Bun support
import { EdgarClient } from "edgar-ts"
const client = new EdgarClient({
userAgent: "AcmeLegalBot/1.0 (ops@acme.test)",
})
// Look up a company by ticker
const matches = await client.lookupCompany("AAPL")
const { cik } = matches[0] // "0000320193"
// Get company metadata
const company = await client.getCompanyInfo(cik)
console.log(company.name, company.sic, company.tickers)
// Discover filings for a specific company
const filings = await client.discoverFilings({
cik,
from: "2026-01-01",
to: "2026-12-31",
})
// Or discover across ALL filers (uses quarterly index files)
const allFilings = await client.discoverFilings({
from: "2026-01-01",
to: "2026-03-31",
formTypes: ["10-K"],
})
// Get contract exhibits (EX-10*) for a filing
for (const filing of filings) {
const exhibits = await client.listContractExhibits(filing)
for (const exhibit of exhibits) {
const { bytes, sha256, sizeBytes } = await client.downloadExhibit(exhibit)
// Store bytes and metadata in your downstream system
}
}| Option | Type | Default | Description |
|---|---|---|---|
userAgent |
string |
required | Descriptive user-agent for SEC compliance |
maxRequestsPerSecond |
number |
8 |
Global request rate cap |
timeoutMs |
number |
10000 |
Per-request timeout |
retries |
RetryOptions |
{ maxAttempts: 3, baseDelayMs: 250, maxDelayMs: 4000 } |
Retry configuration |
telemetry |
TelemetryOptions |
— | Optional request/retry hooks |
| Method | Returns | Description |
|---|---|---|
getCompanyInfo(cik) |
Promise<CompanyInfo> |
Company metadata (name, tickers, SIC, entity type, etc.) |
lookupCompany(query) |
Promise<CompanyTicker[]> |
Search by ticker (exact) or company name (substring) |
| Method | Returns | Description |
|---|---|---|
discoverFilings(input) |
Promise<FilingRef[]> |
Date-bounded filing discovery. With CIK: uses Submissions API. Without CIK: uses quarterly index files. |
| Method | Returns | Description |
|---|---|---|
listExhibits(filing) |
Promise<ExhibitRef[]> |
All exhibits for a filing |
listContractExhibits(filing) |
Promise<ExhibitRef[]> |
Contract exhibits only (EX-10*) |
downloadExhibit(exhibit) |
Promise<DownloadedExhibit> |
Raw bytes + metadata + SHA-256 |
| Method | Returns | Description |
|---|---|---|
downloadSubmissionsBulk() |
Promise<BulkDownloadResult> |
Download SEC nightly submissions.zip (~2GB) |
downloadCompanyFactsBulk() |
Promise<BulkDownloadResult> |
Download SEC nightly companyfacts.zip |
| Method | Returns | Description |
|---|---|---|
getCompanyFacts(cik) |
Promise<CompanyFacts> |
All XBRL facts across all filings |
getCompanyConcept(cik, taxonomy, tag) |
Promise<CompanyConcept> |
Single concept time series (e.g., us-gaap/Revenue) |
getFrame(taxonomy, tag, unit, period) |
Promise<Frame> |
Cross-company comparison at a point in time |
| Method | Returns | Description |
|---|---|---|
searchFilings(query) |
Promise<SearchResult> |
Keyword search with form type, date, and entity filters |
Note:
searchFilingswraps the SEC's EFTS Elasticsearch API, which is undocumented and could change without notice.
// Find a company by ticker
const results = await client.lookupCompany("MSFT")
// [{ cik: "0000789019", ticker: "MSFT", name: "MICROSOFT CORP", exchange: "Nasdaq" }]
// Get full company metadata
const info = await client.getCompanyInfo("789019")
// { cik: "0000789019", name: "MICROSOFT CORP", tickers: ["MSFT"], sic: "7372", ... }// Discover filings for a specific company
const filings = await client.discoverFilings({
cik: "320193",
from: "2026-01-01",
to: "2026-12-31",
})
// Discover across all filers (no CIK — uses index files)
const allFilings = await client.discoverFilings({
from: "2026-01-01",
to: "2026-03-31",
formTypes: ["10-K", "10-Q"],
})
// Custom form types for a specific company
const proxyFilings = await client.discoverFilings({
cik: "320193",
from: "2026-01-01",
to: "2026-12-31",
formTypes: ["DEF 14A"],
})// Get all XBRL facts for Apple
const facts = await client.getCompanyFacts("320193")
// Get Revenue time series
const revenue = await client.getCompanyConcept("320193", "us-gaap", "Revenue")
for (const val of revenue.units.USD) {
console.log(`FY${val.fy}: $${val.val}`)
}
// Compare Revenue across all companies for Q1 2024
const frame = await client.getFrame("us-gaap", "Revenue", "USD", "CY2024Q1")
console.log(`${frame.data.length} companies reported Revenue`)// Search for filings mentioning "non-compete"
const results = await client.searchFilings({
q: "non-compete agreement",
formTypes: ["10-K", "8-K"],
from: "2024-01-01",
to: "2024-12-31",
})
console.log(`${results.total} filings found`)
for (const hit of results.hits) {
console.log(`${hit.entityName} — ${hit.formType} (${hit.fileDate})`)
}const filing = filings[0]
const exhibits = await client.listExhibits(filing)
const contracts = await client.listContractExhibits(filing)
const downloaded = await client.downloadExhibit(contracts[0])
console.log(`Downloaded ${downloaded.sizeBytes} bytes`)
console.log(`SHA-256: ${downloaded.sha256}`)
console.log(`MIME type: ${downloaded.mimeType || "unknown"}`)import type {
EdgarClientOptions,
FilingRef,
ExhibitRef,
DownloadedExhibit,
CompanyInfo,
CompanyTicker,
} from "edgar-ts"import { EdgarError, ValidationError, TimeoutError } from "edgar-ts"
try {
await client.discoverFilings(input)
} catch (err) {
if (err instanceof ValidationError) {
// Invalid input parameters
} else if (err instanceof TimeoutError) {
// Request exceeded timeout
}
}edgar-ts provides optional telemetry helpers for logging and metrics:
Human-readable colored output for development:
import { EdgarClient } from "edgar-ts"
import { createConsoleLogger } from "edgar-ts/telemetry"
const client = new EdgarClient({
userAgent: "MyBot/1.0 (contact@example.com)",
telemetry: createConsoleLogger()
})
// Outputs:
// → GET https://data.sec.gov/submissions/... [discoverFilings] {abc12345}
// ← 200 GET https://data.sec.gov/submissions/... 1234ms [discoverFilings]
// ⟳ Retry 2/3 after 500ms: GET ... (TIMEOUT)Track request lifecycle and rate limiting metrics:
import { createMetricsAggregator } from "edgar-ts/telemetry"
const metrics = createMetricsAggregator()
const client = new EdgarClient({
userAgent: "MyBot/1.0 (contact@example.com)",
telemetry: metrics
})
// ... make requests ...
const snapshot = metrics.getSnapshot()
console.log(snapshot.requestsTotal) // 42
console.log(snapshot.requestsSuccessful) // 40
console.log(snapshot.requestsFailed) // 2
console.log(snapshot.latencyByOperation) // { discoverFilings: { avg: 250, min: 100, max: 1200 } }
console.log(snapshot.rateLimitedRequests) // 0JSON Lines output for log aggregation systems:
import { createStructuredLogger } from "edgar-ts/telemetry"
const client = new EdgarClient({
userAgent: "MyBot/1.0 (contact@example.com)",
telemetry: createStructuredLogger()
})
// Outputs JSON Lines:
// {"event":"request.start","url":"...","operation":"discoverFilings",...}
// {"event":"request.end","statusCode":200,"durationMs":1234,...}
// {"event":"request.retry","attempt":2,"error":"TIMEOUT",...}Implement your own hooks for integration with observability platforms:
const client = new EdgarClient({
userAgent: "MyBot/1.0 (contact@example.com)",
telemetry: {
onRequestStart: (event) => {
console.log(`Starting ${event.operation} (${event.requestId})`)
},
onRequestEnd: (event) => {
console.log(`Completed in ${event.durationMs}ms`)
},
onRetry: (event) => {
console.log(`Retry ${event.attempt}/${event.maxAttempts}`)
}
}
})Telemetry Event Fields:
requestId- Unique ID for request correlationoperation- EdgarClient method (discoverFilings, listExhibits, getCompanyInfo, searchFilings, etc.)endpointClass- SEC endpoint type (submissions, archive, full-index, xbrl, efts, files, bulk-data)runtime- Detected runtime (node or bun)timestamp- Event timestamp (milliseconds)url,method,statusCode,durationMs- Request details
pnpm install # Install dependencies
pnpm test:run # Run tests
pnpm build # Build (ESM + CJS)
pnpm lint # Lint
pnpm typecheck # Type checkMIT