# Pinnacle Sportsbook Odds Scraper (`parseforge/pinnacle-odds-scraper`) Actor

Scrape Pinnacle odds for any sport, league, or match: moneyline, spread, total, team totals, alternate lines, period markets and props, with bet limits, implied probability, bookmaker margin and no-vig fair odds on every row. No login, no API key, no proxy.

- **URL**: https://apify.com/parseforge/pinnacle-odds-scraper.md
- **Developed by:** [ParseForge](https://apify.com/parseforge) (community)
- **Categories:** News
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.23 / 1,000 matches

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?

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

[![ParseForge](https://raw.githubusercontent.com/ParseForge/apify-assets/main/banner.jpg)](https://apify.com/parseforge?fpr=vmoqkp)

### Pinnacle Odds Scraper - Sportsbook API with No-Vig Fair Odds

**Scrape Pinnacle sportsbook odds for any sport, league, or match, pre-match and in-play.** Every game comes back as one row with the moneyline, spread and total, the bet limit Pinnacle attaches to each market, the implied probability of every price, the bookmaker margin, and the no-vig fair odds left once that margin is divided out. No login, no API key, no proxy. Export to CSV, JSON, Excel, or XML.

Pinnacle is the reference book: low margin, high limits, and lines the rest of the market follows. This reads its public odds feed directly, filtered by sport, league, country, team, start time, price, margin, or bet limit, and returns each match in one fixed schema.

| Who uses it | What they scrape Pinnacle for |
|---|---|
| Sports bettors | Sharp lines to compare against a soft book before placing a bet |
| Quant and model builders | No-vig probabilities as the training target and the benchmark |
| Arbitrage and value hunters | A low-margin reference to price every other book against |
| Odds comparison sites | A complete, keyless board across 20+ sports on a schedule |
| Traders and researchers | Line movement, bet limits, and margin as a market-efficiency signal |

### What it does

This Actor collects Pinnacle matchups by sport, league, matchup id, or URL, and returns each one as a flat row. Every match carries:

- 🧾 **Core fields:** sport, league, country group, both teams, rotation number, kickoff time, cutoff time, and live status.
- 💰 **Main markets:** moneyline (including the draw), spread with its handicap, and total with its line, each in American and decimal odds.
- 🎯 **The no-vig layer:** implied probability per price, bookmaker margin per market, and the fair probability and fair decimal price with the margin removed.
- 🛡️ **Bet limits:** the maximum stake Pinnacle accepts on each market, which is its own confidence signal.
- 🔴 **Live state:** score, minute, red and yellow cards, corners, and score by period on in-play games.
- ⏱️ **Freshness, stated:** `boardAgeSeconds`, `boardVersion`, and `boardSource` on every row, so you always know how old the price you got is.

Results export to CSV, JSON, Excel, or XML, or stream from the API.

#### How fresh the data is

Pinnacle serves its public board through a CDN with roughly a **15-minute** cache. That is the same board pinnacle.com shows an anonymous visitor, and no public surface is fresher — the site itself takes live price updates over a websocket that needs an account.

This Actor does not pretend otherwise. Every row carries `boardAgeSeconds` straight from the CDN, so you can see whether a price is 10 seconds or 12 minutes old, and `boardVersion`, which rises each time Pinnacle reprices. There is no point scheduling a run more often than about every 15 minutes; you would pay for the same board twice.

For games in play there is one thing worth doing, and the `includeLiveOdds` option does it: the league page and the sport's live feed are cached independently, and the live feed is consistently the newer copy. Measured on eight in-play games, it won eight times out of eight. Turn it on and in-play rows are rebuilt from whichever board version is higher, at two requests per sport rather than per match.

### What you can do with Pinnacle odds

**📊 Price every other book against a sharp line.**

Pull a league's board, read `homeWinProbability`, `drawProbability` and `awayWinProbability`, and compare them to the price your book is offering. The gap is your edge.

**🧮 Skip the vig calculator.**

`moneylineMarginPercent` is the overround on every market, and `fairDecimal` is the price without it. Both are computed per market, so you never convert American odds by hand again.

**📈 Watch the line move.**

Turn on line movement and the Actor takes a second snapshot after a hold, then reports every price and handicap that changed, with direction and probability delta. The hold has to outlast the 15-minute board cache to see anything, so the window defaults to 900 seconds. If the second snapshot comes back as the same board version, the row says `boardRefreshed: false` and the event is not billed.

**🔍 Find value and arbitrage candidates.**

Filter by maximum margin and minimum bet limit to keep only the markets Pinnacle is most confident in, then compare those fair prices to a softer book.

**🤖 Feed a model.**

Schedule a run per league and build a time series of prices, limits, and fair probabilities without touching an official API key.

### Why choose this scraper

| | What you get |
|---|---|
| **Fair odds on every row** | Implied probability, margin, and no-vig fair price, computed per market. Not an add-on you pay extra for. |
| **Four inputs, one run** | Sports, leagues, matchup ids, and Pinnacle URLs together. Every source returns the same fields. |
| **Filters that cut noise** | Country, league, team, start window, price band, maximum margin, and minimum bet limit. Only matching rows are written and billed. |
| **Bet limits included** | The maximum stake per market, on the main row, at no extra event. |
| **Freshness you can audit** | `boardAgeSeconds` and `boardVersion` on every row. Nobody else tells you how old the price is. |
| **Line movement built in** | Two snapshots in one run, with every moved price and handicap reported, and no charge when the board did not reload. |
| **Optional depth, priced separately** | Alternate lines, period markets, team totals, and the full prop book are opt-in blocks, off by default. |
| **All six odds formats** | American, decimal, fractional, Hong Kong, Indonesian, and Malay. |
| **No key, no proxy** | Reads Pinnacle's public guest feed. 948 matches in 56 seconds in our own measurement. |

### How it compares

The other Pinnacle scrapers return the board. This one returns the board plus the analytics layer bettors actually work in, and does not charge extra for it. The comparison below uses each Actor's own published pricing events and description.

| Feature | ParseForge | zhorex | superapis | scrapemint |
|---|---|---|---|---|
| No-vig fair odds | Included on every row | Charged as a $0.05 enrichment | No | No |
| Price per game | $0.007 | $0.01 pre-match, $0.025 live | $0.001 per request | $0.004 |
| Bookmaker margin per market | Yes | Not published | No | No |
| Bet limits (max stake) | Yes | Not published | Not published | No |
| Line movement between snapshots | Yes | No | No | No |
| Board age and version per row | Yes | No | No | No |
| Alternate lines, periods, team totals | Opt-in blocks | Props at $0.05 each | Yes | No |
| Source | Pinnacle | Pinnacle | Pinnacle | Mixed public feed |

### What a Pinnacle match looks like

Every match returns as one flat JSON row. Here is a real record, unedited apart from trimmed price arrays:

```json
{
  "matchupId": 1634624929,
  "rotation": 12208,
  "matchupType": "matchup",
  "units": "Regular",
  "sportId": 29,
  "sportName": "Soccer",
  "leagueId": 1980,
  "leagueName": "England - Premier League",
  "leagueGroup": "England",
  "homeTeam": "Arsenal",
  "awayTeam": "Chelsea",
  "startTime": "2026-09-06T15:30:00Z",
  "startsInHours": 286.73,
  "cutoffAt": "2026-09-06T15:30:00Z",
  "status": "pending",
  "periodStatus": "open",
  "isLive": false,
  "hasAltSpread": true,
  "hasAltTotal": true,
  "parlayRestriction": "unique_matchups",
  "totalMarketCount": 10,
  "primaryPeriod": 0,
  "primaryPeriodLabel": "Match",
  "moneyline": {
    "homeAmerican": -147,
    "homeDecimal": 1.6803,
    "drawAmerican": 260,
    "drawDecimal": 3.6,
    "awayAmerican": 453,
    "awayDecimal": 5.53,
    "maxStake": 500,
    "status": "open",
    "marginPercent": 5.375,
    "selections": [
      { "label": "Arsenal", "american": -147, "decimal": 1.6803, "impliedProbability": 0.59514, "fairProbability": 0.56478, "fairDecimal": 1.7706 },
      { "label": "Chelsea", "american": 453, "decimal": 5.53, "impliedProbability": 0.18083, "fairProbability": 0.17161, "fairDecimal": 5.8272 },
      { "label": "Draw", "american": 260, "decimal": 3.6, "impliedProbability": 0.27778, "fairProbability": 0.26361, "fairDecimal": 3.7935 }
    ]
  },
  "spread": { "points": -0.75, "homeAmerican": -113, "awayAmerican": -103, "maxStake": 500, "marginPercent": 3.791 },
  "total": { "points": 2.5, "overAmerican": -115, "underAmerican": -103, "maxStake": 500, "marginPercent": 4.227 },
  "homeWinProbability": 0.56478,
  "drawProbability": 0.26361,
  "awayWinProbability": 0.17161,
  "homeFairDecimal": 1.7706,
  "awayFairDecimal": 5.8272,
  "moneylineMarginPercent": 5.375,
  "favorite": "Arsenal",
  "maxStake": 500,
  "marketCount": 36,
  "boardVersion": 3703039206,
  "boardAgeSeconds": 630,
  "boardSource": "league-board",
  "url": "https://www.pinnacle.com/en/soccer/england-premier-league/arsenal-vs-chelsea/1634624929/",
  "scrapedAt": "2026-08-25T16:46:13.998Z"
}
```

`drawProbability` is `null` in two-way sports. `spread` and `total` are `null` on sports Pinnacle prices moneyline-only, such as golf, boxing, and cycling. That is the market's real state, not a gap in the data.

Live games add a `liveState` block with the score, the minute, cards, corners, and the score by period. In-play margins run wider than pre-match ones — around 8-11% against 2-5% — which is Pinnacle's real pricing, not an artefact.

`boardAgeSeconds` is how many seconds that board had been sitting in Pinnacle's CDN when it was read, and `boardVersion` is Pinnacle's own revision counter for those markets. A fixture that has kicked off is published as several books at once — pre-match, the normal in-play book, and a restricted `danger_zone` book — and the Actor keeps the one actually on offer, so a fixture is one row. `liveMode` tells you which book you got.

### Configure the run

Drive the Actor from four source types, alone or together: sports, leagues, matchup ids, and Pinnacle URLs. Filters run as each match is read, so only matches reach your dataset. Filters also run **before** the optional blocks are projected out, so you can filter on data you did not buy — screening by margin never charges the alternate-lines event. The Input tab lists every parameter.

Today's and tonight's soccer, sharp markets only:

```json
{ "sports": ["Soccer"], "startsWithinHours": 24, "maxMarginPercent": 3, "maxItems": 200 }
```

One league's full board with every optional block:

```json
{ "leagues": ["England - Premier League"], "includeAlternateLines": true, "includePeriodMarkets": true, "includeTeamTotals": true, "includeProps": true }
```

Every live game across two sports, from Pinnacle's live feed:

```json
{ "sports": ["Soccer", "Tennis"], "onlyLive": true, "maxItems": 100 }
```

A whole country's board, with in-play games taken from the fresher live feed:

```json
{ "sports": ["Soccer"], "leagueGroups": ["England"], "includeLiveOdds": true, "maxItems": 200 }
```

Watch a handful of games move across a full board refresh:

```json
{ "leagues": ["1980"], "trackLineMovement": true, "lineMovementDelaySecs": 900, "maxItems": 20 }
```

Only high-limit markets in one country:

```json
{ "sports": ["Soccer"], "leagueGroups": ["England"], "minMaxStake": 1000, "oddsFormats": ["american", "decimal", "fractional"] }
```

### Pricing

Pay-per-event: **$0.007 per match**, dropping to $0.00623 at higher volume, plus $0.006 per board resolved — one board is a league, or a sport's live feed — and a $0.054 run-start fee. You pay only for matches written to your dataset.

| Matches collected | Boards read | Approximate cost |
|---|---|---|
| 20 (one league) | 1 | $0.20 |
| 100 (top leagues) | 8 | $0.80 |
| 1,000 (all of soccer) | 165 | $8.04 |
| 10,000 (many sports) | ~800 | $74.85 |

The board fee is the fixed cost of paging a competition, shared across the matches it yields, which keeps the per-match price clean. Name the leagues you want and it almost disappears; sweep a whole sport and you pay it once per league.

The optional blocks are billed only when they are delivered, once per match: alternate lines $0.003, period markets $0.003, team totals $0.002, props $0.008, live-feed cross-check $0.004, line movement $0.010. All of them are off by default, so a plain run is the price above and nothing else. The last two only bill when they actually did something — the cross-check when it found a newer board, line movement when the board reloaded between snapshots.

At $0.007 a match this sits above the cheapest generic odds scrapers, which charge $0.003 to $0.005 for a single line. A row here is a whole match — every main market, in six odds formats, with the margin and the fair price already computed. The closest Pinnacle-specific alternative charges $0.01 for a pre-match snapshot and another $0.05 to add fair odds. New Apify accounts start with $5 in free credit.

### Free users

Free-plan runs return up to 10 matches as a preview. [Upgrade your Apify plan](https://console.apify.com/sign-up?fpr=vmoqkp) to collect the full board.

### Run it

1. [Create a free Apify account with $5 in credit](https://console.apify.com/sign-up?fpr=vmoqkp).
2. Open the [Pinnacle Sportsbook Odds Scraper](https://apify.com/parseforge/pinnacle-odds-scraper?fpr=vmoqkp).
3. Enter `sports`, `leagues`, `matchupIds`, or `startUrls`, add any filters, tick the optional blocks you want, and click **Start**.
4. Export the results as CSV, Excel, JSON, or XML from the **Dataset** tab.

Run it programmatically through the [Apify API](https://docs.apify.com/api/v2) or the [ApifyClient](https://docs.apify.com/api/client/js) for JavaScript and Python. Schedule it every 15-30 minutes to build a price history — the public board does not refresh faster than that.

### Use with AI agents (MCP)

Give an AI agent live access to Pinnacle odds through the Model Context Protocol. Add the Actor to Claude, Cursor, or any MCP client:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=parseforge/pinnacle-odds-scraper"
```

Then prompt it in plain language:

- *"Pull today's Premier League odds from Pinnacle and list the games by no-vig home win probability."*
- *"Which live soccer games have a moneyline margin under 6% right now?"*
- *"Track the next hour of line movement on tonight's NBA board and tell me what moved most."*

Copy this into ChatGPT, Claude, or Cursor to start:

```
Use the Apify Actor "parseforge/pinnacle-odds-scraper" to collect Pinnacle sportsbook odds. Input: { "sports": ["<name>"], "leagues": ["<name or id>"], "onlyLive": <true|false>, "maxItems": <n> }. It returns homeTeam, awayTeam, league, startTime, moneyline, spread, total, maxStake, moneylineMarginPercent, and no-vig homeWinProbability / drawProbability / awayWinProbability per match. Call it with the ApifyClient and my APIFY_TOKEN.
```

### Troubleshooting

**Why am I getting no results?**

The sport or league name may be misspelled, or the league may have no open markets right now. Out-of-season leagues such as the NBA in August list futures only and return no games. Widen `startsWithinHours`, or drop `maxMarginPercent` and `minMaxStake`, which are strict filters.

**Why fewer matches than I asked for?**

A league only has as many fixtures as it has. `maxLeaguesPerSport` and `maxMatchupsPerLeague` also cap the run before `maxItems` does.

**Why is a field empty?**

`spread` and `total` are `null` on moneyline-only sports such as golf, boxing, MMA, and cycling. `drawProbability` is `null` wherever there is no draw. `liveState` appears only on in-play games. That is the market's real state.

**Why did my props block come back empty?**

Pinnacle publishes the prop book close to kickoff. A fixture a week out often has none, and `propCount` is `0`. Nothing is billed for a block that was not delivered.

**Why does my line-movement run report nothing moved?**

The window was shorter than Pinnacle's board cache, so both snapshots were the same board. The row says `boardRefreshed: false` and the event is not billed. Raise `lineMovementDelaySecs` to 900 or more.

**Why is `boardAgeSeconds` several hundred?**

That is the CDN cache doing its job, and it is the same board pinnacle.com serves an anonymous visitor. Turn on `includeLiveOdds` to pull in-play games from the fresher live feed, and do not schedule runs more often than about every 15 minutes.

**Why is the run slow?**

Reading a whole sport walks every league that has markets — soccer alone is over 150 of them. Set `maxLeaguesPerSport`, or name the leagues you want. Naming a league by text with no sport given scans the league list of every active sport first; pass `sports` alongside it to skip that.

**Why is my line-movement run capped at 500 rows?**

Rows are held in memory until the second snapshot, so that path is capped to stay inside the run's memory. Split a larger job into several runs.

**A field stopped filling.**

Pinnacle changed its public response shape. Email us with your run ID so we can update the parser.

### FAQ

| Question | Answer |
|---|---|
| Do I need a Pinnacle account or API key? | No. It reads Pinnacle's public guest feed, so there is no account, key, or proxy to manage. |
| Which sports does it cover? | Every sport Pinnacle currently prices — around 20 with open markets, including soccer, tennis, basketball, football, baseball, hockey, MMA, esports, golf, and cycling. |
| Does it return live in-play odds? | Yes. `onlyLive` reads Pinnacle's live feed directly, and `includeLiveOdds` pulls in-play games in a league run from that same fresher feed. |
| How fresh are the prices? | Pinnacle's public board is CDN-cached for about 15 minutes, and every row states its own `boardAgeSeconds`. No public surface is fresher; the site takes live updates over an account-only websocket. |
| What exactly are the fair odds? | The implied probabilities of a market rescaled so they sum to 100%, which removes the bookmaker margin. It is the standard multiplicative no-vig method, and an estimate, not a Pinnacle field. |
| Can I get alternate lines and player props? | Yes, as opt-in blocks. Both are off by default and billed only when delivered. |
| Does it track closing line value? | It captures line movement inside one run. For CLV, schedule a run every 15-30 minutes and compare the last board before kickoff to your entry price. |
| Can I filter by margin or bet limit? | Yes. `maxMarginPercent` keeps only sharp markets and `minMaxStake` keeps only liquid ones. Only matches that pass are written and billed. |
| How many matches per run? | Free plan: 10. Paid: the whole board, bounded by what Pinnacle currently prices. |
| Is this an official Pinnacle product? | No. It is unofficial and reads only publicly available Pinnacle data. |

### Related actors

- [FanDuel Sportsbook Odds Scraper](https://apify.com/parseforge/fanduel-scraper?fpr=vmoqkp): FanDuel moneylines, spreads, totals, and futures.
- [TheSportsDB Scraper](https://apify.com/parseforge/thesportsdb-scraper?fpr=vmoqkp): teams, players, fixtures, and results across leagues.
- [TennisExplorer Match Results Scraper](https://apify.com/parseforge/tennisexplorer-scraper?fpr=vmoqkp): tennis results and head-to-head history.
- [The Odds API Sports Scraper](https://apify.com/parseforge/the-odds-api-sports-scraper?fpr=vmoqkp): multi-book odds through The Odds API.
- [Reddit Posts Scraper](https://apify.com/parseforge/reddit-posts-scraper?fpr=vmoqkp): posts from any subreddit, search, or URL.

Browse the full [ParseForge collection](https://apify.com/parseforge?fpr=vmoqkp) for more scrapers.

🆘 **Need help?** Email parseforge@protonmail.com with your run ID, your input, and what you expected.

⚠️ **Disclaimer.** This Actor is unofficial and is not affiliated with, endorsed by, or sponsored by Pinnacle. It collects only publicly available odds data. Gambling carries financial risk and is restricted or illegal in some jurisdictions; you are responsible for using this data in compliance with Pinnacle's terms and the laws that apply to you. Nothing here is betting advice.

# Actor input Schema

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

Pinnacle sports to read, by name or numeric id. Every league of the sport that currently has markets is read, most featured first.

Examples:

- Soccer
- Tennis
- Basketball
- 29

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

League ids, or a piece of a league name. Names are matched against every sport listed above, or across all active sports when no sport is given.

Examples:

- England - Premier League
- NBA
- 1980

## `matchupIds` (type: `array`):

Individual Pinnacle matchup (event) ids. Use these to re-read the same games on a schedule.

Example: 1634440437

## `startUrls` (type: `array`):

Pinnacle match or league pages. A match page ends in the matchup id; a league page is resolved by its slug.

Examples:

- https://www.pinnacle.com/en/soccer/england-premier-league/matchups/
- https://www.pinnacle.com/en/soccer/england-premier-league/sunderland-vs-fulham/1634440437/

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

Total matchups to write across every source in this run. Free Apify plans return a 10-row preview.

## `maxLeaguesPerSport` (type: `integer`):

Cap how many leagues are read from each sport, so one busy sport such as Soccer cannot use up the whole run. Leave empty to read every league with markets.

## `maxMatchupsPerLeague` (type: `integer`):

Cap how many matchups are taken from each league. Leave empty to let Maximum rows decide.

## `onlyLive` (type: `boolean`):

Keep only games that are in play. With sports as the only source this switches to Pinnacle's live feed, which returns every live game of a sport in two requests instead of walking its leagues.

## `onlyPrematch` (type: `boolean`):

Drop games that are already in play.

## `includeSpecialsAsRows` (type: `boolean`):

Also write prop and futures markets (Correct Score, Both Teams To Score, Regular Season Wins, Goalscorer) as separate rows. Off by default, so a row is one game.

## `leagueGroups` (type: `array`):

Keep only leagues whose group matches one of these, case-insensitive. Pinnacle groups leagues by country or confederation.

Examples:

- England
- Spain
- UEFA

## `leagueNameContains` (type: `array`):

Keep only leagues whose name contains one of these, case-insensitive.

Example: Premier

## `teamNameContains` (type: `array`):

Keep only matchups where a participant name (or a special's description) contains one of these, case-insensitive.

Examples:

- Arsenal
- Djokovic

## `startsWithinHours` (type: `integer`):

Keep only matchups starting inside this many hours from now. 24 gives you today and tonight; 168 gives you the week.

## `startsAfter` (type: `string`):

Keep only matchups starting on or after this moment (YYYY-MM-DD or a full ISO timestamp).

## `startsBefore` (type: `string`):

Keep only matchups starting on or before this moment (YYYY-MM-DD or a full ISO timestamp).

## `minMoneylinePrice` (type: `integer`):

American odds floor. A matchup is kept when at least one moneyline side sits inside the band. -200 keeps out heavy favourites.

## `maxMoneylinePrice` (type: `integer`):

American odds ceiling for the same band. 500 keeps out long shots.

## `maxMarginPercent` (type: `integer`):

Keep only matchups whose main moneyline holds at most this much vig. Pinnacle's top soccer books sit near 2%; a value of 3 keeps only the sharpest markets.

## `minMaxStake` (type: `integer`):

Keep only matchups whose main market accepts at least this maximum stake, in the account currency. Pinnacle raises limits on the markets it is most confident in, so this filters for liquidity.

## `onlyOpenMarkets` (type: `boolean`):

Drop matchups whose main period is suspended or closed.

## `requirePrimaryMarket` (type: `boolean`):

Drop matchups with no moneyline, spread, or total on the main period. On by default, so you never get an empty row.

## `oddsFormats` (type: `array`):

Which price formats to write on every selection. American and decimal are the default; the rest are added as extra fields.

## `includeAlternateLines` (type: `boolean`):

Attach the full alternate spread and total ladder, each rung priced with its own margin and fair odds. Billed as `alternate-lines`, once per matchup that has any.

## `includePeriodMarkets` (type: `boolean`):

Attach the halves, quarters, sets, maps, or innings books, labelled per sport. Billed as `period-markets`, once per matchup that has any.

## `includeTeamTotals` (type: `boolean`):

Attach each side's own over/under book. Billed as `team-totals`, once per matchup that has any.

## `includeProps` (type: `boolean`):

Attach the matchup's prop book: Correct Score, Both Teams To Score, Double Chance, Half-Time/Full-Time, player and team props, each with priced selections. This is the largest block by far. Billed as `prop-markets`, once per matchup that has any.

## `includeLiveOdds` (type: `boolean`):

Pinnacle serves its public board through a CDN, so a league page can be several minutes behind. For games in play this reads the sport's live feed as well and keeps whichever board version is newer. Measured on 8 live games, the live feed was the fresher copy 8 times out of 8. Costs two requests per sport, not per match. Billed as `live-odds`, once per match that actually got a newer board.

## `trackLineMovement` (type: `boolean`):

Take a second odds snapshot after a hold and attach what moved: every price and handicap that changed, with direction and probability delta. Rows are written after the second snapshot. Pinnacle's public board is cached for about 15 minutes, so the hold has to outlast that cache to see anything. Billed as `line-movement`, once per row, and only when the board actually reloaded.

## `lineMovementDelaySecs` (type: `integer`):

How long to hold between the two snapshots, from 60 to 1800 seconds. Pinnacle's public board is CDN-cached for about 900 seconds, so anything shorter usually returns the same board and reports no movement. 900 is the default for that reason. The window is shortened automatically if the run timeout is closer than that.

## Actor input object example

```json
{
  "sports": [
    "Soccer"
  ],
  "maxItems": 50,
  "onlyLive": false,
  "onlyPrematch": false,
  "includeSpecialsAsRows": false,
  "onlyOpenMarkets": false,
  "requirePrimaryMarket": true,
  "oddsFormats": [
    "american",
    "decimal"
  ],
  "includeAlternateLines": false,
  "includePeriodMarkets": false,
  "includeTeamTotals": false,
  "includeProps": false,
  "includeLiveOdds": false,
  "trackLineMovement": false,
  "lineMovementDelaySecs": 900
}
```

# Actor output Schema

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

Every collected matchup with its main markets, computed fair odds, and any optional alternate-line, period, team-total, prop, or line-movement block

# 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 = {
    "sports": [
        "Soccer"
    ],
    "maxItems": 50,
    "lineMovementDelaySecs": 900
};

// Run the Actor and wait for it to finish
const run = await client.actor("parseforge/pinnacle-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 = {
    "sports": ["Soccer"],
    "maxItems": 50,
    "lineMovementDelaySecs": 900,
}

# Run the Actor and wait for it to finish
run = client.actor("parseforge/pinnacle-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 '{
  "sports": [
    "Soccer"
  ],
  "maxItems": 50,
  "lineMovementDelaySecs": 900
}' |
apify call parseforge/pinnacle-odds-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,parseforge/pinnacle-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/Fog4E7XdKr2nWgzcN/builds/KVBQ9WUSmaaFYr9CA/openapi.json
