Skip to content

Company Feeds ​

A company feed fans one company out to several providers and merges the results into one newest-first list of NewsItem records.

ts
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.

RequirementProviders
Tickeryahoo-finance, finviz, tickertick, nasdaq, seeking-alpha, hf-transcripts
Ticker or CIK (CIK wins when both are set)sec-edgar
Company namesec-current, federal-register, courtlistener, msrb-emma
Name or tickerEvery 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:

ts
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.

ts
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. An empty status usually means current headlines simply do not mention the company.
  • Non-subject providers (youtube, cftc-cot, ffiec-cdr, dtcc-sdr) report unsupported in feeds. Use their dedicated APIs instead.

SEC filings ​

Three providers read SEC EDGAR, and all of them require a declared User-Agent:

ProviderWhat it readsNotes
sec-edgarThe company's EDGAR Atom feedTicker or CIK; secForms issues one request per form
sec-fulltextEDGAR full-text searchSearches the company name or ticker; honors secForms, since, and until
sec-currentThe latest-filings streamCompany subjects use EDGAR's name search; honors secForms
ts
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 ​

ts
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`);
  }
}
  • since and until are inclusive and accept ISO strings or Date objects. since after until throws a RangeError.
  • 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-emma maps since onto EMMA's fixed posting windows.
  • With a window, items without a parseable publishedAt are excluded (fail closed) and counted in undatedExcluded.
  • limit caps 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: 0 returns an empty result without any requests. Negative or fractional limits throw a RangeError.

Event hints ​

Each item can carry eventKind and tags, deterministic hints derived from keywords in the title, summary, source, SEC form type, and URL:

eventKindTriggered by (examples)
filingAn 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-releaseResearch 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:

ts
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.

xbbg is independent open-source software and is not affiliated with, endorsed by, sponsored by, or approved by Bloomberg Finance L.P.