# BetMGM Scraper - Live Sports Odds, Markets & Player Props (`abotapi/betmgm-com-odds-scraper`) Actor

Scrape BetMGM (betmgm.com) live and upcoming sports betting odds. Extract events, moneylines, spreads, totals, player props, scores and start times. Get decimal and American odds for sportsbook monitoring, market comparison and odds analysis.

- **URL**: https://apify.com/abotapi/betmgm-com-odds-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Collect live and upcoming sportsbook odds from BetMGM (betmgm.com) as clean rows: one row per market with decimal and American odds, every selection, scores and start times.

### Why This Scraper?

- **One row per market.** Each row carries the headline selection plus a full `quote` object listing every selection with decimal odds, American odds, implied probability and line.
- **Live and upcoming events.** Filter to events in play now or upcoming only, with current score, period scores and clock for live games.
- **Every market on demand.** Moneylines, spreads, totals and player props for each event when you turn on all markets.
- **Two ways in.** Search by sport, region, competition or text, or paste event links.
- **State boards.** Pick the US state whose odds board you want to read.
- **Final scores of past games.** Turn on **Final scores of past games** to also get one row per finished game with the final score, winner and venue, linked to the odds event id where the game matches one.
- **Recurring updates.** Incremental mode labels rows NEW, UPDATED or REAPPEARED and skips unchanged rows, so scheduled runs only deliver what moved.

### Use Cases

- **Odds comparison:** line up BetMGM prices against other books in a spreadsheet or model.
- **Line movement tracking:** schedule runs and keep only the rows whose odds changed.
- **Betting research:** build datasets of spreads, totals and player props across sports.
- **Live dashboards:** feed in-play scores and prices into alerts.
- **Results and model back-testing:** pair past final scores with the odds you collected.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `recordKey` | `betmgm:00000000:0000000000` |
| `eventId` | `00000000` |
| `event` | `Team A at Team B` |
| `homeTeam` / `awayTeam` | `Team B` / `Team A` |
| `homeScore` / `awayScore` | `0` / `0` |
| `sport` | `Basketball` |
| `competition` | `NBA` |
| `competitionFull` | `USA - NBA` |
| `startTime` | `2026-01-01T00:00:00.000Z` |
| `eventStatus` | `scheduled` |
| `market` | `Moneyline` |
| `canonical` | `moneyline` |
| `line` | `-3.5` |
| `isMainLine` | `true` |
| `outcome` | `Team A` |
| `odds` | `1.91` |
| `americanOdds` | `-110` |
| `lastOdds` | `1.91` |
| `quote.selections[]` | name, role, decimal, american, impliedProbability, line |
| `periods` | per-period scores |
| `stats` | event statistics |
| `url` | event page |

Odds rows carry `recordType` = `odds`; final-score rows (opt in) carry `recordType` = `result`, `recordKey` = `result:<game id>`, `homeTeam` / `awayTeam`, `homeScore` / `awayScore`, `score`, `winner` (`home`, `away` or `draw`), `venue`, `sport`, `competition`, `startTime`, `status` = `final`, `source` = `espn.com` and `eventId` (the linked odds event id, or `null` when no odds event matches).

### How to Use

1. Pick **Search events** or **Event URLs**.
2. Choose sports and filters, or paste event links.
3. Set **Max items** (default 20) and optionally turn on **Fetch all markets**.
4. Run the actor and open the dataset.

**Basketball odds, headline markets:**

```json
{ "mode": "search", "sports": ["basketball"], "maxItems": 20 }
```

**Every market of live soccer events:**

```json
{ "mode": "search", "sports": ["soccer"], "stage": "live", "fetchDetails": true, "maxItems": 100 }
```

**One event by link:**

```json
{ "mode": "url", "urls": ["https://www.betmgm.com/en/sports/events/team-a-at-team-b-00000000"] }
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("<ACTOR_ID>").call(run_input={"mode": "search", "sports": ["basketball"], "maxItems": 20})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["event"], row["market"], row["americanOdds"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('<ACTOR_ID>').call({ mode: 'search', sports: ['basketball'], maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length);
```

#### Final scores of past games

Turn on **Final scores of past games** and set **Results: days back** (1 to 14, default 2) to add one row per finished game to the dataset. Notes:

- **Source and coverage.** The sportsbook publishes no list of finished games, so final scores are read from the public scoreboard of espn.com, one request per league per day. Covered: NFL, NBA, WNBA, MLB, NHL, college football, men's and women's college basketball, MLS, NWSL and the main soccer leagues and cups (Premier League, Championship, FA Cup, League Cup, La Liga, Bundesliga, Serie A, Ligue 1, Eredivisie, Primeira Liga, Liga MX, Brasileirao, Argentine league, Champions League, Europa League, Conference League, Nations League, World Cup qualifying, friendlies, Concacaf and Copa Libertadores). Tennis, golf, boxing, cricket, Formula 1 and other sports return no final scores.
- **Finished games only.** Live games, upcoming games, and postponed or canceled games are never returned.
- **Search mode only.** The **Sports** and **Text filter** inputs narrow the results; region, competition, stage and state filters do not apply to final scores. URL mode ignores this option.
- **Linking to odds.** `eventId` is filled when a current or just-ended odds event (seen in this run or remembered by incremental mode) matches by team names and a start time within a few hours; otherwise it is `null`.
- **Max items.** About half of **Max items** is held for final scores while the odds are collected, so a small cap returns both kinds of rows. Every result row is a normal dataset item.
- **Incremental mode.** A result row is NEW the first time, UPDATED only if its status or final score changes, and never reported as EXPIRED. The results settings are not part of the remembered scope, so changing the days back does not restart the baseline.
- **If the scores cannot be read.** When every score read is refused and nothing else was collected, the run ends FAILED with a clear message instead of an empty success. When odds rows were collected, the run succeeds and the status message says the scores could not be read.

**Basketball odds plus final scores of the last three days:**

```json
{ "mode": "search", "sports": ["basketball"], "includeResults": true, "resultsDaysBack": 3, "maxItems": 40 }
```

#### Tips

- Search mode returns each event's headline markets. Turn on **Fetch all markets** for spreads, totals and player props.
- Use **Main lines only** to keep just the moneyline row of each event.
- Use the same **State key** on every scheduled run when you use incremental mode.

### Send results into your apps (MCP connectors)

Select one or more MCP connectors authorized under your Apify settings to receive a readable summary of the rows. Notion gets a page per row when you add a parent page. Your dataset is never changed by the export.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | Search events or read event links. |
| `sports` | array | `[]` | Sport names or ids. Empty = all sports. |
| `regionIds` | array | `[]` | Optional region ids. |
| `competitionIds` | array | `[]` | Optional competition ids. |
| `searchTerm` | string | empty | Keep events whose name, league or region contains this text. |
| `stage` | string | `all` | All, live only or upcoming only. |
| `state` | string | `NY` | US state odds board. |
| `mainLinesOnly` | boolean | `false` | Keep only each event's main line. |
| `urls` | array | `[]` | Event page links, links carrying `fixtureIds=`, or ids (`20101854`, `2:7913596`) for URL mode; non-event links are skipped with a warning. |
| `fetchDetails` | boolean | `false` | Load every market for each event. |
| `includeResults` | boolean | `false` | Also return final scores of finished games (search mode). |
| `resultsDaysBack` | integer | `2` | Days back for final scores, 1 to 14. |
| `maxPages` | integer | empty | Page limit for search mode. Does not cap results; Max items does. |
| `maxItems` | integer | `20` | Stop after this many rows. 0 = no limit. |
| `resumeFromRunId` | string | empty | Skip rows an earlier run already collected. |
| `incrementalMode` | boolean | `false` | Label rows NEW, UPDATED or REAPPEARED and skip unchanged rows. |
| `emitUnchanged` | boolean | `false` | Also output unchanged rows. |
| `emitExpired` | boolean | `true` | Report markets no longer offered as EXPIRED in URL mode. |
| `stateKey` | string | empty | Name of the remembered baseline. |
| `ignoreFieldsForChanges` | array | `[]` | Fields that never count as a change. |
| `proxy` | object | Datacenter proxy | Connection settings. Residential is recommended for the most reliable results. |
| `mcpConnectors` | array | `[]` | Connectors to send a summary to. |
| `notionParentPageUrl` | string | empty | Notion parent page for the export. |
| `maxNotifyListings` | integer | `50` | Cap on rows sent to each connector. |

#### Connection and proxy

The datacenter proxy is the default and works on every Apify plan. **Residential is recommended** for the most reliable results, especially for large or frequent runs: choose it in the Proxy configuration field. Leave the field empty and the actor still uses the datacenter proxy.

If a connection is refused or keeps failing, the actor switches to a more robust one on its own. Residential traffic used by that automatic switch is billed to your Apify plan. Apify free plans do not include residential proxies: on a free plan the run logs a warning and continues on a backup connection, and the run fails with a clear message if that connection cannot load the pages either. To use your own proxies, enter their URLs in the Proxy configuration field.

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "recordKey": "betmgm:00000000:0000000000",
  "eventId": "00000000",
  "event": "Team A at Team B",
  "homeTeam": "Team B",
  "awayTeam": "Team A",
  "sport": "Basketball",
  "competition": "NBA",
  "startTime": "2026-01-01T00:00:00.000Z",
  "eventStatus": "scheduled",
  "market": "Moneyline",
  "canonical": "moneyline",
  "isMainLine": true,
  "outcome": "Team A",
  "odds": 1.91,
  "americanOdds": -110,
  "quote": {
    "selections": [
      {
        "name": "Team A",
        "role": "away",
        "decimal": 1.91,
        "american": -110,
        "impliedProbability": 0.5236,
        "line": null
      },
      {
        "name": "Team B",
        "role": "home",
        "decimal": 1.91,
        "american": -110,
        "impliedProbability": 0.5236,
        "line": null
      }
    ]
  }
}
```

### Plan Requirement

The default connection is Apify Proxy, and it needs a plan that includes proxy access. If your Apify plan does not include proxy access, the run fails with a clear message instead of returning an empty result; a paid plan with proxy access is required. The actor switches connection automatically when one is refused and tells you in the run log what it used.

### FAQ

#### How much does a run cost?

You pay per row written to the dataset (odds rows and final-score rows alike) plus a small start fee. Set Max items to bound the spend.

#### Is it legal to scrape BetMGM (betmgm.com)?

The actor reads publicly listed odds. Check the site's terms and the rules that apply to you before using the data.

#### Can I schedule it?

Yes. Use incremental mode with a fixed State key so each run delivers only changed rows.

#### Why did my run return nothing?

The filters may match no events right now, for example Live only when no game is in play.

#### Can I use it with AI tools?

Yes. Connect the dataset through the Apify MCP server or send rows to your apps with the connectors above.

### 🔗 Want more sports data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>⚽ <a href="https://apify.com/abotapi/fanduel-odds-scraper"><b>FanDuel Odds Scraper</b></a><br>Scrape FanDuel sportsbook odds, including moneylines, spreads, totals, player props and...</td><td>⚽ <a href="https://apify.com/abotapi/draftkings-sportsbook-odds-scraper"><b>DraftKings Scraper</b></a><br>Scrape DraftKings Sportsbook odds, game lines, team totals, player and game props...</td></tr>
<tr><td>⚽ <a href="https://apify.com/abotapi/bet365-odds-scraper"><b>bet365 Odds Scraper</b></a><br>Scrape bet365 live in-play and pre-match odds across 20+ sports. Extract fixture...</td><td>⚽ <a href="https://apify.com/abotapi/sportsbook-odds-scraper"><b>Sportsbook Odds Scraper (1xBet, Melbet, Linebet, Paripulse)</b></a><br>Collect live and prematch betting odds from 1xBet, Melbet, Linebet and Paripulse. Give it...</td></tr>
<tr><td>⚽ <a href="https://apify.com/abotapi/sofascore-scraper"><b>SofaScore Scraper ⚽ Live Scores, Stats, Players &amp; Odds</b></a><br>Pull structured sports data from SofaScore across football, basketball, tennis, and 20+...</td><td>⚽ <a href="https://apify.com/abotapi/sportsbet-au-scraper"><b>Sportsbet AU Scraper 🏅 Sports Odds &amp; Racing Prices</b></a><br>Scrape sportsbet.com.au odds: events, markets, selections and prices for AFL, NRL...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/betmgm-com-odds-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Search lists events by sport and filters. URL mode reads the event pages you paste below.

## `sports` (type: `array`):

Sport names (basketball, soccer, football or american football, hockey or ice hockey, baseball, tennis, golf, cricket, boxing, formula 1) or numeric sport ids. Leave empty for every sport.

## `regionIds` (type: `array`):

Optional numeric region ids (for example 9 = USA). Empty = all regions. To filter by a region or league name instead, use the Text filter.

## `competitionIds` (type: `array`):

Optional numeric competition or league ids (for example 6004 = NBA group). Empty = all competitions. To filter by a league name instead, use the Text filter.

## `searchTerm` (type: `string`):

Keep only events whose name, league or region contains this text (for example Knicks).

## `stage` (type: `string`):

All events, only events in play now, or only upcoming events.

## `state` (type: `string`):

US state whose odds board is read. Prices can differ slightly between states.

## `mainLinesOnly` (type: `boolean`):

Keep only each event's main line (moneyline). Off returns every market returned.

## `urls` (type: `array`):

Event page links (…/sports/events/<teams>-<id>), links carrying fixtureIds=<id>, or bare event ids such as 20101854 or 2:7913596. Every market of each event is returned. Links that are not events (home page, sport or league pages) are skipped with a warning. Leave empty to read the next open event. Search filters are ignored in URL mode.

## `fetchDetails` (type: `boolean`):

Search mode returns each event's headline markets. Turn on to load every market (spreads, totals, player props) for each event; one extra request per event. URL mode always returns every market.

## `includeResults` (type: `boolean`):

Also return the final score of every finished game in the selected sports (search mode only): home and away team, final score, winner, venue and the odds event id where it can be linked. One row per finished game; games not yet over, postponed or canceled are never returned. Covered: NFL, NBA, WNBA, MLB, NHL, college football and basketball, and the major soccer leagues and cups; other sports return no final scores. Final scores come from espn.com, not from the sportsbook. Each result row is one dataset item.

## `resultsDaysBack` (type: `integer`):

How many days back to collect final scores (1 to 14). Only used when Final scores of past games is on.

## `maxPages` (type: `integer`):

Optional page limit for search mode. Empty or 0 = unlimited. This does not cap results by default; it defers to Max items (default 20), which is the only default cap.

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

Stop after this many odds rows (one row per market). This is the only default cap (20); Max pages does not cap and defers to it. 0 = no limit.

## `proxy` (type: `object`):

Datacenter proxy is the default and works on every plan. Residential is recommended for the most reliable results, especially for large or frequent runs. If a connection is refused or keeps failing, the actor switches to a more robust one automatically.

## `resumeFromRunId` (type: `string`):

Run ID of an earlier run of this actor. Rows it already collected are not collected or charged again.

## `incrementalMode` (type: `boolean`):

Remember each odds row between runs and label every row NEW, UPDATED or REAPPEARED. Unchanged rows are skipped and not charged. In URL mode, a market that is no longer offered is reported once as EXPIRED. Search results are a bounded window, so they never report EXPIRED.

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

Incremental mode only. Output odds rows that did not change as UNCHANGED rows (each is a charged row).

## `emitExpired` (type: `boolean`):

Incremental mode only. In URL mode, report a market that is no longer offered as EXPIRED (a charged row).

## `stateKey` (type: `string`):

Name of the remembered baseline. Leave empty to derive it from your sports, filters and links. Use the same key on every scheduled run.

## `ignoreFieldsForChanges` (type: `array`):

Field names (dotted paths allowed, e.g. quote.line) that never count as a change. scrapedAt is always ignored.

## `mcpConnectors` (type: `array`):

Optionally send a readable summary of each odds row into the apps you already use via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify Settings, then select it here. Notion gets a page per odds row; other connectors receive a best-effort write. Leave empty to skip. The dataset is never changed.

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which event pages are created. Required for the Notion export, ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on events written to each selected connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "sports": [
    "basketball"
  ],
  "regionIds": [],
  "competitionIds": [],
  "stage": "all",
  "state": "NY",
  "mainLinesOnly": false,
  "urls": [
    "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741",
    "https://www.betmgm.com/en/sports/events/bahrain-yemen-2:7913596",
    "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741?fixtureIds=20050741",
    "20101854",
    "2:7913596"
  ],
  "fetchDetails": false,
  "includeResults": false,
  "resultsDaysBack": 2,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": true,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

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

No description

# 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 = {
    "mode": "search",
    "sports": [
        "basketball"
    ],
    "urls": [
        "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741",
        "https://www.betmgm.com/en/sports/events/bahrain-yemen-2:7913596",
        "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741?fixtureIds=20050741",
        "20101854",
        "2:7913596"
    ],
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/betmgm-com-odds-scraper").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 = {
    "mode": "search",
    "sports": ["basketball"],
    "urls": [
        "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741",
        "https://www.betmgm.com/en/sports/events/bahrain-yemen-2:7913596",
        "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741?fixtureIds=20050741",
        "20101854",
        "2:7913596",
    ],
    "maxItems": 20,
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/betmgm-com-odds-scraper").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 '{
  "mode": "search",
  "sports": [
    "basketball"
  ],
  "urls": [
    "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741",
    "https://www.betmgm.com/en/sports/events/bahrain-yemen-2:7913596",
    "https://www.betmgm.com/en/sports/events/utah-jazz-at-houston-rockets-20050741?fixtureIds=20050741",
    "20101854",
    "2:7913596"
  ],
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/betmgm-com-odds-scraper --silent --output-dataset

```

## MCP server setup

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

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/w8ubc7e9h29kPdKCs/builds/KEejAfLiJwBhqP4zB/openapi.json
