# Stock Analyst Ratings - Upgrades, Downgrades, Targets (`michael_b/stock-analyst-ratings`) Actor

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.

- **URL**: https://apify.com/michael\_b/stock-analyst-ratings.md
- **Developed by:** [Michal Búci](https://apify.com/michael_b) (community)
- **Categories:** AI, MCP servers, Automation
- **Stats:** 1 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 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](https://www.marketbeat.com) 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**

1. Open the [Stock Analyst Ratings Scraper](https://apify.com/michael_b/stock-analyst-ratings) actor page
2. Pick a mode: leave everything blank for the daily feed, add `tickers` for per-ticker history, or add `brokerages` for per-firm feeds
3. Click **Start**
4. 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:

```json
{ "market": "us", "actions": ["upgrade", "downgrade"] }
```

Ticker history for a portfolio, with consensus:

```json
{ "tickers": "AAPL, TSLA, NVDA, MSFT", "includeConsensus": true }
```

Recent JPMorgan and Goldman calls:

```json
{ "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).

```json
{
  "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)

```json
{
  "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**

```python
from apify_client import ApifyClient

client = 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**

```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](https://apify.com/michael_b/stock-analyst-ratings/issues).

# Actor input Schema

## `tickers` (type: `string`):

Comma-separated ticker symbols to pull rating history for. When set, the actor switches to ticker history mode and returns the last ~15 dated ratings per ticker plus optional consensus block. Example: AAPL, TSLA, NVDA. Leave blank to scrape the daily market feed. If a ticker is ambiguous across markets, prefix it with its exchange (e.g. NASDAQ:AAPL, LON:ABF, TSE:ABX).

## `brokerages` (type: `string`):

Comma-separated brokerage names to pull recent ratings per firm. Fuzzy-matched against MarketBeat's 300+ tracked issuers (e.g. "JPMorgan", "Goldman Sachs", "Citi"). When set, the actor switches to brokerage mode and returns ~100 dated ratings per brokerage with 12-month ROI.

## `market` (type: `string`):

Filter the daily feed by market. 'all' returns everything (US + UK + Canada). Ignored when tickers or brokerages are set.

## `actions` (type: `array`):

Keep only ratings matching these action types. Leave empty to include all actions.

## `daysBack` (type: `integer`):

Keep only ratings within the last N days. Applies to ticker and brokerage modes where rating dates are available. Ignored for the daily feed (always today). Set to 0 to disable.

## `includeConsensus` (type: `boolean`):

In ticker mode, attach a consensus block (average rating, price target, breakdown, trend over 1m/3m/1yr) per ticker. Adds no extra requests - data is parsed from the same page.

## `maxItems` (type: `integer`):

Hard cap on total rating rows returned across all modes. Set to 0 for no limit.

## Actor input object example

```json
{
  "market": "all",
  "actions": [],
  "daysBack": 0,
  "includeConsensus": true,
  "maxItems": 0
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset of analyst rating rows with normalized fields: ticker, exchange, company, action, brokerage, analyst, prior/new rating, prior/new price target, current price, upside pct, optional rating date, detail ID for dedup, and source URL.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("michael_b/stock-analyst-ratings").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("michael_b/stock-analyst-ratings").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call michael_b/stock-analyst-ratings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=michael_b/stock-analyst-ratings",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/9c8pcSsxcXWyplraN/builds/VwteWrrXj5Lk07m28/openapi.json
