Stake API: 124 CS2 Markets per Match, Mirrored by 9 Feeds

Stake API header: Stake odds in Python with OddsPapi
How To Guides October 22, 2026

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.