# Betting Odds Movement Tracker & Line Alert (`ichigowa/odds-movement-tracker`) Actor

Track sportsbook line movement between runs: spreads, totals, and moneylines diffed against the previous snapshot. Outputs per-line deltas, new-line flags, and movement alerts for NFL, NBA, MLB, NHL. Run on a schedule to catch moves. Unofficial; not affiliated with ESPN.

- **URL**: https://apify.com/ichigowa/odds-movement-tracker.md
- **Developed by:** [kyle herman](https://apify.com/ichigowa) (community)
- **Categories:** Sports, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.20 / 1,000 odds movement rows

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

## Betting Odds Movement Tracker & Line Alert

**Odds movement API / line movement tracker** for sportsbook lines. Each run fetches
current betting odds (spread, moneyline, over/under) from public ESPN endpoints for
MLB, NFL, NBA, NHL, WNBA, NCAAF, NCAAB, EPL and MLS, diffs them against the previous
run's snapshot, and outputs one row per line with **movement deltas**, an `is_new`
flag, and a `moved` flag — perfect for **betting line alerts**.

Run it on a schedule (e.g. **every 15 minutes**) and pair it with
[Apify webhooks](https://docs.apify.com/platform/integrations/webhooks) or the
dataset API to catch line moves as they happen.

> Odds are informational only — **not betting advice**. Unofficial; not affiliated
> with ESPN or any sportsbook.

### How it works

1. Fetches the current odds slate for your selected leagues.
2. Loads the previous snapshot from a named key-value store
   (`odds-movement-snapshots`) — persisted across runs.
3. Diffs each line (keyed by `league:event_id:provider`) and computes deltas for
   spread, over/under and both moneylines.
4. Saves the fresh snapshot (full replace, so finished/absent events are pruned
   automatically).

The **first run seeds the baseline** — all rows come back with `is_new: true` and
null deltas. From the second run onward you get real movement data.

### Input

All fields are optional; an empty input `{}` works out of the box.

| Field | Type | Default | Description |
|---|---|---|---|
| `leagues` | array | `["mlb","nfl","nba","nhl"]` | League codes: `mlb`, `nfl`, `nba`, `nhl`, `wnba`, `ncaaf`, `ncaab`, `epl`, `mls` |
| `minSpreadMove` | number | `0` | Minimum absolute spread change (points) for a spread-only move to be flagged `moved`. `0` reports all moves. |
| `emitUnchanged` | boolean | `true` | When `true`, every current line is pushed each run (deltas of 0 for unchanged). When `false`, only moved/new rows are pushed — ideal for alert pipelines. |
| `includeAllProviders` | boolean | `false` | When `true`, enumerates every bookmaker's line per event (slower). Otherwise uses fast scoreboard consensus odds. |

```json
{
    "leagues": ["nfl", "mlb"],
    "minSpreadMove": 0.5,
    "emitUnchanged": false
}
```

### Output

One row per current event-provider line (real example from a run):

```json
{
    "league": "nfl",
    "event_id": "401873271",
    "event_name": "Carolina Panthers at Arizona Cardinals",
    "start_time_utc": "2026-08-07T00:00:00Z",
    "home_team": "Arizona Cardinals",
    "away_team": "Carolina Panthers",
    "provider": "DraftKings",
    "spread": 1.5,
    "spread_details": "CAR -1.5",
    "over_under": 35.5,
    "home_moneyline": -102,
    "away_moneyline": -118,
    "fetched_at": "2026-08-06T13:14:42Z",
    "prev_spread": 1.5,
    "spread_delta": 0.0,
    "prev_over_under": 35.5,
    "over_under_delta": 0.0,
    "prev_home_moneyline": -102,
    "home_moneyline_delta": 0,
    "prev_away_moneyline": -118,
    "away_moneyline_delta": 0,
    "is_new": false,
    "moved": false,
    "snapshot_age_minutes": 0.33
}
```

Field notes:

- `spread` is relative to the home team; `spread_details` is the human-readable
  line (e.g. `"CAR -1.5"`).
- `prev_*` and `*_delta` fields are `null` on the first sighting of a line
  (`is_new: true`) or when either side is missing a value.
- `moved` is `true` when any non-null delta is non-zero (subject to
  `minSpreadMove` for spread-only moves).
- `snapshot_age_minutes` tells you how stale the comparison baseline is.

### Alerting recipe

1. Schedule the Actor every 15 minutes.
2. Set `emitUnchanged: false` and `minSpreadMove: 0.5`.
3. Attach a webhook on run-succeeded that reads the default dataset — every item
   is a line that just moved (or a brand-new line).

### Notes & limits

- In the offseason or early in the day, ESPN may not have attached odds to
  events yet; the Actor logs this and exits cleanly without touching the
  snapshot. (`emitUnchanged: true`, the default, ensures rows are produced
  whenever any odds exist.)
- Data source: ESPN's public scoreboard and odds JSON APIs. Coverage and
  bookmakers depend on what ESPN exposes (often DraftKings consensus).
- Snapshot lives in the named key-value store `odds-movement-snapshots` under
  key `snapshot` and is fully replaced each run.

# Actor input Schema

## `leagues` (type: `array`):

League codes to track. Supported: mlb, nfl, nba, nhl, wnba, ncaaf, ncaab, epl (English Premier League), mls (Major League Soccer).

## `minSpreadMove` (type: `number`):

Minimum absolute spread change (in points) for a spread-only move to be flagged as 'moved'. 0 reports all moves. Moneyline and over/under changes always count as moves.

## `emitUnchanged` (type: `boolean`):

When true (default), every current line is pushed each run, including unchanged ones (deltas of 0). When false, only rows that moved or are new are pushed — useful for alert pipelines, but may yield an empty dataset on quiet runs.

## `includeAllProviders` (type: `boolean`):

When true, queries ESPN's per-event odds endpoint to enumerate every available bookmaker's line (slower, one extra request per event). When false, uses the fast scoreboard-embedded consensus odds (usually one provider per event).

## Actor input object example

```json
{
  "leagues": [
    "mlb",
    "nfl",
    "nba",
    "nhl"
  ],
  "minSpreadMove": 0,
  "emitUnchanged": true,
  "includeAllProviders": false
}
```

# 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 = {
    "leagues": [
        "mlb",
        "nfl",
        "nba",
        "nhl"
    ],
    "minSpreadMove": 0,
    "emitUnchanged": true,
    "includeAllProviders": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("ichigowa/odds-movement-tracker").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 = {
    "leagues": [
        "mlb",
        "nfl",
        "nba",
        "nhl",
    ],
    "minSpreadMove": 0,
    "emitUnchanged": True,
    "includeAllProviders": False,
}

# Run the Actor and wait for it to finish
run = client.actor("ichigowa/odds-movement-tracker").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "leagues": [
    "mlb",
    "nfl",
    "nba",
    "nhl"
  ],
  "minSpreadMove": 0,
  "emitUnchanged": true,
  "includeAllProviders": false
}' |
apify call ichigowa/odds-movement-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ichigowa/odds-movement-tracker"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/SYL2PDSZGntYY9ODT/builds/RDZTp2n2WYrds1l9w/openapi.json
