Products
News & Sentiment
Equity news and sentiment in one product: a seconds-fresh cross-publisher newswire, one-call news digests per ticker, filtered news search, detected market events, analyst rating actions, and daily sentiment scores back to 2000. Use it to spot catalysts, explain a move, and measure whether the news backdrop is turning.
What's inside
- Live newswire. Cross-publisher headlines with publisher, tickers, an
is_majorflag and feed-supplied sentiment, seconds-to-minutes fresh. - Newswire stories and explainers. Full newswire stories by ticker or channel with history back to 2018, "Why It's Moving" explainers with a catalyst type, the movers channel, company press releases and the crypto channel.
- Per-ticker news. A one-call digest (recent stories, sentiment, trending status, events and analyst actions), raw headlines over a short window, company-only coverage, co-mention feeds and a full filtered search.
- Market-wide news. Aggregated cross-source feed, category and topic feeds on the news source's own categories (earnings, M&A, dividends, layoffs, lawsuits and more), headline-only alerts and an end-of-day digest.
- Events. Detected market-moving events, structured events by type, and every article clustered under one event id.
- Sentiment. Daily sentiment per ticker (-1.5 to +1.5), market-wide rollups, most-mentioned tickers, and a deep daily sentiment history per company from 2000.
- Analysts. Consensus rating with price-target bands, and rating actions (upgrades, downgrades, initiations) back to 2022-04-08.
- Social attention. A daily Twitter follower count with its weekly percent change per ticker. There is no mention count, and the upstream dataset was found returning no rows for any ticker in July 2026 (see the notes below).
Crypto news with sentiment is on the Crypto page and FX news is on Macro, Rates & FX. The r/wallstreetbets feed is part of Alt-Data.
Access
2 credits/call · Starter and up
Which endpoint to call
- "What's the news on X?" Start with
GET /. It answers most single-ticker questions in one call. Reach for the drill-down routes (api/ v3/ news/ summary/ {ticker} news/latest,news/trending) only when the summary is not enough. - "What's happening in the market right now?"
GET /(fastest wire) orapi/ v3/ intel/ news_ headlines GET /api/v3/news/aggregated. - "Why is X moving today?"
GET /. For "what moved today",api/ v3/ intel/ news_ why_ moving? tickers=X GET /api/v3/intel/news_.movers - "Did anything big happen today?"
GET /api/v3/news/eventsorGET /.api/ v3/ news/ curated_ events - News with filters (topic, source, sentiment, keyword):
GET /api/v3/news/ticker_. For sector or industry news,news GET /api/v3/news/category. - "Headlines on X in the last few hours":
GET /.api/ v3/ news/ aggregated? ticker=X& hours=N - How sentiment is trending:
GET /for recent days,api/ v3/ news/ sentiment_ stats GET /for years.api/ v3/ news/ sentiment_ history/ {ticker}
Endpoints
date_range on the newswire routes accepts today, yesterday, last7days, last30days, or a custom MMDDYYYY-MMDDYYYY window (sentiment_stats also takes last60days). items is the page size and page the page number where a route paginates.
One ticker
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/v3/news/summary/{ticker} | news_summary | One-shot news for a ticker: recent_news (today), 7-day sentiment_stats, trending, 7-day events and analyst_actions, plus news_count_24h. The primary call for a single name. 90 s cache. |
| GET | /api/v3/news/latest | news_latest | Drill-down only. Raw newswire headlines for one ticker over a short look-back. Query: ticker (the one symbol to read; a request without it is rejected with 422), minutes (5–1440, default 60). Up to 60 the look-back snaps to the newswire's windows of 5, 10, 15, 30, 45 or 60 minutes; above 60 it reads the last 7 days and keeps the headlines published inside the window (up to 50). Returns {ticker, window_; an empty window carries a note saying the source answered with nothing, so it is not read as "no news exists". 60 s cache. |
| GET | /api/v3/news/trending | news_trending | Drill-down only. The newswire's noise-filtered top stories, for one ticker (ticker, optional) or the whole market. Returns {ticker, items[]}. |
| GET | /api/v3/news/ticker_ | news_ticker_only | Stories tagged with only this ticker, excluding articles that co-tag peers. Query: ticker (required), items (1–50, default 50), page (1–50, default 1). Returns {ticker, items[], n}. |
| GET | /api/v3/news/multi_ | news_multi_ticker | Stories where all the listed tickers appear in the same article (deals, rivalries, shared catalysts). Query: tickers (required, comma-separated), items (1–50, default 50), page (1–50, default 1). Returns {tickers[], items[], n}. |
| GET | /api/v3/news/ticker_ | news_ticker_news | Filtered search for when you have filters, not for plain "news on X". Query: tickers (required, comma-separated), items (1–50, default 50), page (1–50, default 1), date_range, topic, topic_or, topic_exclude (see News topics; topic_or and topic_exclude take a comma list), source, source_exclude, news_type (article|video), sentiment (positive|negative|neutral), sortby (rank|oldestfirst), days (1–30, used with sortby=rank), search, search_or, fallback (bool, default false). Returns {tickers[], items[], n}. There is no sector filter here: the source ignores it beside tickers, so sector is refused with 422 unsupported_filter; filter by sector on news/category. |
Market-wide news
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/v3/intel/news_ | intel_news_headlines | Market-wide live newswire. Each item carries headline, publisher, created_at, tickers[], is_major, sentiment and tags[]. Query: ticker (optional filter), limit (1–100, default 50). For one company's news use news_summary. Returns {ticker, items[], count, as_. 120 s cache. |
| GET | /api/v3/news/aggregated | news_aggregated | Aggregated cross-source feed published in the last hours, newest first: market-wide, or for one ticker. Query: hours (1–168, default 24, a trailing window), limit (1–1000, default 100), ticker (optional, one symbol). One call reads at most 150 items (coverage.max_items_read), and coverage.complete is false, with a note, when the answer does not reach back to the window's start. With ticker, only headlines the source tags with that symbol are served, source is news_ticker, and the answer echoes ticker; no headline in the window is an empty news list with a note and symbol_check, and a symbol no listing knows is 404 unknown_ticker, not billed. A list in ticker is refused with a 422; for several symbols use news/ticker_news. Returns {news[], window_; ticker is null market-wide. Not de-duplicated: a story carried by several outlets appears once per outlet. |
| GET | /api/v3/news/category | news_category | Themed or sector-level news. Query: section (general for market-wide, alltickers for company-tagged stories; default general), topic (optional, one of the news topics), sector, industry, source, items (1–50, default 50), page (1–50, default 1), date_range. Returns {section, topic, sector, industry, source, date_. |
| GET | /api/v3/news/by_topic | news_by_topic | Headlines on one event type, optionally for one ticker. Query: topic (required, one of the news topics; anything else is 422 invalid_topic), ticker, items (1–50, default 50), date_range (default last7days). Returns {topic, source_: topic is your word and source_topic the category sent to the source. |
| GET | /api/v3/news/alerts | news_alerts | Headline-only alerts, lighter and faster than full items. Query: category (general for market alerts, ticker to scope by tickers; omit for the latest cross-wire headlines), tickers (comma-separated; tickers alone means category=ticker, and tickers with category=general is 422 unsupported_filter), items (1–100, default 100), page (1–50, default 1). Returns {category, tickers, items[], n}. 30 s cache. |
| GET | /api/v3/news/sundown | news_sundown | Curated end-of-day market recap. Query: date_range (optional). Returns {date_range, digest[]}. |
Newswire stories
Five routes over the newswire, live, newest first. Each item carries id, title, teaser, url, tickers, channels and tags (lower case), published (UTC) and updated; body=true adds the full HTML body. date_from and date_to (UTC days, inclusive, at most 366 days apart) read past windows back to 2018. An empty list is a real empty answer (result: "empty_); a feed that did not answer is a 503 refusal, never an empty list. Quote each story with its time.
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/v3/intel/news_pro | intel_news_pro | Newswire stories, cross-ticker, by ticker (comma-separated, up to 20) or by channel. Items also carry ticker, coins (crypto assets named), author and created (the same instant as published). Channel names are the feed's, lower case, such as movers, press releases, cryptocurrency, earnings, guidance, analyst ratings, price target, markets, top stories, economics and federal reserve; Why It's Moving selects the move explainers. as_of is the newest item's publication time. Use it for themed or full-text news beyond headlines. Query: tickers, channels, limit (1–100, default 25), body, date_from, date_to. |
| GET | /api/v3/intel/news_ | intel_news_why_moving | "Why It's Moving" explainers: short stories on why a stock or coin is making a notable move, each with catalyst_type (earnings, guidance, analyst, ma, regulatory, capital, contract, personnel, macro or other: a keyword reading of the headline, not a model). Explainers are written on US trading days, so the newest can be a day or more old over a weekend. Call it first for "why is X moving today?". Query: tickers (optional), limit (1–100, default 25), body, date_from, date_to. |
| GET | /api/v3/intel/news_ | intel_news_movers | The movers channel: stories on the biggest gainers, losers, halts and breakouts. A story from an earlier session is not today's mover. Use it for "what moved today" questions and end-of-day recaps. Query: tickers, limit (1–100, default 25), body, date_from, date_to. |
| GET | /api/ | intel_ | Company press releases carried on the newswire. Coverage is sparse: about fifty releases from March 2024 to September 2026, so the newest can be weeks old (the staleness block grades it against a 14-day cadence), and an empty answer does not show that a company issued none. Query: tickers, limit (1–100, default 25), body, date_from, date_to. |
| GET | /api/v3/intel/news_ | intel_news_crypto | The newswire's cryptocurrency channel, with coins on each item. tickers takes coins or coin-stock proxies (COIN, MSTR); see Crypto for how a symbol shared by a coin and a stock is read. Query: tickers, limit (1–100, default 25), body, date_from, date_to, asset_class. |
News topics
topic on by_topic and category, and topic, topic_or and topic_exclude on ticker_news, take the news source's own categories. Case does not matter.
earnings,dividend(ordividends),layoffs,lawsuit,product,ceo,podcast,oil,futures.mergersfor M&A.acquisition,acquisitions,mergers_acquisitions,m&aandmandamean the same; all are sent asmanda.pricemovement(orprice_movement),tanalysisfor technical analysis (ortechnical_analysis), andpressrelease(orpress_release).- The access tags
paywallandpaylimitwall, most useful intopic_excludeto drop paywalled articles.
Anything else returns 422 with error: "invalid_topic", and detail names the supported topics; the source is not called and the call is not billed. That includes ipo, fda, analysts, pricetarget, guidance, stock_buyback, stocksplit, partnership, spinoff, bankruptcy, insider, esg and crypto, which this page used to list. The source has no such category, so they always answered an empty list, which read as "no news"; the 422 now says there is no such category. For those subjects use search on ticker_news, and news/ratings for analyst actions.
by_topic echoes your word in topic and the category it sent in source_topic: topic=mergers answers topic: "mergers" and source_topic: "manda". category echoes your word in topic.
Events and catalysts
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/v3/news/events | news_events | Detected market-moving events across the market. Query: hours (1–168, default 24, a trailing window cut by each event's date, read the same way as news/aggregated). Returns {events[], window_. |
| GET | /api/v3/news/curated_ | news_curated_events | Structured events (earnings, M&A, FDA decisions, guidance changes, price-target moves and more). Query: ticker, event_type (for example earnings, mergers, acquisition, pricetarget, fda, guidance), date_range (default today), items (1–50, default 50). Returns {ticker, event_. |
| GET | /api/v3/news/event_ | news_event_by_id | Every article clustered under one event. Query: eventid (required, taken from another news result), items (1–50, default 50), page (1–20, default 1). Returns {eventid, items[], n}. |
Sentiment
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/v3/news/sentiment_ | news_sentiment_stats | Daily sentiment for one ticker: a -1.5 to +1.5 sentiment_score per day with positive, negative and neutral article counts. Query: ticker (required), date_range (default last30days). A day with no articles is absent, not zero. Returns {ticker, date_. |
| GET | /api/v3/news/market_ | news_market_sentiment | Market-wide daily sentiment rollup of general market news. Query: date_range (default last7days). Returns {date_range, market}. |
| GET | /api/ | news_ | Daily sentiment rollup across company-tagged news for the whole tracked universe. Query: date_range (default last7days), page (1–20, default 1). Returns {date_. The rows are one aggregate per day (bucket counts and sentiment_score) and carry no per-ticker breakdown; for per-ticker sentiment call sentiment_stats. |
| GET | /api/v3/news/top_ | news_top_mentions | Most-mentioned tickers in the news with their sentiment tilt, a market-attention proxy. Query: date_range (default today), sector (optional). Returns {date_. |
| GET | /api/ | news_sentiment_history | Daily aggregated news sentiment for one company from 2000: mean event sentiment (ess_mean, -1 to +1), event count and mean relevance per day. Query: start (YYYY-MM-DD, default one year before end), end (YYYY-MM-DD, default today), min_relevance (0–100, default 70; 90+ means the story is about the company), entity (exact entity-name override), limit (1–10000, default 400). Returns {ticker, entity_. |
Analysts
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/ | intel_analyst_consensus | Consensus rating and price-target band. consensus carries rating_label (Strong Buy to Strong Sell), rating_score (1 = Strong Buy, 5 = Strong Sell), the five bucket counts, total_analysts and price_target_avg/_high/_low/_count. When the band comes from a source's own summary, price_target_avg is that source's mean target, not a mean of its mean, high and low. price_ says what price_target_count counts: rating_actions or news_rating_actions (actions carrying a target), or summary_fields (the source's low, mean and high, so 3, never a number of analysts); null when there are no targets. price_target_analysts is the number of analysts behind the targets when the source says (for rating actions, the number of distinct firms), otherwise null. pt_band_source_status names the fallback source of the band (fallback_price_target, news_rating_actions or free_quote_scrape) or says why there is none. recent_actions[] (firm, analyst, rating and target from/to) comes only from the ratings calendar's original feed, which is retired, so it is currently always empty; that does not mean no analyst acted. recent_ says why the list is what it is: calendar_answered, calendar_error, source_retired or source_not_configured. For rating actions use news/ratings below or the ratings calendar. Query: recent_action_limit (1–50, default 10; caps recent_actions). 1 h cache. |
| GET | /api/v3/news/ratings | news_ratings | Analyst rating actions: upgrades, downgrades and initiations, history back to 2022-04-08. Query: tickers (comma-separated), rating_type (upgrade|downgrade|initiation), date_range, items (1–50, default 50), page (1–50, default 1). Returns {tickers, rating_. |
The ratings calendar, GET / on Macro, Rates & FX, reads the same live ratings feed as news/ratings and adds each action's price-target move (action_pt), derived from the prior and current targets, with action and date filters. Until 2026-09-25 it served an archive that ended on 2026-06-26.
Social attention
| Method | Path | MCP tool | Description |
|---|---|---|---|
| GET | /api/ | intel_twitter | Daily Twitter follower count for one ticker. Each item has ticker, date, followers and pct_change (weekly percent change); there is no mention count. While the upstream dataset returns no rows for any ticker, the route answers 503 with error_ and Retry-After: 86400, not billed; for retail attention use intel/wsb on Alt-Data. Query: limit (days, 1–365, default 60). Returns {ticker, items[], count, source}. |
Coverage and freshness notes.
- A filter a route does not take is refused, never dropped. On every
/api/v3/news/*,/api/v3/intel/news_*,/api/v3/news_crypto/*and/api/v3/news_forex/*route, a non-blankticker,tickers,symbol,symbols,sector,sectors,industry,topicortopicsthat the route does not declare (and, onnews_forex,pair,pairs,currencyorcurrencies) answers422witherror: "unsupported_, not billed.filter" detailnames the parameter or route to use instead,unsupportedlists the refused names andacceptedthe route's own parameters. Before this, such a filter was silently dropped and the unfiltered answer was served as if filtered. Any other unknown parameter is still ignored. - Newswire routes are read-through caches with short lifetimes (30 s for alerts, 60 s for
news/latest, 90 s fornews/summary, 120 s forintel/news_headlines, up to 10 minutes for rollups), so the same call inside that window returns the same payload. - When the news feed fails and returns nothing, the
/api/v3/news/*routes answer503witherror: "upstream_, not billed, instead of aunavailable" 200with an empty list.error_codenames the failure (upstream_,quota_ exhausted upstream_http_error,upstream_not_configuredand so on),upstream_errorsummarises it, andRetry-Aftersays when to retry. The body has noitemsfield. A feed that answered with nothing is still a200with an empty list, and a partial failure keeps its rows beside avendor_errorblock.news/summaryrefuses this way when its sub-calls come back empty while the feed's quota is exhausted. When the feed is out of quota and states when its quota resets,upstream_erroraddsquota_resets_at(UTC) andquota_resets_at_basis(the feed names no time zone; its time is read as New York time), and the body addsretry_:after_ basis: "firm_ recheck" Retry-Afteris when Wealthnow next checks the feed, within the hour, not when the quota returns. Untilquota_resets_at, or a plan change at the source, expect the same refusal. sentiment_historystitches a news-analytics archive (2000 to 2025-12-31) with the live feed from 2026. It keys the archive by the resolved company's name, soidentity_verifiedis alwaysfalse; if the name cannot be resolved you geterror: "entity_and can passunmatched" entityto override. With no matching rows the response carrieswarning: "no_rows". When the archive or the live feed cannot be read, it answers503witherror: "upstream_andunavailable" error_, not billed, never an empty series.code: "warehouse_ read_ failed" - The rating-actions feed behind
recent_actionsonanalyst_consensusis retired (no data after 2026-06-30).recent_actionsis empty,recent_isactions_ source_ status source_retired, and the price-target band comes from the fallback sources named inpt_band_source_status.news/ratingsand, since 2026-09-25, the ratings calendar read a different, live feed; use them for rating actions. - The upstream Twitter dataset returned no rows for any ticker when checked on 2026-07-02 and 2026-09-24, and the route has no fallback source. While that lasts,
intel/twitteranswers503 upstream_rather than an empty list. It does not mean the ticker had no followers or attention.dataset_ empty
Examples
Example 1: One call for a ticker's news (bash)
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/news/summary/AAPL" # Response:# {# "ok": true,# "timestamp": "2026-09-24T14:05:11Z",# "ticker": "AAPL",# "recent_news": [# {# "title": "Apple shares rise after supplier raises its outlook",# "text": "Shares of Apple rose after ...",# "news_url": "https://...",# "source_name": "...",# "date": "Thu, 24 Sep 2026 09:41:00 -0400",# "sentiment": "Positive",# "tickers": ["AAPL"]# }# ],# "sentiment_stats": { ... }, # 7-day daily rollup, same shape as news/sentiment_stats# "trending": [ ... ],# "events": [ ... ], # 7-day structured events# "analyst_actions": [ ... ], # 7-day upgrades / downgrades / initiations# "news_count_24h": 18# }Example 2: Market-wide headlines (bash)
intel/news_headlines is the market-wide wire. Leave ticker off for a market sweep.
curl -H "Authorization: Bearer $WEALTHNOW_API_KEY" \ "https://firm.wealthnow.io/api/v3/intel/news_headlines?limit=10" # Response:# {# "ok": true,# "timestamp": "2026-09-24T14:06:02Z",# "ticker": null,# "items": [# {# "headline": "Treasury yields climb ahead of the jobs report",# "publisher": "...",# "created_at": "2026-09-24T14:04:37Z",# "tickers": [],# "is_major": true,# "sentiment": "neutral",# "tags": []# }# ],# "count": 10,# "as_of": "2026-09-24T14:06:02Z",# "source": "vendor:options-flow"# }Example 3: Filtered search and daily sentiment (Python)
import os, requests HEADERS = {"Authorization": f"Bearer {os.environ['WEALTHNOW_API_KEY']}"}BASE = "https://firm.wealthnow.io" # Negative earnings coverage on two names over the last weekr = requests.get( f"{BASE}/api/v3/news/ticker_news", params={"tickers": "NVDA,AMD", "topic": "earnings", "sentiment": "negative", "date_range": "last7days", "items": 20}, headers=HEADERS, timeout=30,)r.raise_for_status()data = r.json() # {"ok": true, "timestamp": ..., "tickers": ["NVDA", "AMD"], "items": [...], "n": 20}for item in data["items"][:5]: print(item["date"], item["source_name"], item["title"]) # Daily sentiment for one ticker: outer keys are dates, inner keys are tickersr = requests.get( f"{BASE}/api/v3/news/sentiment_stats", params={"ticker": "NVDA", "date_range": "last7days"}, headers=HEADERS, timeout=30,)r.raise_for_status()stats = r.json()["stats"]# stats["data"] == {"2026-09-23": {"NVDA": {"Positive": 14, "Negative": 3,# "Neutral": 9, "sentiment_score": 0.42}}, ...}for day, by_ticker in sorted(stats.get("data", {}).items()): row = by_ticker.get("NVDA") if row: print(day, row["sentiment_score"])Example 4: Daily sentiment through the 2008 crisis (Python)
import os, requests r = requests.get( "https://firm.wealthnow.io/api/v3/news/sentiment_history/MSFT", params={"start": "2008-01-01", "end": "2009-12-31", "min_relevance": 90, "limit": 1000}, headers={"Authorization": f"Bearer {os.environ['WEALTHNOW_API_KEY']}"}, timeout=60,)r.raise_for_status()data = r.json()# {# "ok": true,# "ticker": "MSFT",# "entity_names": ["MICROSOFT CORP"],# "start": "2008-01-01", "end": "2009-12-31",# "min_relevance": 90.0,# "n": 498,# "rows": [{"date": "2008-01-02", "ess_mean": 0.21, "n_events": 12, "relevance_mean": 97.5}, ...],# "identity": {...},# "identity_verified": false,# "source": "warehouse:news-analytics-archive"# }worst = min(data["rows"], key=lambda row: row["ess_mean"])print("Most negative day:", worst["date"], worst["ess_mean"])Example 5: Analyst consensus and recent rating actions (Python)
recent_actions on analyst_consensus is empty because its source feed is retired (recent_), so read rating actions from news/ratings.
import os, requests HEADERS = {"Authorization": f"Bearer {os.environ['WEALTHNOW_API_KEY']}"}BASE = "https://firm.wealthnow.io" r = requests.get(f"{BASE}/api/v3/intel/analyst_consensus/AAPL", headers=HEADERS, timeout=30)r.raise_for_status()data = r.json() # envelope: {"ok": true, "timestamp": "...", "consensus": {...}, "pt_band_source_status": "...", ...}c = data["consensus"]print(f"{c['rating_label']} (score {c['rating_score']}, {c['total_analysts']} analysts)")print(f"Price target {c['price_target_low']} to {c['price_target_high']}, " f"avg {c['price_target_avg']}, source: {data['pt_band_source_status']}") r = requests.get( f"{BASE}/api/v3/news/ratings", params={"tickers": "AAPL", "date_range": "last30days", "items": 5}, headers=HEADERS, timeout=30,)r.raise_for_status()for action in r.json()["items"]: print(action)