ParseForge Scrapers

Pinnacle Sportsbook Odds Scraper

parseforge/pinnacle-odds-scraper

News & media

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.

Run this scraper See the API call
Total users
2
Monthly active
1
Total runs
10
Bookmarked
0
Rating
Not rated yet
Last modified
28 hours ago

Overview

ParseForge

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:

{
  "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:

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

One league's full board with every optional block:

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

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

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

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

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

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

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

Only high-limit markets in one country:

{ "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 to collect the full board.

Run it

  1. Create a free Apify account with $5 in credit.
  2. Open the Pinnacle Sportsbook Odds Scraper.
  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 or the ApifyClient 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:

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

Browse the full ParseForge collection 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.

Input

FieldTypeWhat it doesDefault
sports 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 ["Soccer"]
leagues 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 not set
matchupIds array Individual Pinnacle matchup (event) ids. Use these to re-read the same games on a schedule. Example: 1634440437 not set
startUrls 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/ not set
maxItems integer Total matchups to write across every source in this run. Free Apify plans return a 10-row preview. 50
maxLeaguesPerSport 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. not set
maxMatchupsPerLeague integer Cap how many matchups are taken from each league. Leave empty to let Maximum rows decide. not set
onlyLive 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. false
onlyPrematch boolean Drop games that are already in play. false
includeSpecialsAsRows 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. false
leagueGroups array Keep only leagues whose group matches one of these, case-insensitive. Pinnacle groups leagues by country or confederation. Examples: - England - Spain - UEFA not set
leagueNameContains array Keep only leagues whose name contains one of these, case-insensitive. Example: Premier not set
teamNameContains array Keep only matchups where a participant name (or a special's description) contains one of these, case-insensitive. Examples: - Arsenal - Djokovic not set
startsWithinHours integer Keep only matchups starting inside this many hours from now. 24 gives you today and tonight; 168 gives you the week. not set
startsAfter string Keep only matchups starting on or after this moment (YYYY-MM-DD or a full ISO timestamp). not set
startsBefore string Keep only matchups starting on or before this moment (YYYY-MM-DD or a full ISO timestamp). not set
minMoneylinePrice integer American odds floor. A matchup is kept when at least one moneyline side sits inside the band. -200 keeps out heavy favourites. not set
maxMoneylinePrice integer American odds ceiling for the same band. 500 keeps out long shots. not set
maxMarginPercent 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. not set
minMaxStake 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. not set
onlyOpenMarkets boolean Drop matchups whose main period is suspended or closed. false
requirePrimaryMarket boolean Drop matchups with no moneyline, spread, or total on the main period. On by default, so you never get an empty row. true
oddsFormats array Which price formats to write on every selection. American and decimal are the default; the rest are added as extra fields. ["american","decimal"]
includeAlternateLines 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. false
includePeriodMarkets boolean Attach the halves, quarters, sets, maps, or innings books, labelled per sport. Billed as `period-markets`, once per matchup that has any. false
includeTeamTotals boolean Attach each side's own over/under book. Billed as `team-totals`, once per matchup that has any. false
includeProps 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. false
includeLiveOdds 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. false
trackLineMovement 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. false
lineMovementDelaySecs 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. 900

Pricing

from $6.23 per 1,000 results

Charged forWhat it isPrice each
Match One Pinnacle match written to the dataset, with its moneyline, spread and total, bet limits, bookmaker margin and no-vig fair odds. $0.00623 to $0.007
Actor Start Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event). $0.04806 to $0.054
Board scan Charged once per board resolved against Pinnacle: a league fixture list with its markets, or a sport live feed. $0.00534 to $0.006
Alternate lines Charged per match enriched with its alternate spread and total ladder. $0.00267 to $0.003
Period markets Charged per match enriched with its halves, quarters, sets, maps or innings books. $0.00267 to $0.003
Team totals Charged per match enriched with each side’s own over/under book. $0.00178 to $0.002
Props and specials Charged per match enriched with its prop book: correct score, both teams to score, double chance, player and team props. $0.00712 to $0.008
Live feed cross-check Charged per in-play match rebuilt from Pinnacle’s live feed after it proved to be a newer board than the league page. Not charged when the league page was already the newest copy. $0.00356 to $0.004
Line movement Charged per match compared against a second odds snapshot that came back as a genuinely newer board, reporting every price and handicap that moved. Not charged when the board did not reload. $0.0089 to $0.01

Tiered: the lower figure is the price on a higher Apify plan. Billing and the free credit live on Apify.

API

One POST returns the dataset directly. Same shape for every scraper in the library, so swapping the slug is the only change.

POST · run and get results
curl -X POST "https://api.apify.com/v2/acts/parseforge~pinnacle-odds-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "helloWorld": 123
  }'

Examples

Input that runs as-is.

input.json
{
  "helloWorld": 123
}

Reviews

No reviews yet. Be the first.

Issues

We build and maintain this scraper, so a problem with it comes to us. Report it on the Apify listing and the thread stays attached to the scraper where the next person can find it: open an issue.

Broken and urgent, or you would rather not post in public? Write to parseforge@protonmail.com and it reaches the people who wrote it.

Related scrapers

Run Pinnacle Sportsbook Odds Scraper on Apify All scrapers