Stake API: 124 CS2 Markets per Match, Mirrored by 9 Feeds
If you searched for a Stake API, you want Stake’s lines as JSON, next to the books you already price against. We found no public sportsbook odds API from Stake. Its developer program, Engine, is for casino game studios and carries no sports data. OddsPapi carries the book under the stake slug, in the same schema as Pinnacle and 350+ other bookmakers, and one call returns it.
The Stake board runs deepest on esports. On 16 October 2026, Stake listed 122 to 124 active markets on the six tier-one CS2 matches we read, the third-widest menu on five of them (boards of 102 to 145 books). Nine other slugs quoted 71% to 100% of those same prices. This guide pulls the Stake board, finds the feeds that mirror it, and checks Stake’s closing line on 80 played fixtures.
Why a Stake scraper gives you one book and nine blind spots
A scraper on stake.com gives you one site’s markup to maintain and one book. It does not tell you that duelbits, gamdom, nextbet, dafabet, betsson, betsafe, nordicbet, inkabet and betfirst.be quote most of the same CS2 prices. If you put all ten into a consensus, one price counts up to ten times. Our consensus odds guide covers why that pulls your fair line toward one trader.
OddsPapi gives every book a bookmakerFixtureId and a fixturePath deep link to the event on Stake’s own site. Stake’s event ID matched no other slug on any board we read, so the price set is the only way to find its twins. Step 3 does that.
| Scrape stake.com | OddsPapi stake |
|
|---|---|---|
| Access | No public sportsbook API, so you parse the site | REST and WebSocket, one apiKey query parameter |
| Other books | None | 350+ bookmakers in the same call, Pinnacle included |
| Twin feeds | Invisible | Compare price sets across the full board (Step 3) |
| Schema | Stake’s own markup | One JSON shape for every book, with native market IDs for maps, handicaps and props |
| Played fixtures | Only what your recorder saved | /v4/historical-odds on the free tier |
Step 1: find the Stake slugs in the catalogue
Authentication is one query parameter, apiKey. The helper waits out a 429 for as long as the error body says, backs off on a 5xx, and returns None on a 404.
import os, time, requests
from datetime import datetime, timedelta, timezone
from statistics import median
API_KEY = os.environ["ODDSPAPI_API_KEY"]
BASE = "https://api.oddspapi.io/v4"
def get(path, **params):
params["apiKey"] = API_KEY
for _ in range(8):
r = requests.get(f"{BASE}/{path}", params=params, timeout=180)
if r.status_code == 429: # rate limited: wait as told
time.sleep(r.json().get("error", {}).get("retryMs", 2000) / 1000 + 0.5)
continue
if r.status_code >= 500: # transient: back off and retry
time.sleep(2)
continue
if r.status_code == 404: # empty window
return None
r.raise_for_status()
return r.json()
raise RuntimeError(f"gave up on {path}")
books = get("bookmakers")
print(f"{len(books)} bookmakers in the catalogue\n")
# every slug with "stake" in the slug, the name, or the clone pointer
for b in books:
if "stake" in f'{b["slug"]} {b["bookmakerName"]} {b["cloneOf"]}'.lower():
print(f'{b["slug"]:<13} {b["bookmakerName"]:<10} '
f'liveOdds={b["liveOdds"]!s:<5} cloneOf={b["cloneOf"]}')
366 bookmakers in the catalogue
31bet 31Bet liveOdds=True cloneOf=mystake
freshbet FreshBet liveOdds=True cloneOf=mystake
goldenbet GoldenBet liveOdds=True cloneOf=mystake
jackbit JackBit liveOdds=True cloneOf=mystake
mystake MyStake liveOdds=True cloneOf=None
rolletto Rolletto liveOdds=True cloneOf=mystake
stake Stake.com liveOdds=True cloneOf=None
stake.bet.br Stake BR liveOdds=True cloneOf=None
velobet VeloBet liveOdds=True cloneOf=mystake
Two slugs belong to Stake. stake is stake.com, the international site, and every snippet below reads it. stake.bet.br is the Brazil-licensed site. It carries its own event ID, which it shares with 39 slugs from the Kambi family, among them unibet and betrivers, and it quotes its own prices. Our Unibet API guide shows how to group that family.
The other seven rows are a different brand. MyStake is not Stake, and the catalogue flags six sites as its clones: 31bet, freshbet, goldenbet, jackbit, rolletto and velobet. If you filter on the substring “stake”, drop them.
Step 2: read the Stake menu across a slate
This step reads the next three fixtures in five tournaments and prints two numbers per book: how many markets carry an active price, and the margin on the headline market. It asks for stake and pinnacle only, which keeps each payload small. A margin counts only when every leg is active.
# Reuses get() and the imports from Step 1.
# label -> /fixtures filter and the market that carries the headline price
SLATE = {"Premier League": ({"tournamentId": 17}, "101"), # 1X2
"Serie A": ({"tournamentId": 23}, "101"),
"NHL": ({"tournamentId": 234}, "151"), # Winner incl. OT
"NFL": ({"tournamentId": 31}, "141"), # Winner incl. OT
"CS2": ({"sportId": 17}, "171")} # Winner
BOOKS = ["stake", "pinnacle"]
def active_markets(book):
"""Market IDs where the book quotes at least one active price."""
return {mid for mid, m in book["markets"].items()
if any(p.get("active") for o in m["outcomes"].values()
for p in (o.get("players") or {}).values())}
def overround(book, market_id):
"""Margin in % on one market, or None unless every leg is active."""
m = book["markets"].get(market_id)
if not m:
return None
legs = [o["players"]["0"] for o in m["outcomes"].values()]
if not all(l.get("active") and l.get("price") for l in legs):
return None
return (sum(1 / l["price"] for l in legs) - 1) * 100
now = datetime.now(timezone.utc)
window = {"from": now.strftime("%Y-%m-%d"),
"to": (now + timedelta(days=4)).strftime("%Y-%m-%d")}
for label, (flt, market_id) in SLATE.items():
fixtures = get("fixtures", **flt, **window) or []
shown = 0
for f in sorted(fixtures, key=lambda f: f["startTime"]):
if shown == 3 or not f["hasOdds"] or f["startTime"] <= now.isoformat():
continue
odds = get("odds", fixtureId=f["fixtureId"], bookmakers=",".join(BOOKS))
time.sleep(1)
board = (odds or {}).get("bookmakerOdds", {})
stake = board.get("stake")
if not stake or len(active_markets(stake)) < 20: # skip thin boards
continue
shown += 1
pin = board.get("pinnacle")
sm, pm = overround(stake, market_id), pin and overround(pin, market_id)
name = f'{f["participant1Name"]} v {f["participant2Name"]}'[:32]
print(f'{label:<15} {name:<32} stake {len(active_markets(stake)):>3} mkts '
f'{sm and f"{sm:.2f}%" or "-":>6} pinnacle '
f'{pin and len(active_markets(pin)) or 0:>3} mkts '
f'{pm and f"{pm:.2f}%" or "-":>6}')
Premier League Everton FC v Chelsea FC stake 124 mkts 4.71% pinnacle 102 mkts 3.27%
Premier League Brentford FC v Liverpool FC stake 120 mkts 5.76% pinnacle 103 mkts 3.37%
Premier League Fulham FC v Hull City stake 120 mkts 6.12% pinnacle 101 mkts 3.59%
Serie A Frosinone Calcio v Sassuolo Calc stake 126 mkts 4.89% pinnacle 102 mkts 2.72%
Serie A Venezia FC v SSC Napoli stake 124 mkts 5.22% pinnacle 100 mkts 3.63%
Serie A Bologna FC v Inter Milano stake 121 mkts 5.80% pinnacle 100 mkts 3.64%
NHL Colorado Avalanche v Pittsburgh stake 73 mkts 5.76% pinnacle 62 mkts 2.71%
NHL Anaheim Ducks v Boston Bruins stake 69 mkts 5.87% pinnacle 70 mkts 2.38%
NFL Jacksonville Jaguars v Houston T stake 39 mkts 8.00% pinnacle 95 mkts 3.08%
NFL Atlanta Falcons v Chicago Bears stake 39 mkts 7.00% pinnacle 80 mkts 2.93%
NFL Cleveland Browns v Baltimore Rav stake 45 mkts 7.91% pinnacle 67 mkts 3.26%
CS2 Aurora Gaming v 9Z Team stake 124 mkts 8.47% pinnacle 58 mkts 4.85%
CS2 Eyeballers v Bbl eSports stake 124 mkts 8.14% pinnacle 0 mkts -
CS2 Astralis v Mibr stake 124 mkts 8.30% pinnacle 40 mkts 5.71%
We ran this on 16 October 2026 at 13:21 UTC. Stake listed 120 to 126 markets on each Premier League and Serie A fixture, about 20 more than Pinnacle. On CS2 it listed 124 markets on all three boards, more than twice Pinnacle’s menu, and it priced a PGL Bucharest match that had no Pinnacle price at the time. On the NFL, the menu thins to 39 to 45 markets.
The full board puts those counts in context. We read every book on 70 boards between 13:11 and 13:20 UTC the same day:
| Tournament (16 Oct 2026) | Boards with Stake | Books per board (median) | Stake active markets | Stake’s rank by menu size (median) |
|---|---|---|---|---|
| CS2, tier-one events | 6 | 139.5 | 122 to 124 | 3 |
| League of Legends (Worlds, Demacia Cup) | 3 | 150 | 207 to 234 | 13 |
| NHL | 2 | 229.5 | 69 to 73 | 46.5 |
| Top-five European soccer | 12 | 284.5 | 117 to 126 | 151 |
| Brasileiro Serie A | 3 | 269 | 100 to 116 | 160 |
| NFL | 5 | 283 | 39 to 45 | 216 |
Read the rank next to the board size. A soccer board with 285 books runs to 359 markets on the 22bet.com family, so 124 markets places Stake mid-table. On CS2, only duelbits and gamdom listed more, and those two quote most of Stake’s prices, as the next step shows. For the CS2 market IDs themselves, read our CS2 odds API guide.
Step 3: find the feeds that mirror Stake on CS2
This step finds the next CS2 fixture where Stake quotes 200 or more active prices, reads every book on that board, and scores each slug on how many of Stake’s prices it matches exactly.
# Reuses get() and the imports from Step 1.
def active_prices(book):
"""Every active price a book quotes, keyed by (market, outcome, player)."""
return {(mid, oid, pid): p["price"]
for mid, m in book["markets"].items()
for oid, o in m["outcomes"].items()
for pid, p in (o.get("players") or {}).items()
if p.get("active") and p.get("price")}
now = datetime.now(timezone.utc)
fixtures = get("fixtures", sportId=17, # CS2
**{"from": now.strftime("%Y-%m-%d"),
"to": (now + timedelta(days=4)).strftime("%Y-%m-%d")}) or []
for f in sorted(fixtures, key=lambda f: f["startTime"]):
if not f["hasOdds"] or f["startTime"] <= now.isoformat():
continue
probe = get("odds", fixtureId=f["fixtureId"], bookmakers="stake")
time.sleep(1)
stake = (probe or {}).get("bookmakerOdds", {}).get("stake")
if stake and len(active_prices(stake)) >= 200: # a deep Stake board
break
print(f["participant1Name"], "v", f["participant2Name"], f["startTime"])
board = get("odds", fixtureId=f["fixtureId"])["bookmakerOdds"] # every book
board = {s: b for s, b in board.items() if "+" not in s and s != "demo"}
ref = active_prices(board["stake"])
print(f"{len(board)} books on the board, stake quotes {len(ref)} active prices\n")
rows = []
for slug, book in board.items():
prices = active_prices(book)
shared = set(ref) & set(prices)
if slug == "stake" or len(shared) < 50:
continue
same = sum(1 for k in shared if prices[k] == ref[k])
rows.append((same / len(shared), len(shared), slug))
for share, n, slug in sorted(rows, reverse=True)[:12]:
print(f"{slug:<12} {n:>3} shared prices, {share:6.1%} identical to stake")
twins = [s for share, n, s in rows if share >= 0.6]
print(f"\n{len(twins)} slugs match stake on 60%+ of shared prices")
Aurora Gaming v 9Z Team 2026-10-16T14:00:00.000Z
145 books on the board, stake quotes 250 active prices
nextbet 186 shared prices, 100.0% identical to stake
dafabet 186 shared prices, 100.0% identical to stake
duelbits 248 shared prices, 90.7% identical to stake
nordicbet 142 shared prices, 88.0% identical to stake
inkabet 142 shared prices, 88.0% identical to stake
betsson 142 shared prices, 88.0% identical to stake
betsafe 142 shared prices, 88.0% identical to stake
betfirst.be 142 shared prices, 88.0% identical to stake
gamdom 250 shared prices, 74.0% identical to stake
betway.es 234 shared prices, 51.7% identical to stake
betway 234 shared prices, 51.7% identical to stake
sportingbet 52 shared prices, 23.1% identical to stake
9 slugs match stake on 60%+ of shared prices
Nine slugs matched Stake on 60% or more of the prices they share with it. The match moves with the clock, because each copy updates at its own moment. Across the six deep CS2 boards we read that day, nextbet and dafabet matched 100% of shared prices on five, duelbits 91% to 100%, betsson, betsafe, nordicbet, inkabet and betfirst.be 87% to 99%, and gamdom 71% to 100%.
Keep one book from that group in a consensus, and weight the rest at zero. On League of Legends the link is looser: the same slugs matched 22% to 66% of Stake’s prices, so treat Stake there as its own book. On soccer, no slug matched more than 68% of the prices it shared with Stake on any board.
Step 4: check the closing line on played fixtures
The close shows the price Stake settled on after a full market cycle. This step pulls the played Premier League round of 10 to 12 October from /v4/historical-odds and takes the last active price before kickoff on each leg. Use startTime as the cutoff. On the fixtures we checked, trueStartTime sat one to three minutes after kickoff, which lets in-play prices into the close.
# Reuses get() and the imports from Step 1.
BOOKS = "stake,pinnacle"
EPL, RESULT = 17, ["101", "102", "103"] # Full Time Result: home, draw, away
def closing_prices(fixture_id, kickoff, outcome_id):
"""Last active price before kickoff, per book, for one outcome."""
hist = get("historical-odds", fixtureId=fixture_id,
bookmakers=BOOKS, outcomeId=outcome_id)
time.sleep(4.6) # /historical-odds needs a longer gap
close = {}
for slug, book in (hist or {}).get("bookmakers", {}).items():
for market in book["markets"].values():
for o in market["outcomes"].values():
snaps = [s for s in o["players"].get("0", [])
if s["active"] and s["createdAt"] < kickoff]
if snaps:
close[slug] = snaps[-1]["price"]
return close
played = get("fixtures", tournamentId=EPL,
**{"from": "2026-10-09", "to": "2026-10-13"}) or []
played = [f for f in played if f["statusName"] == "Finished"]
margins = {s: [] for s in BOOKS.split(",")}
for f in played:
kickoff = f["startTime"] # scheduled kickoff, not trueStartTime
legs = [closing_prices(f["fixtureId"], kickoff, oid) for oid in RESULT]
row = {}
for slug in margins:
if all(slug in leg for leg in legs):
row[slug] = (sum(1 / leg[slug] for leg in legs) - 1) * 100
margins[slug].append(row[slug])
name = f'{f["participant1Name"]} v {f["participant2Name"]}'[:34]
print(f"{name:<34}", " ".join(f"{s} {v:.2f}%" for s, v in row.items()))
print(f"\n{len(played)} played Premier League fixtures, closing 1X2:")
for slug, vals in margins.items():
print(f" {slug:<8} median {median(vals):.2f}% ({len(vals)} fixtures)")
Arsenal FC v Leeds United stake 4.06% pinnacle 2.83%
Aston Villa v Brentford FC stake 4.43% pinnacle 2.94%
Chelsea FC v AFC Bournemouth stake 4.03% pinnacle 3.28%
Ipswich Town v Fulham FC stake 3.91% pinnacle 2.81%
Sunderland AFC v Brighton & Hove A stake 3.80% pinnacle 2.97%
Manchester United v Tottenham Hots stake 4.17% pinnacle 3.25%
Crystal Palace v Nottingham Forest stake 4.12% pinnacle 2.92%
Hull City v Everton FC stake 4.06% pinnacle 2.82%
Liverpool FC v Manchester City stake 3.88% pinnacle 2.86%
Coventry City v Newcastle United stake 3.80% pinnacle 2.91%
10 played Premier League fixtures, closing 1X2:
stake median 4.04% (10 fixtures)
pinnacle median 2.91% (10 fixtures)
Stake closed every one of the 10 fixtures between 3.80% and 4.43%, about one point over Pinnacle. We ran the same check on 80 played fixtures between 5 and 14 October:
| Tournament (played, 5 to 14 Oct 2026) | Fixtures | Median close, stake |
Median close, pinnacle |
Median gap |
|---|---|---|---|---|
| Premier League | 10 | 4.04% | 2.91% | 1.05 |
| LaLiga | 10 | 4.09% | 2.74% | 1.04 |
| Serie A | 6 | 4.08% | 3.11% | 1.01 |
| Bundesliga | 6 | 4.30% | 2.83% | 1.36 |
| Ligue 1 | 6 | 3.92% | 2.92% | 0.96 |
| Brasileiro Serie A | 6 | 6.96% | 2.98% | 4.00 |
| NFL (moneyline) | 12 | 4.93% | 3.05% | 1.86 |
| MLB (moneyline) | 6 | 4.10% | 2.06% | 2.12 |
| NHL (moneyline) | 10 | 5.02% | 2.57% | 2.47 |
| CS2, ESL Pro League (winner) | 8 | 8.22% | 4.82% | 3.51 |
Across the 38 top-five soccer fixtures, Stake closed the 1X2 at a 4.07% median against Pinnacle’s 2.91%. Use Stake as a second line on European soccer, and take fair value from Pinnacle or the deduplicated board. On CS2, the deepest Stake menu carries its widest margin, so take Stake’s markets for breadth and de-vig the deduplicated board for the price. Our backtesting guide runs a full strategy test on closes like these.
Snapshots record polling, so collapse identical consecutive prices before you count line moves. To read Stake live without a polling loop, use the WebSocket feed. For a one-page view of the slug, see the Stake sportsbook page. For 1xBet, Cloudbet and the other crypto sportsbooks, read the crypto bookmaker API guide and the 1xBet API guide.
Stake API FAQ
Does Stake have an API?
We found no public sportsbook odds API from Stake. Its developer program, Engine, serves casino game studios. OddsPapi carries stake.com prices under the stake slug on /v4/odds, next to 350+ other bookmakers.
How do I get Stake odds in Python?
Call /v4/odds with a fixtureId and bookmakers=stake, and pass your key as the apiKey query parameter. Add pinnacle to the same call to compare Stake's line with a sharp book.
Which feeds quote the same CS2 prices as Stake?
On six tier-one CS2 boards read on 16 October 2026, nextbet, dafabet, duelbits, gamdom, betsson, betsafe, nordicbet, inkabet and betfirst.be matched 71% to 100% of the prices they shared with stake. Keep one of them in a consensus.
Is MyStake the same as Stake?
No. MyStake is a different brand. The catalogue lists it as mystake and flags 31bet, freshbet, goldenbet, jackbit, rolletto and velobet as its clones. Stake is the stake slug, and the Brazil-licensed site is stake.bet.br.
Can I get historical Stake odds?
Yes. /v4/historical-odds returns stake snapshots on played fixtures, on the free tier. Across 38 top-five soccer fixtures played 5 to 14 October 2026, stake closed the 1X2 at a 4.07% median margin against 2.91% on Pinnacle.
Read Stake next to 350+ books from one key
Every call in this guide runs on the free key: 350+ bookmakers in the catalogue, free historical odds for the closing-line check, and a WebSocket feed when polling gets too slow. Get a free API key and run Step 3 on the next CS2 slate.