Company Feeds
A company feed fans one company out to several providers and merges the results into one newest-first list of NewsItem records.
import { buildCompanyNewsFeed, buildCompanyNewsFeedResult } from "@xbbg/xnews";
// Just the items.
const items = await buildCompanyNewsFeed({
ticker: "RGA",
companyName: "Reinsurance Group of America",
secUserAgent: "my-app/1.0 ops@example.com",
limit: 20,
});
// Items plus per-provider diagnostics.
const result = await buildCompanyNewsFeedResult({
ticker: "RGA",
companyName: "Reinsurance Group of America",
secUserAgent: "my-app/1.0 ops@example.com",
sources: ["sec-edgar", "yahoo-finance", "google-news", "finviz", "nasdaq", "gdelt"],
});
console.log(items.length, result.providers.map((p) => `${p.provider}:${p.status}`));buildCompanyNewsFeed returns Promise<NewsItem[]>. buildCompanyNewsFeedResult returns a NewsFeedResult with providers, warnings, partial, and the normalized subject. Use the result form in production so you can see which providers answered.
Identify the company
CompanyNewsQuery takes a required ticker plus optional companyName and cik. xnews trims and upper-cases the ticker, collapses whitespace in the name, and trims the CIK.
Supply both a ticker and a name whenever you can: some providers only search by ticker, others only by name.
| Requirement | Providers |
|---|---|
| Ticker | yahoo-finance, finviz, tickertick, nasdaq, seeking-alpha, hf-transcripts |
| Ticker or CIK (CIK wins when both are set) | sec-edgar |
| Company name | sec-current, federal-register, courtlistener, msrb-emma |
| Name or ticker | Every other company-capable provider, including google-news, bing-news, gdelt, hacker-news, yahoo-search, sec-fulltext, research providers, ofac, who-outbreaks, and all fixed feeds |
A provider whose requirement is not met is not called. It reports status: "unsupported" with a warning such as nasdaq: company ticker is required.
How the subject becomes a query also varies. Google News and Bing News search for "Company Name" TICKER when both are present, GDELT searches the quoted company name, and most other query providers use the company name and fall back to the ticker. yahoo-search prefers the ticker.
Feeds without a ticker
buildCompanyNewsFeedResult needs a ticker. For a company you only know by CIK or name, call the general subject API, which accepts any of the three identifiers:
import { buildNewsFeedResult } from "@xbbg/xnews";
const filings = await buildNewsFeedResult({
subject: { kind: "company", cik: "0000320193", companyName: "Apple Inc." },
sources: ["sec-edgar", "sec-current", "federal-register"],
secUserAgent: "my-app/1.0 ops@example.com",
});A company subject with no ticker, name, or CIK throws a RangeError before any request is made.
Choose providers
Without sources, company feeds use sec-edgar, yahoo-finance, google-news, and finviz. Passing sources replaces that default entirely, so include the defaults you still want. Any provider with the company capability can participate; the Source Catalog lists all of them.
import { buildCompanyNewsFeedResult, providerCapabilities } from "@xbbg/xnews";
console.log(providerCapabilities("sec-fulltext")); // ["company", "topic", "filing"]
const wide = await buildCompanyNewsFeedResult({
ticker: "NVDA",
companyName: "NVIDIA",
secUserAgent: "my-app/1.0 ops@example.com",
sources: [
"sec-edgar",
"sec-fulltext",
"yahoo-finance",
"google-news",
"bing-news",
"finviz",
"tickertick",
"nasdaq",
"pr-newswire",
"globenewswire",
],
limit: 50,
});Providers fall into three groups:
- Query providers call an upstream search or per-symbol endpoint for your subject (
google-news,sec-edgar,gdelt, research indexes, and so on). - Fixed feeds (68 of them, such as
pr-newswire,cnbc,federal-reserve, and the central-bank feeds) download whole RSS/Atom feeds and keep items whose title or summary mentions the company name as a phrase or the ticker as a standalone uppercase token (RGA,$RGA,NYSE:RGA). Single-letter tickers only match with a cashtag or exchange prefix. Anemptystatus usually means current headlines simply do not mention the company. - Non-subject providers (
youtube,cftc-cot,ffiec-cdr,dtcc-sdr) reportunsupportedin feeds. Use their dedicated APIs instead.
SEC filings
Three providers read SEC EDGAR, and all of them require a declared User-Agent:
| Provider | What it reads | Notes |
|---|---|---|
sec-edgar | The company's EDGAR Atom feed | Ticker or CIK; secForms issues one request per form |
sec-fulltext | EDGAR full-text search | Searches the company name or ticker; honors secForms, since, and until |
sec-current | The latest-filings stream | Company subjects use EDGAR's name search; honors secForms |
import { buildCompanyNewsFeedResult } from "@xbbg/xnews";
const filings = await buildCompanyNewsFeedResult({
ticker: "RGA",
companyName: "Reinsurance Group of America",
sources: ["sec-edgar", "sec-fulltext"],
secForms: ["8-K", "10-Q", "10-K"],
secUserAgent: "my-app/1.0 ops@example.com",
limit: 30,
});
for (const filing of filings.items) {
console.log(filing.formType, filing.filingDate ?? filing.publishedAt, filing.title, filing.url);
}secUserAgent is required for every request to sec.gov and its subdomains; a shared userAgent satisfies it when secUserAgent is omitted. Values are trimmed, and blank values or values with control characters fail before any network I/O. Use a string that names your application and a real contact. SEC's fair-access policy also caps traffic at 10 requests per second across all sec.gov endpoints (PROVIDER_POLICIES["sec-edgar"]), so keep concurrency low when you fan out across many companies.
Date windows and limits
import { buildCompanyNewsFeedResult } from "@xbbg/xnews";
const june = await buildCompanyNewsFeedResult({
ticker: "RGA",
companyName: "Reinsurance Group of America",
secUserAgent: "my-app/1.0 ops@example.com",
since: "2026-06-01T00:00:00.000Z",
until: "2026-06-30T23:59:59.999Z",
limit: 100,
});
for (const provider of june.providers) {
if (provider.undatedExcluded) {
console.log(`${provider.provider} dropped ${provider.undatedExcluded} undated items`);
}
}sinceanduntilare inclusive and accept ISO strings orDateobjects.sinceafteruntilthrows aRangeError.- The window is forwarded upstream where an endpoint supports it (GDELT, SEC full-text search, Federal Register, CourtListener, and the arXiv, OpenAlex, Crossref, Europe PMC, World Bank, bioRxiv/medRxiv, and OSF research indexes) and is always enforced locally after fetching.
msrb-emmamapssinceonto EMMA's fixed posting windows. - With a window, items without a parseable
publishedAtare excluded (fail closed) and counted inundatedExcluded. limitcaps the merged list and is also passed to each provider as its page size. When a window is set, every provider except arXiv, OpenAlex, Crossref, Europe PMC, and World Bank (whose queries absorb the window) is fetched without the limit, so the window is never applied to a truncated page; the limit is applied after merging.limit: 0returns an empty result without any requests. Negative or fractional limits throw aRangeError.
Event hints
Each item can carry eventKind and tags, deterministic hints derived from keywords in the title, summary, source, SEC form type, and URL:
eventKind | Triggered by (examples) |
|---|---|
filing | An SEC form type or a filing item |
earnings | "earnings", "quarter results", "Q3 results" |
management | "appoints", "CEO", "resigns", "board of directors" |
capital-markets | "offering", "prices", "senior notes" (also tags debt) |
preferred | "preferred stock", "depositary shares" |
dividend | "dividend" |
rating | "upgrade", "downgrade", "Moody", "Fitch", "S&P" |
regulatory | "regulator", "Federal Reserve", "FDIC", "SEC" |
legal | "lawsuit", "settlement", "litigation", "court" |
mna | "merger", "acquisition", "takeover" |
analysis / press-release | Research papers / press-release feeds |
The first matching rule sets eventKind; tags collects every match. These are routing hints for filtering and UI grouping. They are not sentiment, materiality, or investment signals. classifyMarketEvent(item) exposes the same classifier for your own records.
Dedupe and identity
Within one call, items that share a canonical URL (or item URL) are merged into one record. The merged item keeps every contributing provider in seenInProviders and every { provider, source, url } in provenance. Google News links point at news.google.com redirect URLs, so Google News items rarely merge with the same story from another provider.
Across calls, key your store on NewsItem.id. It is derived from the provider, the item's GUID or link (the accession number for SEC Atom entries), and the title, and its derivation is versioned by NEWS_ITEM_ID_SCHEME_VERSION:
import { buildCompanyNewsFeed, NEWS_ITEM_ID_SCHEME_VERSION, type NewsItem } from "@xbbg/xnews";
const seen = new Map<string, NewsItem>(); // replace with your database
const items = await buildCompanyNewsFeed({
ticker: "RGA",
companyName: "Reinsurance Group of America",
secUserAgent: "my-app/1.0 ops@example.com",
});
const fresh = items.filter((item) => !seen.has(item.id));
for (const item of fresh) seen.set(item.id, item);
console.log(`${fresh.length} new items (id scheme v${NEWS_ITEM_ID_SCHEME_VERSION})`);Store NEWS_ITEM_ID_SCHEME_VERSION with your records; if a future release changes the derivation, the version tells you which ids to re-key. Watchers apply the same logic for you (Watchlists & Watchers).
Strict mode
The array-returning builders accept strict: true and throw Error("News feed incomplete: …") when any provider produced a warning, including unsupported or disabled providers. Leave it off when you deliberately include providers that may not apply to every company.
Related
- Earnings Calls & Commentary: transcripts and curated SEC full-text queries.
- Watchlists & Watchers: many companies at once, and polling.
- Provider Policies: SEC, EMMA, and other access requirements.
