If you bet sharp lines, the only book that genuinely matters for fair-value is Pinnacle. Every EV model, every CLV report, every "did I beat the close?" check eventually compresses down to one question: what was Pinnacle showing on this market at T-1?
For years the standard way to get that feed was The Odds API ($249/mo for 15M credits) or OddsJam Gold ($249/mo, $499+ for Pro). For a tipster shop polling 100 fixtures a day that math is tolerable. For a solo bettor running CLV on 20 fixtures it's overspend. For a specials trader it's worse — OddsJam gates futures and yes/no markets behind their highest tier and The Odds API doesn't surface most of them at all.
There's now an Apify Actor that does the same job pay-per-snapshot:
zhorex/sports-odds-aggregator — Pinnacle h2h + spreads + totals + 5,000+ specials per sport, from $0.01 a snapshot. Datacenter-proxy friendly. No login, no monthly minimum.
This post is the playbook: four recipes that show exactly how to run it, what each costs, and where the savings show up vs. the SaaS-incumbent pricing.
Quick note on naming: the Actor's title still references Bet365 because Bet365 is the second-book slot, but Bet365's public mobile-web path is under repair as of May 2026. Pinnacle is shipping today, and the moment Bet365 returns the cross-book best-price flag (
isBestPriceAcrossBooks) and fuzzy event-matching activate automatically — no input change on your side.
Pricing in plain English
Four event types, billed pay-per-event (PPE):
| Event | Price | When it fires |
|---|---|---|
odds-snapshot-pre-match |
$0.01 / snapshot | One market-outcome from a scheduled (not in-play) fixture |
odds-snapshot-live |
$0.02 / snapshot | One market-outcome from a live (in-play) match |
odds-snapshot-player-prop |
$0.04 / snapshot | One special / future / yes-no / team prop / exact-totals row |
scheduled-run |
$0.05 / run | Once per cron tick — often fully offset by the dedup window |
A typical pre-match fixture with ["h2h", "spreads", "totals"] emits ~7 snapshots (3 h2h outcomes + 2 spreads + 2 totals). Add "specials" and you get an extra 30–80 rows per fixture — yes/no markets, exact totals, first-team-to-score, winning margin per scoreline, team props.
The bit that turns this from "interesting" to "actually cheap": the deduplicationWindowSeconds setting suppresses snapshots when the line hasn't moved. On stable mid-week Premier League pre-match polls you typically charge for 5–15% of "naïve" volume. A 60-second cron on a stable line is essentially free.
Recipe 1 — Pinnacle closing-line value (CLV) tracker
The recipe that pays for the Actor in its first weekend.
Setup:
mode: "pre_match_only"- Trigger at T-30 minutes and T-1 minute per fixture
- Bet your soft book at T-30, log Pinnacle's T-1 close, compute CLV per ticket
Pinnacle's closing line is the canonical sharp benchmark. If you're consistently beating Pinnacle's close, your edge is real. If you aren't, you can stop pretending — CLV is the ground truth of whether you're a winning bettor or a noise-trader.
Cost for 200 fixtures/week (h2h+spreads+totals, ~7 snapshots × 2 polls each): ~$65/month.
The Odds API doesn't expose "Pinnacle close at T-1" as a first-class field, so you're paying $249/mo for the feed and still rolling your own snapshot scheduler. Here the snapshot scheduler (Apify cron) and the snapshot itself together come in at ~25% of the price.
Recipe 2 — EV-model live edge harvester
The model-on-top use case. If you have a fair-value model and you harvest the moments where book_price × your_fair_value > 1.03, you want a polling firehose during in-play, not an hourly dump.
Setup:
{
"books": ["pinnacle"],
"sports": ["basketball", "tennis", "soccer"],
"marketTypes": ["h2h", "spreads", "totals"],
"mode": "live_only",
"deduplicationWindowSeconds": 0
}
Schedule: 60-second cron during target match windows.
Volume: ~50K live snapshots/month × $0.02 + orchestration ≈ ~$1,080 / month.
That looks pricey until you put it next to OddsJam Pro at $499+/mo for a SaaS API you don't control and that throttles by tier. The trade is: you pay more per request, but you pay only for what you actually consume, you set the cadence, and a stable line costs you nothing.
The other thing the SaaS won't sell you: every snapshot includes isLive, matchClock, and matchScore. Your model doesn't have to join against a separate scoreboard feed during a live NBA fourth quarter.
Recipe 3 — Specials sniper (the OddsJam gating trick)
For value bettors and exact-totals modellers. This is the recipe where the pricing gap gets embarrassing.
Pinnacle's withSpecials=true matchups call returns ~5,000 markets per major sport:
- First Team To Score (3-way)
- Win to Nil 1st Half (yes/no)
- Exact Total Goals 1st Half (multi-way)
- Winning Margin per scoreline
- A long tail of team props and player-related markets
These are the markets soft books are slowest to sharpen up on — which is where the actual edge lives. OddsJam gates futures and props behind their highest tier. The Odds API doesn't surface most of them.
Setup:
{
"books": ["pinnacle"],
"sports": ["soccer"],
"marketTypes": ["specials"],
"mode": "pre_match_only",
"deduplicationWindowSeconds": 60
}
Schedule: 4-hour cron during the season.
Volume: ~6K specials snapshots + 180 runs ≈ ~$250 / month for the segment that powers the largest EV pockets in retail sports betting.
A pattern that works: filter the dataset to marketType == "specials" && impliedProbability < 0.10. Pinnacle longshots above 10× implied with sharp money backing are where the soft-book mispricings concentrate.
Recipe 4 — Tipster Discord auto-poster
The cheapest one and the easiest to sell to a small operation.
Setup:
sports: ["soccer"]leagueFilter: ["UEFA", "EPL", "La Liga"]mode: "pre_match_only"- Every 6 hours, webhook → Discord
Cost: ~$30/month for a daily top-10 spreads + totals digest piped straight into the channel. If you currently screenshot OddsJam into Discord by hand, this is the upgrade.
The pay-per-event math in one table
| Workflow | Volume | Monthly cost | Replaces |
|---|---|---|---|
| Casual bettor — daily 9am pre-match dump, 30 fixtures | ~900 snapshots | ~$11 | $59/mo Odds API tier |
| CLV tracker — T-30 + T-1, 80 fixtures/wk | ~3.2K snapshots | ~$65 | $249/mo OddsJam Gold |
| Tipster shop — 100 fixtures × 7 outcomes, hourly | ~21K snapshots | ~$245 | $249/mo OddsJam Gold (parity) |
| Specials trader — daily soccer sweep | ~6K snapshots | ~$250 | Highest-tier gate (not available below) |
| EV model — live NBA + tennis + soccer, 90s cron | ~50K live snapshots | ~$1,200 | OddsJam Pro $499+ + you control cadence |
Flat-rate SaaS wins only once you cross ~150K snapshots/month of stable workload. Below that — which is most solo sharps, most tipster operations, and every specials trader — PPE is just cheaper, and the cost curve is linear in actual usage rather than tier-jumpy.
The other PPE advantage that quietly compounds: there's no annual contract. Off-season for a sport? Cron stops, billing stops. You don't pay for unused capacity in August when soccer is dead.
Three steps to a running cron
Step 1 — Pick your sports and markets.
Defaults are ["soccer", "tennis"] — the two highest-liquidity sharp markets year-round. For CLV add "spreads", "totals". For specials sniping add "specials". The full sport list is 11 deep (soccer, tennis, basketball, MMA, baseball, hockey, esports, AFL, NFL/college, golf, rugby).
Step 2 — Run once with default input and verify Pinnacle returns data for your sport+league pick. Output lands in your Apify dataset.
Step 3 — Save as Task → Schedules → New Schedule with the cron string you want:
*/5 * * * * pre-match every 5 minutes
* * * * * live every minute during target windows
0 9 * * * daily 9am pre-match dump
0 */6 * * * every 6 hours
0 10 * * 6 Saturday morning weekly audit
Attach a webhook to the schedule and ship the dataset into your EV pipeline, Discord/Slack bot, Sheets workbook, or wherever your model lives.
Python in 12 lines
from apify_client import ApifyClient
client = 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:
evaluate_for_bet(snapshot)
That's the whole integration. Every snapshot arrives in a flat per-market-outcome shape with priceAmerican, priceFractional, price (decimal), impliedProbability, and isBestPriceAcrossBooks on every row — your model doesn't have to do format gymnastics or join against a separate American-odds conversion table.
What a snapshot looks like
{
"snapshotId": "a1b2c3d4e5f6789012345678",
"book": "pinnacle",
"sport": "soccer",
"league": "Premier League",
"homeTeam": "Manchester City",
"awayTeam": "Liverpool",
"commenceTime": "2026-05-22T19:00:00Z",
"isLive": false,
"marketType": "h2h",
"outcomeKey": "home",
"outcomeLabel": "Home",
"price": 1.91,
"priceAmerican": -110,
"priceFractional": "10/11",
"impliedProbability": 0.52356,
"isBestPriceAcrossBooks": true,
"scrapedAt": "2026-05-18T14:32:00Z"
}
snapshotId is a stable sha1 derived from book+event+market+outcome+timestamp, so it makes a clean primary key if you're persisting into Postgres / DuckDB.
For high-volume operations
If your monthly burn is past 50K snapshots and you need a dedicated polling cadence, custom market types (Asian handicap quarter-lines, derivative props, fancy bets), or a schema SLA for a downstream production pipeline, the Actor page has an "Enterprise inquiry" pointer. Webhook integrations, dedicated proxy pools, and custom dataset views ship in roughly a week. Sustained seven-figure-action operations can talk dedicated-instance posture.
For everyone else the default Apify Proxy works on Pinnacle's guest API — Pinnacle's public surface tolerates datacenter IPs by design (which is why it's on the supported-books list to begin with). If your plan includes datacenter, override apifyProxyGroups: ["DATACENTER"] and your proxy cost drops to roughly 5% of a residential-default scraper.
Things worth knowing before you run it
- Not US bookmakers. DraftKings / FanDuel / BetMGM / Caesars / ESPN BET are geo-gated behind Akamai and need US residential proxy, which kills the per-snapshot economics. Other Apify Actors target those — this one stays out.
- Personal-analysis use only. Pinnacle's TOS forbids commercial redistribution of raw odds. The architecture is per-buyer-execution — you run it in your own Apify account against your own polling cadence. Don't resell the feed.
- Not a streaming WebSocket feed. Poll-based, fastest meaningful cadence ~60s.
- Bet365 returns when Bet365 returns. Cross-book best-price flag and fuzzy event-matching are already in the codebase; the day a second book ships, arb infra activates without an input change.
Where to start
If you currently pay The Odds API or OddsJam Gold $249/mo for the Pinnacle column, the cheapest experiment is:
- Spin up the Actor with the default input.
- Run it on five of your usual fixtures.
- Compare the snapshots against whatever your incumbent feed gave you for the same fixtures.
The break-even comes faster than you'd expect — most workflows under 150K snapshots/month earn back the SaaS subscription inside the first month, and the dedup window keeps marginal cost near zero on stable lines.
Actor link: apify.com/zhorex/sports-odds-aggregator
If the Actor saves you a month of OddsJam Gold, the single highest-leverage thing you can do back is a 30-second review on the Actor page — it directly funds the next defensive patch when the books shift their schemas.
Roadmap is public: Smarkets adapter (v0.4) reactivates cross-book arb infra, Pinnacle alternate-lines / period markets (v0.5) opens half/quarter handicap decomposition, Betfair Exchange BYO-credentials (v0.6), WebSocket mode (v0.7), automatic arb finder (v0.8).
Top comments (0)