Use SecFeed from TypeScript and Node.js
A small typed client for the SecFeed SEC EDGAR API in TypeScript, using the fetch built into Node.js. Map a ticker to a CIK and list its latest 10-Q filings.
SecFeed is a plain HTTPS API that returns JSON, so you do not need an SDK to use it from TypeScript. Node.js 18 and later ship fetch, and Node.js 22.18 and later run .ts files directly by stripping the types, so the whole client below is one file with no dependencies and no build step.
What you need
- Node.js 22.18 or later (or Node.js 18+ with
tsxorts-nodeto run TypeScript) - A SecFeed API key from your dashboard, exported as
SECFEED_KEY
export SECFEED_KEY="sf_your_key_here"
A typed client in 40 lines
Save this as secfeed.ts. The interfaces describe the fields this example reads; the API returns a few more, which TypeScript will simply ignore.
// secfeed.ts: a tiny typed client for the SecFeed API (Node 18+ has fetch built in)
const BASE = process.env.SECFEED_BASE ?? "https://secfeedapi.com";
const KEY = process.env.SECFEED_KEY ?? "";
interface Filing {
accessionNo: string;
cik: string;
ticker: string;
companyName: string;
formType: string;
filedAt: string;
linkToFilingDetails: string;
}
interface FilingSearch {
total: { value: number; relation: "eq" | "gte" };
filings: Filing[];
}
interface TickerMatch {
cik: number;
name: string;
ticker: string;
exchange: string;
}
async function get<T>(path: string, params: Record<string, string | number> = {}): Promise<T> {
const url = new URL(path, BASE);
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
const res = await fetch(url, {
headers: { Authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(60_000),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return (await res.json()) as T;
}
const [match] = await get<TickerMatch[]>("/v1/mapping/ticker", { ticker: "MSFT" });
console.log(`${match.ticker} -> CIK ${match.cik} (${match.name}, ${match.exchange})`);
const search = await get<FilingSearch>("/v1/filings", {
query: `cik:${match.cik} AND formType:"10-Q"`,
size: 3,
});
console.log(`${search.total.value} 10-Q filings (${search.total.relation})`);
for (const f of search.filings) {
console.log(`${f.filedAt} ${f.formType} ${f.accessionNo}`);
}
Run it
node secfeed.ts
Output:
MSFT -> CIK 789019 (MICROSOFT CORP, Nasdaq)
98 10-Q filings (eq)
2026-04-29 10-Q 0001193125-26-191507
2026-01-28 10-Q 0001193125-26-027207
2025-10-29 10-Q 0001193125-25-256321
Microsoft has filed 98 quarterly reports on EDGAR, and the three newest come back first.
How the pieces fit
Authentication. Every request carries Authorization: Bearer <key>. You can also pass ?token=<key> in the URL, which is handy in a browser address bar but easier to leak in logs, so prefer the header in code.
Totals. total.relation is "eq" when the count is exact and "gte" when the API stopped counting at 10,000. Market-wide queries such as every Form 4 ever filed return { value: 10000, relation: "gte" } so the response stays fast.
Timeouts. AbortSignal.timeout(60_000) cancels a request that takes longer than a minute. Most endpoints answer in well under a second; full-text search and PDF rendering take longer.
Paging. /v1/filings returns up to 200 results per call. Pass from to page:
const page2 = await get<FilingSearch>("/v1/filings", {
query: 'formType:"8-K" AND filedAt:[2026-10-01 TO 2026-10-09]',
size: 200,
from: 200,
});
Using it in a browser or edge runtime
The same code works in Deno, Bun, Cloudflare Workers and modern browsers because they all implement fetch. Keep the key on the server: call SecFeed from your backend or an edge function and send only the data your page needs to the browser.
Next steps
- Lucene query syntax for everything
queryaccepts - EDGAR Filing Query API and CIK, CUSIP and ticker mapping
- Form 10-Q dataset
- The full API reference