Skip to content

Topic Feeds & Subjects ​

A topic feed runs a free-text query across every selected provider that can search by topic, then merges and sorts the results like a company feed.

ts
import { buildTopicNewsFeed, buildTopicNewsFeedResult } from "@xbbg/xnews";

const items = await buildTopicNewsFeed({ query: "insurance regulation", limit: 10 });

const result = await buildTopicNewsFeedResult({
  query: "insurance regulation",
  sources: ["google-news", "bing-news", "gdelt", "federal-register", "sec-fulltext"],
  secUserAgent: "my-app/1.0 ops@example.com",
  limit: 25,
});

Topic feeds default to google-news only. Pass sources to fan out.

Which providers accept topics ​

Providers with the topic capability:

  • Search APIs: google-news, bing-news, gdelt, hacker-news, yahoo-search, federal-register, courtlistener.
  • SEC: sec-fulltext (EDGAR full-text search) and sec-current (the latest-filings stream, filtered locally by the query). Both need secUserAgent.
  • Municipal disclosures: msrb-emma, filtered locally. Requires msrbAcceptTermsOfUse: true.
  • Research indexes: arxiv, openalex, bis-research, bis-research-hub, nber, ssrn, crossref, world-bank, biorxiv, medrxiv, europe-pmc, hf-papers, osf-preprints (Research Papers).
  • Publisher-wide lists: ofac (OFAC recent actions) and who-outbreaks (WHO Disease Outbreak News), filtered locally.
  • All 68 fixed feeds, filtered locally (see below).

Ticker-only providers (yahoo-finance, finviz, tickertick, nasdaq, seeking-alpha, hf-transcripts) and sec-edgar cannot search by topic. If you request them for a topic, they are skipped and reported as status: "unsupported" with a warning such as finviz: topic subjects are unsupported.

How fixed feeds match a topic ​

Fixed feeds (wires, newspapers, regulators, central banks) have no search endpoint, so xnews downloads the whole feed and keeps items whose title or summary contains every whitespace-separated token of the query, case-insensitively and at word boundaries. Surrounding quotes on a token are ignored.

That makes short, specific queries work best for fixed feeds. "capital requirements" matches items mentioning both words anywhere in the title or summary; "basel iii endgame capital requirements" requires all five.

ts
import { buildTopicNewsFeedResult } from "@xbbg/xnews";

const regulators = await buildTopicNewsFeedResult({
  query: "capital",
  sources: ["federal-reserve", "fdic", "occ", "ffiec", "cfpb", "sec-press", "federal-register"],
  secUserAgent: "my-app/1.0 ops@example.com", // sec-press is served from sec.gov
});

for (const provider of regulators.providers) {
  console.log(provider.provider, provider.status, provider.itemCount);
}

An empty status from a fixed feed usually means the current headlines do not mention the topic. Feeds with several URLs (for example marketwatch and cnbc) return partial with the surviving items when only some URLs fail.

Recipes ​

Markets and macro headlines ​

ts
import { buildTopicNewsFeed } from "@xbbg/xnews";

const inflation = await buildTopicNewsFeed({
  query: "inflation",
  sources: [
    "google-news",
    "gdelt",
    "wsj",
    "ft",
    "economist",
    "nyt",
    "bbc",
    "guardian",
    "marketwatch",
    "cnbc",
  ],
  since: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
  limit: 50,
});

GDELT enforces roughly one request per five seconds per IP; a shared egress IP can see 429 responses that surface as a provider error while the other providers keep working.

Crypto ​

ts
import { buildTopicNewsFeed } from "@xbbg/xnews";

const etf = await buildTopicNewsFeed({
  query: "bitcoin ETF",
  sources: ["coindesk", "cointelegraph", "google-news", "hacker-news"],
  limit: 30,
});

Municipal disclosures (MSRB EMMA) ​

EMMA publishes a market-wide stream of continuing disclosures (rating changes, bond calls, financial statements). xnews filters the stream locally by issuer name or query.

ts
import { buildTopicNewsFeedResult } from "@xbbg/xnews";

const munis = await buildTopicNewsFeedResult({
  query: "rating",
  sources: ["msrb-emma"],
  // Records your acceptance of the EMMA Terms of Use (https://emma.msrb.org).
  msrbAcceptTermsOfUse: true,
  since: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000),
});

Without msrbAcceptTermsOfUse: true, the provider reports disabled with a config error: accepting EMMA's terms is the caller's act, never the library's. EMMA serves fixed posting windows; xnews fetches Today and Yesterday by default and widens to ThisWeek and LastWeek (EMMA's maximum lookback) when since reaches further back.

Sanctions and outbreaks ​

ts
import { buildTopicNewsFeed } from "@xbbg/xnews";

const sanctions = await buildTopicNewsFeed({ query: "Russia", sources: ["ofac"] });
const outbreaks = await buildTopicNewsFeed({ query: "avian influenza", sources: ["who-outbreaks"] });

The general subject API ​

buildNewsFeed and buildNewsFeedResult take an explicit subject, so one code path can serve both kinds:

ts
import { buildNewsFeedResult, type NewsSubjectInput } from "@xbbg/xnews";

const subjects: NewsSubjectInput[] = [
  { kind: "company", ticker: "RGA", companyName: "Reinsurance Group of America" },
  { kind: "topic", query: "preferred stock offerings" },
];

for (const subject of subjects) {
  const result = await buildNewsFeedResult({
    subject,
    since: "2026-06-01T00:00:00.000Z",
    until: "2026-07-01T00:00:00.000Z",
    secUserAgent: "my-app/1.0 ops@example.com",
  });
  console.log(result.subject.displayName, result.items.length, result.partial);
}

Supported subject kinds are "company" and "topic". Model sectors, funds, indexes, regions, and macro themes as topic queries. The normalized subject on the result carries a stable key (the collapsed query, or the ticker, CIK, or name for companies) and a displayName. An empty topic query throws a RangeError.

buildCompanyNewsFeed* and buildTopicNewsFeed* are thin wrappers that build the subject for you.

Use fixed feeds directly ​

The fixed-feed registry is public, so you can inspect it, fetch one feed with your own subject terms, or parse feed XML you retrieved yourself:

ts
import { FIXED_FEEDS, fetchFixedFeedNews, isFixedFeedProvider } from "@xbbg/xnews";

const definition = FIXED_FEEDS["federal-reserve"];
console.log(definition.label, definition.urls, definition.suggestedMinPollSeconds);

// Provider ids from configuration are plain strings; narrow them before use.
const requested = process.env.XNEWS_FEED ?? "ecb-news";
if (isFixedFeedProvider(requested)) {
  const items = await fetchFixedFeedNews(requested, { query: "monetary policy" }, { limit: 10 });
  console.log(items.map((item) => item.title));
}

Each FIXED_FEEDS entry has a label, its urls, an optional kind (for example press-release), and suggestedMinPollSeconds, a scheduling suggestion rather than an enforced limit. parseFixedFeedNews(provider, xml, terms) from @xbbg/xnews/parsers applies the same parsing and matching to XML you fetched yourself.

Watch a topic ​

ts
import { createTopicNewsWatcher } from "@xbbg/xnews";

for await (const fresh of createTopicNewsWatcher({
  query: "stress test",
  sources: ["federal-reserve", "google-news"],
  intervalMs: 10 * 60_000,
})) {
  for (const item of fresh) console.log(item.title);
}

Watchers poll every intervalMs (default 60 seconds) and yield only unseen items. See Watchlists & Watchers for persistence and shutdown.

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