Sports Betting Odds Scraper — NFL, NBA, Soccer, Tennis & Props
Pricing
from $10.00 / 1,000 pre-match odds snapshots
Sports Betting Odds Scraper — NFL, NBA, Soccer, Tennis & Props
Keyless sports odds API: live odds and pre-match odds from Pinnacle and Bovada — moneyline, spreads, totals, specials, player props, de-vigged fair odds, line movement, odds comparison. NFL, College Football, MLB, NBA, NHL, UFC, tennis and soccer odds (Premier League, La Liga, Champions League).
Pricing
from $10.00 / 1,000 pre-match odds snapshots
Rating
0.0
(0)
Developer
Sami
Maintained by CommunityActor stats
3
Bookmarked
176
Total users
40
Monthly active users
4 hours ago
Last modified
Categories
Share
Live and pre-match odds from Pinnacle (the sharp reference), Bovada (a major US offshore book), Kambi (Unibet, Paf) and the Smarkets exchange — one row per market outcome, no API key. Pick the leagues you want (NFL, NBA, NHL…) or whole sports, compare the same game across books, and add no-vig fair odds with each price's edge against the sharp one.
What the Start button runs. The form is prefilled for a cheap first try: Bovada,
leaguesNFL + NBA + NHL, 8 games each — 144 rows, about $1.49, on the books' own feeds saved 24-28 Sep 2026. API calls and saved Tasks that omitbooks,sports,leaguesormaxEventsPerSportkeep the long-standing defaults: Pinnacle, soccer + tennis, no league pick, 50.
Dated fix notes and data corrections (August-October 2026) are in Changelog & data corrections at the bottom of this page.
What you get
- One row per outcome — book, league, teams, start time, market (
h2h,spreads,totals, specials, player props, team totals, first half), line, and the price in decimal, American and fractional, with its implied probability. - The same game across books. Pick 2+ books and
isBestPriceAcrossBooksflags the best price wherever at least two of them quote the same outcome and line. - No-vig fair odds and edge (paid add-on
includeFairOdds, $0.05 per enriched row): the price with the book's margin removed, and — with Pinnacle or Smarkets among your books —edgePct, how far each price sits above or below the sharp no-vig price for the same bet, pluscrossBookMarginPct, the market's margin at the best price per outcome across your books. Both on pre-match prices only, and only where a sharp book (or a second book) quotes the same market in full — other rows keep them null. - Line movement, free: previous price, change and direction from your second run of a market onward.
Five rows from the saved 28 Sep 2026 feeds (NBA, games of 20-21 October), Bovada + Pinnacle with includeFairOdds on:
| book | game | market | outcome | price (US) | noVigFairPrice | edgePct |
|---|---|---|---|---|---|---|
| bovada | Spurs v Thunder | h2h | Spurs (home) | 1.8333 (-120) | 1.9167 | +2.00 vs Pinnacle's 1.7974 |
| bovada | Spurs v Thunder | h2h | Thunder (away) | 2.00 (+100) | 2.0909 | -11.28 vs Pinnacle's 2.2541 |
| pinnacle | Spurs v Thunder | h2h | Spurs (home) | 1.7463 (-134) | 1.7974 | null — the reference itself |
| pinnacle | Spurs v Thunder | h2h | Thunder (away) | 2.19 (+119) | 2.2541 | null — the reference itself |
| bovada | Knicks v 76ers | spreads | Knicks -5.5 | 1.9091 (-110) | 2.0000 | -7.60 vs Pinnacle's 2.0663 |
The edge is the two prices as this run read them: a feed can lag (Bovada's coupon is cached by its CDN), so re-check a price before acting on it.
Three inputs to copy
- NBA lines, Bovada vs Pinnacle — up to 15 games per book: 108 rows, about $1.13 on the 28 Sep feeds:
{ "books": ["bovada", "pinnacle"], "leagues": ["NBA"], "maxEventsPerSport": 15 }
- No-vig fair odds + edge against the Smarkets exchange (NHL) — 5 games per book, 60 rows, 24 of the 30 Bovada rows with an edge against the exchange's no-vig price, about $3.65 on the 28 Sep feeds (each enriched row is $0.01 + $0.05; the exchange's own rows are the reference and carry no edge). Swap
"smarkets"for"pinnacle"to compare against Pinnacle instead:{ "books": ["bovada", "smarkets"], "leagues": ["NHL"], "maxEventsPerSport": 5, "includeFairOdds": true } - A line-movement watch — save as a Task and schedule it
0 */6 * * *(4 runs a day). From the second run on, every row carries its previous price, change and direction at no extra charge; each run bills the rows it delivers — 48 rows, about $0.53, for 8 NFL games on the 24 Sep feed:{ "books": ["bovada"], "leagues": ["NFL"], "maxEventsPerSport": 8, "includeLineMovement": true }
A sportsbook odds scraper. Pre-match + in-play odds from Pinnacle (the sharp reference), Bovada (a major US offshore book), the Kambi network and the Smarkets exchange, in one normalized feed — including Pinnacle's specials (thousands per sport in soccer, hundreds in NFL). Datacenter-proxy friendly, poll every 60 s or slower, with the same fixture matched across the books you enable. Pay-Per-Event from $0.01 per pre-match snapshot, $0.025 per live snapshot, $0.05 per specials / player-prop snapshot. Built for solo sharps, 2-3 person tipster shops, EV-modelling teams, and arbitrage operations who currently pay $249/mo for The Odds API or OddsJam Gold.
Pricing anchor: The Odds API sits at $249/mo for 15M credits ($0.0166/1K credits effective). OddsJam Gold is $249/mo monthly / $199/mo annual. Here you pay per snapshot, so the bill follows your cadence: two pre-match captures a day on 20 fixtures (h2h + spreads + totals, ~130 rows per run) is about $81/mo (60 runs × 130 × $0.01 + 60 × $0.05), while a 5-minute cron on the same fixtures costs far more (see the Schedule table below).
How to scrape sports odds in 3 easy steps
- Pick your leagues or sports.
leagues(NFL, NCAAF, NBA, WNBA, NHL, MLB) sets each sport and filters it to that league in one step —["NFL", "NBA"]returns NFL and NBA games only. For everything else (soccer, tennis, MMA, cricket…) leaveleaguesempty and usesports, whose default is["soccer", "tennis"], withleagueFilterto name a competition (a sport on its own takes leagues in each book's own order, up tomaxEventsPerSport; and leagueFilter applies to every sport of the run). Default book is["pinnacle"]; add"bovada"for a major US offshore book (the sharp-vs-offshore pair +EV models compare),"kambi"for Unibet / Paf and the rest of the Kambi network in the same feed, and"smarkets"for the exchange price with no bookmaker margin (Bet365 mode still under repair). - Pick your market types.
h2h(moneyline) +spreads+totalsis the canonical EV-modelling triad. Addspecialsfor Pinnacle's futures / yes-no / exact-totals / first-team-to-score / winning-margin / team-props markets and its over/under player props (premium $0.05 tier — these are the markets sharps and tipster shops actually mine for edge).player_propsis Pinnacle's per-game player over/unders — one row per player, stat and side, with the game's teams inhomeTeam/awayTeamplusplayerName,propStat,propDescriptionandparentEventId(premium $0.05 tier; Pinnacle lists them game by game, so a run that finds none says so and bills nothing for them).team_totalsadds each team's own over/under from Pinnacle — measured 2026-09-18 at 399 markets in American football and 155 in baseball, about +20% and +40% on top of the base three — billed like any pre-match snapshot, with outcome keyshome_over/home_under/away_over/away_under. - Schedule a cron. Apify Saved Tasks → Schedules → e.g.
*/5 * * * *for 5-minute pre-match polling,* * * * *for in-play during target matches. To cut what you pay for unchanged prices, raisededuplicationWindowSeconds. The window is measured from the last time the row was delivered, not from the last run: an unchanged price is billed again as soon as the window has elapsed since its last delivery. So use the largest window you can tolerate — on a*/5Schedule,3600(the maximum) bills an unchanged row on about 1 run in 12,600on 1 in 3,360on 1 in 2, and the default30on every run. A changed price is always delivered and billed immediately, whatever the window.
That's it. Snapshots land in your Apify dataset, ready for export to your EV pipeline, Discord bot, or Sheets workbook.
Feature requests or bugs? Open an Issue on this Actor's page.
Who buys this Actor
| Buyer persona | Typical monthly spend | Why this Actor wins |
|---|---|---|
| Solo sharp bettor (10-30 events/day) | $30-80/mo | Replaces a $59 The Odds API tier with two books in one feed and cross-book best-price flagging built in |
| 2-3 person tipster operation | $160-240/mo | Pre-match polling every 6 hours across 20-30 fixtures, h2h+spreads+totals |
| EV-modelling solo / small team | $200-650/mo | Pinnacle's sharp closing lines are the canonical fair-value benchmark — feed straight into your EV-model with delta polling |
| Specials / futures trader | $150-500/mo | Pinnacle specials — thousands per sport in soccer, hundreds in NFL (futures, exact-totals, first-to-score, winning margins, over/under player props) |
| In-play model / fantasy ladder | $650-1,900/mo | Live-only mode + 60-180s cadence + isLive on every row (match clock and score on Kambi's Unibet/Paf in-play rows only; null on the other books) |
| Sports-media analyst / blog | $35-120/mo | Daily pre-match dump for "lines moved" content; 6h cron covers the news cycle |
Example use cases
- Pinnacle closing-line value (CLV) tracker —
mode: "pre_match_only", run atT-30minandT-1minper fixture. Bet your soft book at T-30, measure CLV against Pinnacle's T-1 close. Pinnacle is the canonical sharp benchmark for fair-value. Cost: ~7 rows × $0.01 per fixture per capture, plus $0.05 per run that delivers rows. - EV-model live edge harvester —
mode: "live_only", 60-second cron,sports: ["basketball"]withleagueFilter: ["NBA"](a filter applies to every sport in the run, so give tennis and soccer their own Task). Stream snapshots into your model; emit a bet ticket when (your_fair_value × book_price) > 1.03. ~$1,250/mo per 50K live snapshots/month ($0.025 each), plus $0.05 per run that delivers rows. - Tipster Discord auto-poster —
sports: ["soccer"], leagueFilter: ["UEFA", "England - Premier League"], 6h cron. Webhook → Discord with the day's top 10 spreads + totals. Cost: 120 runs/month × (fixtures × ~7 rows × $0.01 + $0.05). - Specials / futures sniper —
marketTypes: ["specials"], sports: ["soccer"], 4h cron during the season. Soccer carries thousands of Pinnacle specials (yes/no markets, exact totals, first-to-score, winning margins) — the segment where soft books haven't fully sharpened up. Cost: 180 runs/month × specials rows × $0.05 — cap the volume withmaxEventsPerSport. - The Odds API replacement (Pinnacle slice) — drop-in feed for teams currently paying $249/mo The Odds API just for the Pinnacle column. ~$0.01-0.05 per snapshot vs. $0.0166/1K credits effective — cheaper only at low capture counts (a few captures a day on a few dozen fixtures; a 5-minute cron passes $249/mo quickly); raise the dedup window so unchanged prices are billed less often (see step 3 for the exact rule).
- Cross-book best-price —
includeBestPriceFlag: truecompares prices per outcome across the books you enable, e.g.books: ["pinnacle", "bovada"]. It only fires on fixtures BOTH books cover, and only on outcomes both books quote at the same line. Measured 2026-08-02: a soccer run returned 2 Pinnacle fixtures against 60 Kambi ones with zero overlap, so the flag stayedfalseon all 186 rows — working as designed, but useless without shared fixtures. The run's status message now reports how many fixtures were matched across books, so you can tell "nothing was cheaper" apart from "the books never met". To force overlap, raisemaxEventsPerSportand useleagueFilterto pin both books to the same competitions.
Smarkets — the exchange in the feed (books: ["smarkets"])
Every other book here is a bookmaker, and a bookmaker's posted price includes its margin. Smarkets is an exchange: people betting against each other, so the price that clears is the market's own. Measured on the fixtures captured 2026-09-23, its 1X2 back prices summed to 100.61% where a bookmaker posts 104-108%.
What that buys you, concretely:
- A margin-free reference price. Compare any book's
priceagainst the exchange's on the same fixture, market and line. - Honest fair odds. With
includeFairOddson and"smarkets"among your books, thenoVigFairPriceof a fixture the exchange also quotes is taken from the exchange instead of from de-vigging the same bookmaker whose margin you are trying to remove. Every enriched row says which it got innoVigSource(smarketsorbook-devig), and the run status reports how many rows were anchored. This changes the number, not the bill: only rows that already carried a fair price are re-anchored, so the count of chargedfair-odds-enrichmentevents is identical either way. Two things the anchor refuses to do, because both would break the number you are paying for: it will not re-base part of a market (if the exchange does not cover every outcome of that market, the whole market keeps its own de-vig — otherwise the shipped probabilities would stop summing to 1, which is the entire point of a no-vig set), and it will not anchor from a quote that is not a market price (a book whose priced sides sum below 100%, or a side resting less than 50 units, is a token order, not a market). - A back price is GROSS of commission. Smarkets makes its money as commission on net
winnings, not as margin in the price — so
priceon an exchange row is not directly comparable, penny for penny, with a bookmaker's. At the exchange's published 2%, a winning 1-unit bet at 2.78 nets 2.7444, which is less than a bookmaker's 2.75.isBestPriceAcrossBookscompares the raw numbers and does not know about commission, so on margins under ~2% treat an exchange "best price" as a tie, not a win. - The money behind the price.
exchangeLiquidityis what is actually resting at that exact price, andexchangeLayPriceis the other side of the book. A 2.50 with 12 units behind it is not the same quote as a 2.50 with 12,000, and on an exchange you can see which one you have. No other book in this feed can tell you that.
What it does not do, said plainly:
-
h2h, spreads and totals only. The exchange lists player props, corners, cards, halves, quarters and team totals; none of them is mapped here, so none of them is promised. Ask for
specials,player_props,team_totalsorfirst_half_*and the run status names Smarkets as a book that does not carry them. -
What each sport actually carries (measured on live payloads 2026-09-23 — not a rule, a list):
Sport h2h spreads totals Soccer 3-way 1X2 yes (Asian handicap) yes American football 2-way (tie voids) yes yes Basketball, baseball 2-way yes yes Rugby union, rugby league 3-way (draw is a real result) yes — Ice hockey (NHL, measured 2026-09-28) 2-way yes (puck line) yes Tennis, cricket, MMA, esports 2-way — — h2his always the same bet your other books quote: the three-way 1X2 where a draw is a priced result, the two-way moneyline where it is not. Bets that only look like the ones above are deliberately skipped rather than filed under a column they do not belong in — rugby's Draw No Bet (a two-runner bet the exchange lists right beside the three-way winner), ice hockey's regulation-time handicap and total (they exclude overtime, so they are not the puck line and total the other books quote — the exchange now lists the full-game ones beside them for NHL games, and those are what comes back: measured 2026-09-28 on the NHL preseason, 5 of 5 games with a puck line and a total — on the same line as Bovada and Pinnacle for the puck line on 4 of them and for the total on 3, because the exchange reports its own main rung), and tennis' sets handicap (not a games handicap). Where a sport carries no spreads or totals, the run status names the sport and the markets, so an empty column never reads as a failing feed. -
One line per market by default. The exchange lists whole ladders (21 totals and 20 handicaps on one NFL game). By default each market reports the rung the exchange itself prices closest to 50/50 — which is what "the line" means on an order book — and
includeAlternateLines: truereturns the scanned ladder withisAlternateon every other rung. There is no "primary" flag in the source, so this is measured from live prices rather than guessed. -
A market with no order gets no row. A runner nobody is offering has no price, and none is invented for it. A winner market with a side missing is dropped whole.
-
It paces itself. The exchange is priced market by market, so a sport is capped at its own request and time budget, and at 100 fixtures per sport however high you set
maxEventsPerSport. If a run hits either cap the status says so and names the sport — lowermaxEventsPerSportfor full coverage of a shorter list.
It bills exactly like Bovada or Kambi: odds-snapshot-pre-match / odds-snapshot-live,
one row per runner. Never one row per depth level — the book is three to five levels
deep on a busy contract, and charging for each would triple the invoice for the same
information.
Line movement — included, not an add-on
Every snapshot is compared against the last price this account's runs saw for the same book + event + market + line + outcome, so a cron turns into a movement feed instead of a pile of disconnected prices:
| Field | Meaning |
|---|---|
previousPrice / previousPriceAt | The prior price for this exact outcome, and when it was seen |
secondsSincePreviousPrice | Gap between the two observations |
priceChange / priceChangePct | Decimal-odds delta and percentage |
impliedProbChange | Change in implied probability — the direction sharps read (odds down = probability up) |
moveDirection | shortened (price fell), drifted (price rose) or unchanged |
firstSeenPrice / firstSeenPriceAt | The first price your runs ever recorded for this outcome |
moveSinceFirstSeen / moveSinceFirstSeenPct | Total movement since that first observation |
It is free. The dedup window already stores the last price per outcome, so movement
costs no extra request and carries no extra charge — turn it off with
includeLineMovement: false if you want the leaner rows.
Honest naming: firstSeenPrice is the first price your runs observed, not the
book's true opening line — no scraper can retro-fetch a line it never saw. Start a cron
early on the fixtures you care about and it becomes your opener. Movement fields appear
from the second run of a market onward, since the first run has nothing to compare to.
{"books": ["pinnacle"],"sports": ["soccer"],"mode": "pre_match_only","includeLineMovement": true,"deduplicationWindowSeconds": 30}
Dedup suppresses an unchanged price only while the window has not yet elapsed since that row was last delivered (a suppressed run does not restart the clock). At the default 30 s that is almost never, so every scheduled run bills every row; at the 3600 maximum a */5 Schedule bills an unchanged row on about 1 run in 12.
Corrected 11 September 2026 — American football on Pinnacle. Until this build, every Pinnacle American-football row (NFL and NCAA) carried the same team in
homeTeamandawayTeam— the away side overwrote the home slot — so outcome labels named the wrong team and those fixtures never matched Bovada's, leavingisBestPriceAcrossBooksfalse. Tennis, basketball, soccer and every Bovada row were not affected. If you consumed Pinnacle football rows before this date, re-pull them; open an issue and it will be made right.
Pricing (Pay-Per-Event)
| Event | Price | When it fires |
|---|---|---|
odds-snapshot-pre-match | $0.01 / snapshot | One market-outcome from a scheduled (not in-play) fixture. Default tier for 5-15 min cron workflows. |
odds-snapshot-live | $0.025 / snapshot | One market-outcome from a live (in-play) match. Premium tier — bot pressure on live endpoints is higher. |
odds-snapshot-player-prop | $0.05 / snapshot | One Pinnacle special (futures / yes-no / exact-totals / first-to-score / winning-margin / team prop) OR one Pinnacle per-game player prop (player_props). Highest WTP segment of sports betting data. |
scheduled-run | $0.05 / run | Once per Actor invocation that delivered at least one row. A run that returns nothing — dedup suppressed everything, no fixtures matched, a book was unreachable — bills nothing at all. |
fair-odds-enrichment | $0.05 / snapshot | Only when includeFairOdds is on (off by default). Adds the vig-free fair price, fair probability and overround to a row, on top of its base snapshot price — and, with 2+ books including Pinnacle or Smarkets, edgePct against the sharp price and crossBookMarginPct, at no extra charge. |
Prices effective 8 August 2026. Live snapshots moved from $0.020 to $0.025 and player-props / specials from $0.040 to $0.050 on that date;
fair-odds-enrichmentis new and opt-in. Pre-match ($0.01) andscheduled-run($0.05) are unchanged.
How to estimate your monthly cost
Cost formula: runs per month × rows per run × $0.01 (pre-match; $0.025 live, $0.05 specials; plus $0.05 per row that carries a fair price when includeFairOdds is on) + runs that delivered rows × $0.05. Runs per month = 43,200 ÷ interval in minutes. Rows per fixture for h2h + spreads + totals measured 2026-09-15: 6.0 (Bovada NFL), 7.0 (Bovada soccer). The Schedule table further down gives upper bounds with nothing suppressed; how many prices stay unchanged is not measured, so no discount is promised.
In Apify Console, the built-in apify-default-dataset-item event is set to $0.00001 to avoid double-billing — your actual line items are the events in the table above.
Python example
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("zhorex/sports-odds-aggregator").call(run_input={"books": ["pinnacle"],"sports": ["soccer", "tennis"],"marketTypes": ["h2h", "spreads", "totals", "specials"],"mode": "pre_match_only","maxEventsPerSport": 50,"deduplicationWindowSeconds": 30,})for snapshot in client.dataset(run["defaultDatasetId"]).iterate_items():if snapshot["marketType"] == "specials" and snapshot["impliedProbability"] < 0.10:# Pinnacle special at >10x implied — possible long-shot value edge.evaluate_for_bet(snapshot)
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('zhorex/sports-odds-aggregator').call({books: ['pinnacle'],sports: ['basketball'],marketTypes: ['h2h', 'spreads', 'totals'],mode: 'live_only',deduplicationWindowSeconds: 0, // emit every snapshot during in-play runs});const { items } = await client.dataset(run.defaultDatasetId).listItems();const liveSpreads = items.filter(s => s.isLive && s.marketType === 'spreads');console.log(`${liveSpreads.length} live spread snapshots`);
Scheduled / recurring runs — the money section
To run it on a schedule:
- Run once with the prefilled input (Bovada; NFL, NBA and NHL through
leagues, 8 games each) to check your leagues return data; add"pinnacle"tobooksfor the sharp reference line.
🏈 College football (NCAAF) is the deepest American-football board right now. Measured on Bovada's own pre-match coupon on 2026-09-17: 57 College Football fixtures against 16 NFL. Each book spells it its own way — Bovada
College Football, PinnacleNCAA, KambiNCAAF— andleagues: ["NCAAF"]picks all three:{"books": ["bovada", "pinnacle"],"leagues": ["NCAAF"],"marketTypes": ["h2h", "spreads", "totals"],"mode": "pre_match_only","maxEventsPerSport": 40}It leaves NFL and CFL out. The same with
sports: ["american_football_intl"]andleagueFilter: ["College Football", "NCAA"]works too (leagueFilter is a case-insensitive whitelist); drop the filter to get every league the sport carries.
🏀 🏒 NBA and NHL: pick the league. A sport on its own takes leagues in the book's own order: on 2026-09-28,
basketballat 15 events came back with 0 of Bovada's 17 NBA games (Euro Cup, WNBA and Euroleague come first), and the run status says when that happens.leaguesfilters each sport to its own league, so NBA + NHL in one run is simply:{"books": ["bovada", "pinnacle", "kambi"],"leagues": ["NBA", "NHL"],"marketTypes": ["h2h", "spreads", "totals"],"mode": "pre_match_only","maxEventsPerSport": 15}(
"sports": ["basketball"], "leagueFilter": ["NBA"]still works.) For hockey add"smarkets"tobooks— the exchange quotes NHL moneyline, puck line and total. What the books listed on 2026-09-28: NBA — Bovada 17 games, Pinnacle 3, Kambi/Unibet 19, Smarkets none; NHL — Bovada 23, Pinnacle 5, Kambi/Unibet 8, Smarkets 22. The first NBA games listed start on 20 October 2026.
- Preseason is not labelled. No book marks it: the NHL games listed that day (29 September to 4 October) are preseason games filed under the plain league name
NHL(Kambi does label its AHL preseason, asAHL Preseason). To keep only the regular season, keep rows whosecommenceTimeis on or after opening night.- Player props. Add
"player_props"tomarketTypesfor Pinnacle's per-game player over/unders (points, rebounds, assists, threes…; shots on goal in hockey), each row carrying the game's teams,playerNameandpropStat. Pinnacle lists them game by game: on 7 October 2026 it listed 161 in ice hockey (NHL, e.g. "Mark Scheifele Total Shots On Goal") and 30 in basketball (NBA preseason). A sport or league with none at run time is named in the run status and nothing is billed for it. They bill at the premiumodds-snapshot-player-proptier, so cap them withmaxEventsPerSport. Each prop counts as one event against that cap (on Pinnacle, games and props are capped separately), and Pinnacle interleaves props across the slate: on the 28 September 2026 capture it listed 205 WNBA props over 4 games, and a cap of 8 returned 8 props (16 rows) spread over all 4 games.
- Save as a Task with your tuned
sports / marketTypes / mode / dedupWindowcombination. - Open the Task → Schedules → New Schedule with one of the cron strings below.
- Add a webhook to the Schedule to ship the dataset into your EV pipeline, Discord/Slack bot, or Sheets workbook.
| Use case | Cron string | Runs / month | Upper bound, nothing suppressed |
|---|---|---|---|
| Pre-match every 5 min, 20 fixtures (~130 rows/run) | */5 * * * * | 8,640 | 3600 a row that never moves bills on 360 on 1 in 2 ( |
| Every 6 hours, 20 fixtures | 0 */6 * * * | 120 | ~$160/mo |
| Daily 9am dump, 30 fixtures | 0 9 * * * | 30 | ~$60/mo |
| Weekly audit, 20 fixtures | 0 10 * * 6 | ~4 | ~$6/mo |
| In-play every minute | * * * * * | up to 43,200 | live rows bill $0.025 each — limit the Schedule to match windows |
Cut what a Schedule pays for unchanged prices.
deduplicationWindowSecondsis measured from the last time a row was delivered, not from the previous run, so an unchanged price is billed again every time the window elapses. Measured on a*/5Schedule against a price that never moves:3600(the maximum) bills it on about 1 run in 12,600on 1 in 3,360on 1 in 2, and the default30on every run — use the largest window your staleness tolerance allows. A run whose rows were all suppressed delivers nothing and bills nothing (no per-run fee).Corrected 10 September 2026. Until this build the
$0.05per-run fee was charged on every invocation, including the ones that returned an empty dataset, so a tight cron on stable prices could bill a standing fee for nothing. It no longer does. If that affected you, open an issue and it will be made right.
Example input
{"books": ["pinnacle"],"sports": ["soccer"],"leagueFilter": ["England - Premier League", "La Liga", "UEFA"],"marketTypes": ["h2h", "spreads", "totals", "specials"],"mode": "pre_match_only","priceFormat": "decimal","maxEventsPerSport": 50,"includeBestPriceFlag": true,"includeFairOdds": true,"deduplicationWindowSeconds": 30,"proxyConfiguration": { "useApifyProxy": true }}
Example output (one snapshot record)
{"snapshotId": "a1b2c3d4e5f6789012345678","book": "pinnacle","sport": "soccer","league": "England - Premier League","eventId": "1583217845","homeTeam": "Manchester City","awayTeam": "Liverpool","commenceTime": "2026-05-22T19:00:00Z","isLive": false,"matchClock": null,"matchScore": { "home": null, "away": null },"marketType": "h2h","marketLine": null,"maxRiskStake": 300,"outcomeKey": "home","outcomeLabel": "Home","price": 1.91,"priceAmerican": -110,"priceFractional": "10/11","impliedProbability": 0.52356,"overround": 0.045,"noVigProb": 0.5011,"noVigFairPrice": 1.9956,"isBestPriceAcrossBooks": false,"scrapedAt": "2026-05-18T14:32:00Z"}
A typical run for one Pinnacle fixture with ["h2h", "spreads", "totals"] emits ~7 snapshot records (3 outcomes h2h + 2 outcomes spreads + 2 outcomes totals). Add "specials" to that input and you typically get an extra 30-80 specials snapshots per fixture (yes/no markets, exact totals, first-to-score, winning margin per scoreline, etc.).
Output schema reference
| Field | Type | Notes |
|---|---|---|
snapshotId | string | Stable id sha1(book | event | market | outcome | marketLine | marketKey | scrapedAt)[:24] — use as dataset primary key. marketLine and marketKey are part of it so every rung of an alternate ladder stays distinct |
book | string | pinnacle, bovada, smarkets, or the Kambi operator name when kambi is enabled (Unibet, Paf). Bet365 mode in repair — when it returns, this column also surfaces bet365. |
sport | string | Canonical sport key (e.g. soccer, tennis, basketball) |
league | string | Book-reported league/competition name |
eventId | string | Book-native event id (not portable across books — use homeTeam+awayTeam+commenceTime for joins) |
homeTeam | string | The home side. On player_props rows, the home team of the prop's game; on specials rows, the special's question |
awayTeam | string | The away side. On player_props rows, the away team of the prop's game; on specials rows, the special's category |
commenceTime | ISO 8601 | UTC scheduled start time |
isLive | bool | True if the match is currently in-play |
matchClock | string | null | Kambi (Unibet, Paf) in-play rows: the period and, on basketball and ice hockey, the ELAPSED game time as Kambi shows it, e.g. 2nd Quarter 19:10 (19:10 played, not time left). Other sports get the period only. Null on pre-match rows, when Kambi sends no clock, and on the other books' rows. On ice hockey the price comes from each fixture's own page, read up to ~30 s after the fixture list, while clock and score are as of the list read |
matchScore | object | Kambi (Unibet, Paf) in-play rows: { "home": 51, "away": 24 } from Kambi's live score (tennis excepted); on ice hockey as of the fixture-list read (see matchClock). { "home": null, "away": null } on pre-match rows, when Kambi's score does not read as two whole numbers, and on the other books' rows |
marketType | string | h2h, spreads, totals, first_half_h2h, first_half_spreads, first_half_totals, specials, player_props, team_totals |
marketLine | float | null | Spread or total line of THIS outcome (e.g. home -1.5 / away 1.5, total 2.5; Asian quarter lines as -1.25); null for h2h. For Pinnacle over/under specials it is the prop's line |
marketKey | string | The book's own market id — Pinnacle (s;0;s;-1.5, s;0;ou;2.5), Bovada and Smarkets; absent on Kambi and on Pinnacle yes/no specials. Both sides of one market share it, which is how fair odds pair them. Use it when two rows share a market type, line and outcome but quote different prices — that happens with alternate ladders, and this is what tells them apart |
isAlternate | bool | Present on Pinnacle and Smarkets spread/total/team-total rows when includeAlternateLines is on: true for the extra rungs of the ladder, false for the primary line. Absent on h2h (no line, so the notion does not apply) and on other books, which do not expose it |
outcomeKey | string | home, away, draw, over, under, yes, no |
playerName | string | null | player_props rows (and Pinnacle specials filed as player props): the player, read from Pinnacle's own " |
propStat | string | null | The prop's stat as Pinnacle names it (Points, Rebounds, Assists, Threes Made, Receiving Yards…). Null on yes/no props with no stat (soccer's <player> To Score) |
propDescription | string | null | Pinnacle's own wording of the prop ("Courtney Williams Total Assists"). Same rows as playerName |
parentEventId | string | Pinnacle id of the GAME a prop or special belongs to (a player_props row's eventId is the prop's own id). Lets you join props to that game's h2h/spreads/totals rows. Absent when the record has no game (futures) |
outcomeLabel | string | Human label (e.g. "Over 2.5", "Manchester City") |
price | float | Decimal odds (canonical internal format) |
priceAmerican | int | Always populated (e.g. -110, +180) |
priceFractional | string | Always populated (e.g. "10/11", "7/4") |
impliedProbability | float | 1 / price (raw — still includes the vig) |
overround | float | null | The market's total margin: Σ(1/price) − 1 (e.g. 0.045 = 4.5% vig). null for an incomplete market line |
noVigProb | float | null | Vig-free fair probability — the implied prob divided by the market overround so a line's outcomes sum to 1. The true-probability estimate sharps actually bet against |
noVigFairPrice | float | null | Vig-free fair decimal odds (1 / noVigProb) — the book's price with its margin stripped out (on Pinnacle, the CLV / +EV benchmark number). Computed per market for h2h, spreads, totals and specials when includeFairOdds is on |
noVigSource | string | null | Where noVigFairPrice came from: smarkets when it was anchored to the exchange price for the same fixture, market and line (no bookmaker margin), book-devig when it is this book's own prices de-vigged. Only present when includeFairOdds is on AND smarkets is among your books |
edgePct | float | null | With includeFairOdds and 2+ books including pinnacle or smarkets. How far this row's price is above (+) or below (−) the sharp no-vig price for the same fixture, market and line: (price × sharp fair probability − 1) × 100. The sharp price is the Smarkets exchange's when it quotes the whole market (same outcomes, same lines), otherwise Pinnacle's own de-vig. Null when neither quotes that market in full — never a guess — on the reference book's own rows and on exchange rows. Pre-match prices only — null on every in-play row |
sharpFairPrice / sharpSource | float / string, each | null | The sharp no-vig price edgePct compares against, and which book it came from (smarkets or pinnacle). Same rows as edgePct |
crossBookMarginPct | float | null | Same runs. The market's margin at the best price per outcome across the books that quote it in full (at least two): (Σ 1/best price − 1) × 100, an exchange price taken net of its 2% commission. Below 0, those best prices sum below 100% as read this run. A quote that is not a market price (exchange sides summing below 100%, or under 50 units behind a side) is left out. Pre-match prices only — null on every in-play row |
exchangeLiquidity | float | Smarkets only. Money resting behind this exact price, in whole currency units (the API reports hundredths). Tells a real quote apart from a two-unit ghost. Absent on bookmaker rows |
exchangeLayPrice | float | Smarkets only. Best LAY price (decimal) on the other side of the order book; price is the best BACK price, and the gap between them is the exchange's spread. Absent on bookmaker rows |
maxRiskStake | float | null | Pinnacle only. Pinnacle's maximum risk stake on this market, as its public guest API reports it (the API names no currency); both sides of a market carry the same limit. Null when the market carries no limit; absent on the other books' rows |
isBestPriceAcrossBooks | bool | True if this is the highest price for this (event, market, outcome, line) and at least two of your books quote that outcome and line; every tied row is flagged. False when no other book quoted it |
scrapedAt | ISO 8601 | UTC timestamp when the snapshot was captured |
What this Actor is NOT
Honest scoping so you don't waste a run finding out:
- NOT US bookmakers. DraftKings / FanDuel / BetMGM / Caesars / ESPN BET / William Hill US are geo-gated behind Akamai and require US residential proxy, pushing per-snapshot cost ~10x. Other Apify Actors target those — this one stays out.
- NOT a managed data feed. Pinnacle's Terms of Service forbid commercial resale of raw odds. You are buying a SCRAPER (infrastructure that runs in YOUR Apify account, executes per-buyer), not a centralized data feed redistributed to many. See "Compliance posture" below.
- NOT real-time WebSocket. This is a poll-based scraper, not a streaming feed. The fastest meaningful cadence is ~60s (limited by both API rate-tolerance and the value of polling faster than the book's own line updates).
- NOT a guaranteed-uptime SLA. Pinnacle occasionally restructures their guest API path; the Actor has defensive degradation (graceful empty-result on schema drift, adaptive backoff on burst errors) and gets defensive patches when a book changes its schema (Bet365 is currently under repair) — but it's not Bloomberg.
- NOT a depth-of-book feed. Smarkets IS included as of 2026-09-23, but as ONE row per runner carrying the best back price and the money resting behind it — not the whole order book. Every price level as its own row would triple the bill for the same information. Betfair Exchange is a separate surface and is still on the roadmap.
Proxy configuration — datacenter friendly
This Actor's default is plain useApifyProxy: true (no specific group required). What you pay is only the events above; the proxy choice does not change your bill. Notes:
- Pinnacle's publicly accessible odds surface tolerates datacenter IPs by design — that's the entire reason Pinnacle is on the supported-books list, and the default proxy setting already uses them. Leave
proxyConfigurationalone: there is no Apify proxy group literally namedDATACENTER, and passing one makes the run refuse to start. Pinnacle does block individual exit ranges from time to time; the Actor rotates exits and, if a sport still comes back with nothing, says so in the run status. If you see that repeatedly, setapifyProxyGroups: ["RESIDENTIAL"]for that run. - Residential is also fine and is the most universal fallback. Pinnacle responds 200 on plain residential without any cookie / fingerprint setup.
- If your run logs show persistent
backoff-activeorthrottledstatus on thepinnaclebucket, switch to residential or lower your scheduled-run frequency.
FAQ
Q: What does this Pinnacle scraper cover? Pinnacle h2h, spreads, totals and specials (futures, yes/no, exact totals, first-to-score, winning margin, team props, over/under player props), plus per-game player props and team totals, with American, decimal and fractional prices, implied probability on every row, and cross-run dedup state.
Q: Is this a Pinnacle API alternative? It IS a wrapper over Pinnacle's publicly accessible guest API, normalized into a single per-market-outcome snapshot schema with both American AND decimal AND fractional price formats AND implied probability on every row. Plus the specials bucket (which Pinnacle exposes but is annoying to flatten yourself) and cross-run dedup state (which a one-off API client doesn't get for free).
Q: When does Bet365 come back? Bet365's publicly accessible odds surface returned HTTP 403 across every variant we probed in May 2026 (8 paths × datacenter AND USA residential). The path itself is dead, not just IP-blocked — even their public homepage 403s. Bet365 will return when either (a) their anti-bot posture relaxes, or (b) the scraping community documents a new entry point. Cross-book comparison already works today with Pinnacle + Bovada.
Q: How does this compare to The Odds API? The Odds API wraps ~80 books behind one paid endpoint at $30/$59/$119/$249/mo tiers. This Actor gives you DIRECT scraped data from Pinnacle (the usual sharp-line reference) without a middleman, at PPE pricing that costs less than The Odds API only at low capture volumes (see the cost formula above). It does NOT give you DraftKings or FanDuel — for those you need The Odds API or a US-residential scraper.
Q: Is this an OddsJam alternative pay-per-use? For OddsJam users whose primary value is line-tracking on a fixed cron, it can be. Your bill here follows cadence and fixture count (two pre-match captures a day on 20 fixtures ≈ $81/mo; a 5-minute cron costs far more — see the Schedule table), against OddsJam Gold's flat $249/mo. Specials are billed at $0.05/snapshot. For OddsJam users whose primary value is the proprietary EV models on top of the data, no — this Actor sells you the data, not the model.
Q: Can I use this for sports arbitrage?
Yes. Pinnacle and Bovada are both live, so one run compares the same fixture across a sharp and an offshore book — the run status tells you how many fixtures matched across books. Compare price across books on matched fixtures; isBestPriceAcrossBooks flags the best price only when at least two of your books quote that outcome and line. With includeFairOdds on, a pre-match market that two or more of your books quote in full (same outcomes, same lines) also carries crossBookMarginPct — below 0 when the best prices across your books sum below 100% — and a bookmaker row whose market Pinnacle or the exchange quotes in full carries its edgePct against that no-vig price; every other row keeps both null (on the saved four-book NHL feeds, 60 of 156 rows got an edge). In-play rows never get either: the books are read one after another and an in-play price moves by the second. Both numbers are the prices as this run received them, not a guarantee of how fresh each book's price was — a feed can lag (Bovada's coupon is served through a CDN cache), so re-check before staking.
Q: Can I use this for in-play odds API workflows?
Yes — mode: "live_only" with a 60-180s cron. isLive is set on every snapshot; matchClock and matchScore are filled on Kambi's (Unibet/Paf) in-play rows only and stay null on the other books. Be aware the live tier charges $0.025/snapshot vs $0.01 pre-match.
Q: What ARE these "specials"?
Pinnacle's withSpecials=true matchups call returns thousands per sport in soccer and a few hundred in American football (280 measured 2026-09-15, including over/under player props such as a quarterback's total interceptions): "First Team To Score" (3-way), "Win to Nil 1st Half" (Yes/No), "Exact Total Goals 1st Half" (multi-way), "Winning Margin" (per scoreline), and lots of team prop variants. They are the segment soft books are slowest to sharpen up on, so they have the most edge for value bettors and exact-totals modellers.
Q: Do I need a Pinnacle account login? No. The Actor reads only data that is publicly displayed on Pinnacle's anonymous guest API (the same API their own marketing-site widgets use). Never set up account-authenticated cookies you don't want exposed to a scraper.
Q: Does this work for esports?
Pinnacle covers esports (CS2, Dota 2, LoL, Valorant), but only a subset of leagues are reliably priced pre-match — most esports volume is in-play. Use sports: ["esports"], mode: "pre_match_and_live".
Compliance posture
This Actor is a scraper tool sold to bettors who run it in their own Apify account for personal analysis, not a centralized commercial data feed.
- Personal-analysis use only. Pinnacle's Terms of Service forbid commercial redistribution of raw odds. Buyers run their own instance against their own polling cadence — the architecture is per-buyer-execution by design.
- No login bypass. The Actor accesses only data that is publicly displayed on Pinnacle's anonymous guest API. It will not authenticate to your account.
- Rate-limited politely. Adaptive backoff prevents burst-traffic that would degrade Pinnacle's service for real users. If a buyer configures an aggressive cadence the backoff will throttle automatically.
- Respect local law. Sports betting and odds-data scraping are legal in many jurisdictions but not all. Confirm before using.
- Do not resell the data feed. If you want to redistribute scraped odds commercially, you need a licensed data agreement with Pinnacle — this Actor is not that agreement.
Roadmap
- v0.3 (shipped, current). Pinnacle h2h + spreads + totals + specials; Bovada live since 2026-08-29; cross-book fixture matching.
- v0.4 —
Smarkets adapterSHIPPED 2026-09-23. UK betting exchange, public API, no auth. Setbooks: ["smarkets"]. Peer-to-peer pricing genuinely different from Pinnacle: h2h, spreads and totals, one row per runner withexchangeLiquidity/exchangeLayPrice, and it anchorsnoVigFairPricefor every fixture your other books also cover. See the Smarkets section above for what it does not carry. - v0.5 —
Pinnacle alternate linesSHIPPED. SetincludeAlternateLines: true. It needed no per-matchup fan-out after all: the samemarkets/straightcall returns the whole ladder onceprimaryOnlyis dropped. Measured 2026-08-07 on billable pre-match rows: ×1.66 soccer, ×2.59 tennis, ×3.24 basketball, ×1.58 baseball, ×1.00 ice hockey. Pinnacle's first half / first set (period 1) is served asfirst_half_*since 28 September 2026, with or without alternates; quarters are still open. - v0.6 — Betfair Exchange adapter (bring-your-own-credentials, opt-in tier for buyers who already have a Betfair account + App Key).
- v0.7 — WebSocket mode where the book supports it, for sub-second in-play cadence.
- v0.8 — Automatic arb finder. The numbers ship inside
includeFairOddssince October 2026 (edgePct,crossBookMarginPct); an output filter that emits only those rows is not built.
Changelog & data corrections
Moved here from the top of the page in October 2026 so the page opens on what the Actor does. Nothing below was shortened.
Bovada: no more day-old cached coupons (7 October 2026). Bovada's CDN can hand over a cached copy of a coupon that is hours old: measured on 7 October, the hockey coupon came back with its newest price change 26.6 h old, and the same request 3 minutes later with a change 1 minute old. A Bovada coupon whose newest price change is older than 30 minutes is now requested once more after a 3-second pause, and the fresher copy is used. A fresh coupon makes one request, exactly as before; a quiet coupon that really has not changed costs one extra request and returns the same rows.
League-first form and edge fields (October 2026) — what changes in your rows. Prices did not change.
- New input
leagues(NFL, NCAAF, NBA, WNBA, NHL, MLB). Each league sets its sport and filters that sport to its own league, so["NCAAF", "NBA"]does not ask basketball for "NCAA" the way leagueFilter does — on the saved feeds, the leagueFilter route billed 6 rows of Pinnacle's "Philippines - NCAA" basketball. Withleaguesset,sportsandleagueFilterare not used and the status says so (forsports, unless it is the soccer + tennis pair the platform also sends when the field is omitted). Two leagues of one sport share itsmaxEventsPerSport, and the status says when one of them got no slot. Default[]: API calls and saved Tasks that omit it run exactly as before. Soccer and tennis competitions are not in the list because the books' labels cannot separate them (Bovada labels the Premier League of 15+ countries plain "Premier League" and tennis by tournament).- The form opens on
leagues(NFL, NBA, NHL), 8 games each: 144 rows, about $1.49, against $1.85 for the previous NFL + NHL at 15 — measured on the same saved feeds. Rarely changed fields are folded under "Advanced". No default moved.includeFairOddsaddsedgePct,sharpFairPrice,sharpSourceandcrossBookMarginPcton runs with 2+ books including Pinnacle or Smarkets, on pre-match prices only. Samefair-odds-enrichmentevent and price, no extra request; the fields only appear on those runs, so every other run's rows keep their shape. Measured on the saved 28 Sep NHL feeds of four books: 60 of 156 rows got an edge (52 against the exchange, 8 against Pinnacle); no market's best prices summed below 100%.- Data correction — fixtures two books list with home and away the other way round (tennis players in a different order, neutral-venue games). The cross-book matcher has always paired them, but
isBestPriceAcrossBooksand the Smarkets anchor ofnoVigFairPricethen compared one book's "home" price with the other's "home" — a team's price with its opponent's. They are now compared team for team, and a listing whose order cannot be told is left out of the comparison. Not measured on past runs: none of the saved NFL, NBA, NHL, college football or Bovada + Kambi soccer feeds had such a pair; runs with 2+ books on tennis are where it could have shown.leagues: ["NBA"]means the NBA only — labels with "G League", "Summer League" or "2K" are left out (the word-start rule alone would let them in once those seasons open).leagueFilter ["NBA"]is unchanged.
Status note (August 2026): This Actor is now multi-book. Pinnacle remains the reference sharp book (h2h, spreads, totals, AND its specials — thousands per sport in soccer, hundreds in NFL), Bovada is live as of 2026-08-29 — a major US offshore book, keyless JSON verified from datacenter IPs, pre-match and live moneyline/spread/total across tennis, soccer, NFL, NBA, MLB and NHL — and Kambi adds Unibet and Paf. Smarkets joined on 2026-09-23 as the one betting exchange in the feed: no bookmaker margin, the money resting behind every price on the row, and — with
includeFairOddson — the anchor for the fair price of any fixture your other books also cover. Setbooks: ["pinnacle", "bovada"]for the sharp-baseline-vs-offshore pair every +EV workflow compares, add"smarkets"for a margin-free reference, or all of them for the widest best-price sweep. The default stays["pinnacle"], so nothing changes for existing runs unless you opt in. Bet365 remains under repair. Per-game player props come from Pinnacle (marketTypes: ["player_props"], since 28 September 2026).
Fix note (21 August 2026) — your soccer runs now return the fixtures you asked for. Pinnacle changed its matchups endpoint: it now returns its
specialrecords (futures, yes/no, team props) even when they are not requested. In soccer those are 92% of the list — 19,707 of 21,395 entries — and this Actor was applying yourmaxEventsPerSportto the raw list before separating them out. The result: a run asking for 50 soccer fixtures was spending 44 of those 50 slots on records it then discarded, and emitting one event. Measured on the same input before and after the fix: 22 rows → 278 rows, 1 fixture → 47. Nothing about pricing changed ($0.01 per pre-match snapshot, as before), but because every snapshot is billed, a soccer run at the default cap now costs about $2.78 instead of $0.22. If that is more volume than you want, lowermaxEventsPerSport— it now means what it says. Tennis and the other sports were never affected. Runs from build 0.3.26 onward.
Data corrections (September 2026) — what changes in your rows. Prices did not change.
- Line-movement history restarts once, on every row of every book. The dedup/movement key now includes the book's own market id, so no row can inherit a different market's previous price. On your first run after this build
previousPrice,priceChange,moveDirection,firstSeenPriceandmoveSinceFirstSeenare absent; from the second run they are correct again.- Pinnacle away spread rows now carry their own line (e.g.
+3.0instead of-3.0); theirsnapshotIdchanges.isBestPriceAcrossBooksis only set when at least two books quote the same outcome and line.- Bovada spreads now get fair odds when
includeFairOddsis on (they came back empty), so those rows now also billfair-odds-enrichment.- Bovada Asian quarter lines show the real line (
-1.25, not-1.0).- Pinnacle 1st-half and 1st-quarter lines are no longer emitted as full-game
h2h/spreads/totalsrows. This applies to every Pinnacle run, with or withoutincludeAlternateLines: measured on the saved NFL payload (h2h + spreads + totals), a default run atmaxEventsPerSport50 goes from 446 to 284 rows and one with alternates from 2,262 to 1,620 — all of them correctly labelled, and billed accordingly. If you need those rows by period, open an Issue on this Actor's page; do not read the drop as missing data.- Pinnacle over/under specials (per-game player props, season win totals) are now included: NFL specials went from 834 to 1,002 rows at full depth on the same payload (measured 2026-09-15).
- Bovada events listed as pre-match that had already started are skipped and not charged; the run status gives the count.
- Kambi: American football now returns rows,
bookshows the operator (Unibet, Paf…) and outcomes arehome/away. Ice hockey is requested too, but Kambi lists only regulation-time hockey markets (a 3-way result and regulation totals), which are skipped rather than mixed with full-game lines, so it returned nothing — fixed on 28 September, see below. Kambi history restarts once.
NBA & NHL (28 September 2026) — what changes in your rows. Prices did not change. Every number below was measured on the four books' own feeds captured that day and replayed offline through this build.
leagueFilter: ["NBA"]now means the NBA. It used to match "WNBA" too: that day an NBA filter returned 4 WNBA games on Bovada, 2 on Pinnacle and nothing but WNBA on Smarkets, and withspecialson it billed Pinnacle's WNBA player props. A league code typed in capitals (2-5 letters or digits:NBA,NHL,NCAA) now has to start a word of the book's league label —NBAstill matchesNBAandNBA Preseason, notWNBA;NCAAstill matchesNCAAF. Any other term keeps the case-insensitive substring match (ligastill findsBundesliga).player_propsreturns rows — from Pinnacle. Each row is one player's over/under for one game, with that game's teams inhomeTeam/awayTeamand four new columns:playerName,propStat,propDescriptionandparentEventId(the game's Pinnacle id). That day Pinnacle listed 205 of them in basketball, all WNBA playoffs, and none yet for the NBA or NHL; a run that finds none says so in its status and bills nothing for them. Sameodds-snapshot-player-propprice as specials. With bothspecialsandplayer_propsselected, a prop comes back once, asplayer_props.specialsrows keep their shape and gain the same columns where they apply. In soccer Pinnacle's player props are yes/no bets ("Harry Kane To Score"): those rows havepropStatandplayerNameempty rather than a made-up stat.- Pinnacle
first_half_*returns rows withoutincludeAlternateLines. It returned none: the default request carries full-game markets only. Measured: 252 first-half rows on 42 basketball fixtures. Those runs download Pinnacle's larger all-periods document and drop its alternate rungs unless you asked for them; runs withoutfirst_half_*send exactly the same request as before.- Ice hockey has no halves.
first_half_*is no longer returned forice_hockey: with alternates on, Pinnacle's 1st-period lines came out labelled first half (74 rows on 5 NHL games). The run status says so. The same goes for esports, where Pinnacle's first part of a match is map 1 (it would have been 176 rows on 50 fixtures). Whatfirst_half_*means elsewhere on Pinnacle: the first set in tennis and the first 5 innings in baseball.- Kambi (Unibet) prices ice hockey. Its list carries only regulation-time lines, so the full-game moneyline, puck line and total (overtime and shootout included, main line only) are read from each fixture's own page — one request per fixture, at most 30 per run, Unibet only. Checked on NHL, Czech Extraliga and Swiss NLA fixture pages.
- Kambi keeps women's fixtures. Teams written "(W)" were dropped as simulated e-sports matches — all 4 WNBA playoff games that day. They are real games and bill like any fixture, and they also pass a league filter that matches their league name:
["Bundesliga"]now includes "Bundesliga (W)" on Kambi, as it already did on the other books.- The status names a crowded-out league. Without a
leagueFilter,basketballat the form's cap of 15 returned 0 of Bovada's 17 NBA games (Euro Cup, WNBA and Euroleague come first in its feed). The run status now says so and names theleagueFilterthat gets them — with every sport's main league together on a multi-sport run (["NBA","NHL"]), because the filter applies to every sport in the run.
⭐ Review CTA
A review, good or bad, helps other buyers decide: apify.com/zhorex/sports-odds-aggregator/reviews.
For sharp-bettor research and finding new betting markets, see also the companion AI research Actor: zhorex/perplexity-ai-scraper.
Last updated: October 2026 — built and maintained by zhorex. Report breakage in the Issues tab.