Stock Analyst Ratings - Upgrades, Downgrades, Targets
Pricing
from $0.40 / 1,000 results
Stock Analyst Ratings - Upgrades, Downgrades, Targets
Daily analyst rating changes across US, UK, and Canadian markets: upgrades, downgrades, price targets, initiations, reiterations. Also per-ticker history with consensus snapshot and per-brokerage feeds with 12-month ROI. Built for AI agents, MCP pipelines, and portfolio monitoring.
Pricing
from $0.40 / 1,000 results
Rating
0.0
(0)
Developer
Michal Búci
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Stock Analyst Ratings Scraper - Upgrades, Downgrades & Price Targets
Get daily analyst rating changes for the US, UK, and Canadian markets in a single call. Upgrades, downgrades, initiations, reiterations, and price target moves — all in one structured dataset. Also supports per-ticker rating history with a consensus snapshot and per-brokerage feeds with 12-month ROI. Built for AI agents, MCP pipelines, and portfolio monitoring.
What does this actor do?
This actor extracts analyst rating activity from MarketBeat and returns it as clean JSON. Three modes, one unified schema:
| Mode | How to trigger | Typical output |
|---|---|---|
| Daily market feed | Default (no tickers/brokerages) | ~400 rows/day across US+UK+Canada |
| Ticker history | Set tickers | Last ~15 dated ratings per ticker + consensus snapshot |
| Brokerage feed | Set brokerages | ~100 dated ratings per firm with 12-month ROI |
Unlike per-ticker lookup tools, the daily feed gives you a market-wide view of every analyst action — no need to query ticker by ticker.
Why scrape analyst ratings from MarketBeat?
Analyst upgrades and downgrades move markets. Getting them structured and timely is hard: most sources only offer per-ticker views, paywalled APIs, or stale RSS. MarketBeat aggregates actions from 150+ research firms covering US, UK, and Canadian stocks and exposes them in plain HTML. This actor turns that into structured JSON, ready for LLMs, trading systems, dashboards, or alerting pipelines.
How to scrape analyst ratings
- Open the Stock Analyst Ratings Scraper actor page
- Pick a mode: leave everything blank for the daily feed, add
tickersfor per-ticker history, or addbrokeragesfor per-firm feeds - Click Start
- Download results as JSON, CSV, or Excel, or read them via the Apify API or MCP
Input
| Parameter | Type | Default | Description |
|---|---|---|---|
tickers | string | — | Comma-separated ticker symbols (e.g. AAPL, TSLA, NVDA). Accepts EXCHANGE:TICKER to skip exchange lookup (NASDAQ:AAPL). Switches to ticker history mode. |
brokerages | string | — | Comma-separated brokerage names. Fuzzy-matched against ~150 tracked firms (JPMorgan, Goldman Sachs, Citi). Switches to brokerage mode. |
market | string | all | Filter the daily feed: all, us (NYSE/NASDAQ/AMEX), uk (LON), ca (TSE). Ignored when tickers/brokerages are set. |
actions | array | [] | Keep only these normalized actions: upgrade, downgrade, initiate, reiterate, target_raised, target_lowered, target_set. Empty = keep all. |
daysBack | integer | 0 | Keep only ratings within the last N days (ticker/brokerage modes only, daily feed is always today). 0 = no filter. |
includeConsensus | boolean | true | In ticker mode, attach consensus snapshot (rating, target, breakdown, 1m/3m/1y history) to each row. No extra requests. |
maxItems | integer | 0 | Hard cap on rows. 0 = unlimited. |
Example inputs
Daily feed, US-only upgrades and downgrades:
{ "market": "us", "actions": ["upgrade", "downgrade"] }
Ticker history for a portfolio, with consensus:
{ "tickers": "AAPL, TSLA, NVDA, MSFT", "includeConsensus": true }
Recent JPMorgan and Goldman calls:
{ "brokerages": "JPMorgan, Goldman Sachs", "daysBack": 30 }
Output
Every row, regardless of mode, uses this schema. Null fields are omitted from each row (only populated fields are included).
{"ticker": "ABNB","exchange": "NASDAQ","company": "Airbnb","action": "upgrade","brokerage": "Wells Fargo & Company","analyst": "Ken Gawrelski","priorRating": "Equal Weight","newRating": "Overweight","priorTarget": 136.0,"newTarget": 178.0,"targetCurrency": "USD","currentPrice": 144.18,"stockPriceChangePct": 1.1,"upsideToTargetPct": 23.45,"ratingId": 2386270,"sourceUrl": "https://www.marketbeat.com/ratings/"}
| Field | Type | Description |
|---|---|---|
ticker | string | Ticker symbol |
exchange | string | NYSE, NASDAQ, NYSEAMERICAN, LON, or TSE |
company | string | Company name |
action | string | Normalized enum: upgrade, downgrade, initiate, reiterate, target_raised, target_lowered, target_set |
brokerage | string | Research firm / investment bank |
analyst | string/null | Individual analyst. null when gated behind MarketBeat's paywall. |
priorRating | string/null | Rating before this action |
newRating | string/null | Rating after this action |
priorTarget | number/null | Previous price target in quote currency |
newTarget | number/null | New price target in quote currency |
targetCurrency | string/null | USD, GBX (British pence), CAD, etc. |
currentPrice | number | Stock price at scrape time (daily + brokerage modes) |
stockPriceChangePct | number | Intraday percent change of the underlying stock price |
upsideToTargetPct | number | Percent upside from current stock price to the new price target |
ratingDate | string | ISO YYYY-MM-DD date the rating was issued (ticker + brokerage modes only) |
recommendationRoi12mo | number | 12-month ROI on this specific recommendation (brokerage mode only) |
ratingId | integer | MarketBeat's stable internal ID for this rating. Use as dedup key. |
sourceUrl | string | Canonical MarketBeat URL the row was scraped from |
consensus | object | Consensus snapshot with 1m/3m/1y history (ticker mode with includeConsensus) |
Consensus object (ticker mode)
{"rating": "Moderate Buy","priceTarget": 303.06,"upsideToTargetPct": 10.94,"breakdown": { "strongBuy": 1, "buy": 22, "hold": 12, "sell": 1 },"history": {"current": { "target": 303.06, "rating": "Moderate Buy" },"oneMonthAgo": { "target": 297.58, "rating": "Moderate Buy" },"threeMonthsAgo": { "target": 281.70, "rating": "Moderate Buy" },"oneYearAgo": { "target": 236.97, "rating": "Moderate Buy" }}}
Useful for detecting whether analysts are getting more bullish or bearish on a name over time — one request, four data points.
Use cases
| Use case | Description | Best for |
|---|---|---|
| Daily rating digest | Pull the full market feed on a schedule for downstream filtering and alerting | Portfolio managers, newsletters |
| Portfolio news monitor | Run in ticker mode each day for a watchlist and flag consensus drift | Retail investors, AI portfolio agents |
| Earnings-week monitoring | Filter for upgrade/downgrade in the days after earnings | Event-driven strategies |
| LLM-fed research | Feed the daily feed into an MCP-enabled agent to summarize market sentiment | ChatGPT, Claude, custom agents |
| Brokerage tracking | Pull a specific firm's calls with their 12-month ROI to evaluate analyst quality | Quants, research desks |
| Alerting | Filter downgrade or large target_lowered moves on a portfolio | Risk-aware traders |
| Historical dataset | Run daily and store; builds a clean analyst ratings database | Data science / backtest research |
How to use with Python
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("michael_b/stock-analyst-ratings").call(run_input={"market": "us","actions": ["upgrade", "downgrade"],})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(f"[{item['ticker']}] {item['brokerage']}: {item['priorRating']} -> {item['newRating']}")
How to use with JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('michael_b/stock-analyst-ratings').call({tickers: 'AAPL, TSLA, NVDA',includeConsensus: true,});const { items } = await client.dataset(run.defaultDatasetId).listItems();items.forEach(r => console.log(`${r.ticker}: ${r.brokerage} -> ${r.newRating} @ ${r.newTarget}`));
How much does it cost?
Raw HTTP requests with no browser keep costs minimal. Recommended memory: 512 MB.
| Scenario | HTTP requests | Output rows | Typical runtime |
|---|---|---|---|
| Daily feed | 1 | ~400 | a few seconds |
| 10 tickers + consensus | 10 | ~150 + consensus | 10-15 seconds |
| 5 brokerages | 6 (+1 index) | ~500 | 10-15 seconds |
Automate with Apify
- Schedule daily for a rolling ratings archive
- Use with MCP — fully compatible with Apify's MCP server for Claude, ChatGPT, and custom agents
- Hook into Make, n8n, or Zapier for no-code alerting
- Export as JSON, CSV, Excel, or stream to your database via the Apify API
FAQ
Is it legal to scrape MarketBeat? This actor only accesses publicly available pages. No private data is read, no authentication is bypassed, and every row links back to the source URL on marketbeat.com.
How often is the data updated? Live on every run. MarketBeat updates the main feed continuously through the trading day.
Why is analyst sometimes null?
MarketBeat gates individual analyst names behind their All Access subscription on the main feed. Ticker history and brokerage pages tend to include analysts more consistently.
Why is ratingDate null on the daily feed?
The main feed table doesn't show per-row dates, every row on it is from today. If you need exact timestamps, use ticker or brokerage mode.
How do I dedup across runs?
Use ratingId. It's MarketBeat's stable internal identifier for each rating and won't collide between runs.
Why doesn't ticker mode show a current price? The per-ticker forecast page doesn't include intraday quote data. The daily feed does. Combine modes if you need both.
How are brokerage names matched?
Fuzzy match against MarketBeat's directory (/ratings/by-issuer/). JPMorgan, JP Morgan, and J.P. Morgan all resolve. If no match is found, the actor logs a warning and skips that name.
Is Canada supported?
Partially. The main feed includes Toronto (TSE) rows. Filter with market: "ca". MarketBeat's dedicated Canadian endpoint redirects back to the main feed — we handle that automatically.
Support
Questions, feature requests, or bugs? Open an issue in the Issues tab.