Superbet API: Route 5 Tournaments to Their Tightest Home Feed
Looking for a Superbet API? You want Superbet’s prices as JSON, per fixture, without scraping superbet.ro, superbet.pl and superbet.rs one by one. OddsPapi returns them under six slugs: superbet, superbet.ro, superbet.pl, superbet.rs, superbet.bet.br and napoleonsports.be. All six carry liveOdds: true, and one API key reads all six.
The six feeds share one Superbet event ID, but they do not share one price. We pulled 27 boards on 30 September 2026 and 15 played fixtures from 20-21 September. In the Romanian Superliga, the Ekstraklasa, the Serbian Superliga, the Brasileirao and the Belgian Pro League, the feed from the tournament’s own country closed the 1X2 tightest on 15 of 15 fixtures, between 0.99 and 3.48 points under its closest sibling. This guide shows how to pull the six feeds, route each tournament to its home feed, and check the closing line yourself.
Why one Superbet scraper gets you one country’s price
Superbet runs one platform with a site per country. The payload shows it: every feed’s fixturePath ends in the same /offer-event/e-<id> path, and bookmakerFixtureId carries the same number on all six. Napoleon Sports in Belgium uses the same path and the same event ID, so it runs on the Superbet platform too. If you want to group a whole board by platform this way, our Unibet API guide does it for the Kambi family.
The event ID tells you the six feeds price the same fixture. It does not tell you what each one charges. A scraper on superbet.com gets you the Romanian price. On the Cluj derby that price is 8.11%. The Polish site charges 11.85% on the same fixture, and the Serbian site 11.06%. Point the scraper at Serbia instead and the numbers invert on Serbian football. So you need every country feed, and you need to know which one to trust for which tournament.
| Scraping Superbet sites | OddsPapi | |
|---|---|---|
| Superbet feeds | One scraper per country site | 6 slugs, one key |
| Which feed prices a league tightest | Unknown until you scrape all of them | Compare the six on one call, route per tournament |
| Closing lines | Only what you stored yourself | /v4/historical-odds, free tier |
| Parked boards | Look like live prices | Book-level suspended flag on every row |
| Rest of the board | None | 350+ bookmakers in the catalogue, 169 to 271 on the Nations League and Brasileirao boards we pulled |
| Push updates | Poll and diff the page | WebSocket feed on the same fixture IDs |
Step 1: find the Superbet slugs
Authentication is one query parameter, apiKey. The helper below waits out a 429 for as long as the error body says, backs off on a 5xx, and returns None on a 404. The first call lists the catalogue and keeps the Superbet platform feeds.
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(5):
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")
superbet = [b for b in books if b["slug"].startswith(("superbet", "napoleonsports"))]
for b in superbet:
print(f"{b['slug']:<18} {b['bookmakerName']:<20} liveOdds={b['liveOdds']} cloneOf={b['cloneOf']}")
print(f"\n{len(books)} bookmakers in the catalogue")
napoleonsports.be Napoleon Sports BE liveOdds=True cloneOf=None
superbet Superbet liveOdds=True cloneOf=superbet.ro
superbet.bet.br Superbet BR liveOdds=True cloneOf=None
superbet.pl Superbet PL liveOdds=True cloneOf=None
superbet.ro Superbet RO liveOdds=True cloneOf=None
superbet.rs Superbet RS liveOdds=True cloneOf=None
361 bookmakers in the catalogue
The catalogue flags one relationship: superbet is a clone of superbet.ro. That flag is correct. Across the 25 boards where both appeared, the two feeds shared 12,720 prices and matched on all 12,720. Use superbet.ro and skip superbet. The other five carry cloneOf: None, and the catalogue says nothing about how their prices relate. The board does.
Step 2: read the six feeds on one fixture
This step finds the next Romanian Superliga fixture that has not kicked off and pulls it with bookmakers= set to the six slugs. The filter keeps the payload small. Without it, a full board on a Nations League fixture came back at up to 67 MB.
# Reuses get() and the imports from Step 1.
FEEDS = "superbet,superbet.ro,superbet.pl,superbet.rs,superbet.bet.br,napoleonsports.be"
def margin_1x2(book):
"""Overround on market 101 (1X2), or None if any leg is missing or suspended."""
legs = book.get("markets", {}).get("101", {}).get("outcomes", {})
prices = [p["price"] for o in legs.values() for p in o["players"].values()
if p.get("active") and p.get("price", 0) > 1.0]
return sum(1 / p for p in prices) - 1 if len(prices) == 3 else None
now = datetime.now(timezone.utc)
fixtures = get("fixtures", tournamentId=152, # Romanian Superliga
**{"from": now.strftime("%Y-%m-%d"), "to": (now + timedelta(days=10)).strftime("%Y-%m-%d")})
fx = next(f for f in sorted(fixtures, key=lambda f: f["startTime"])
if f["statusName"] == "Pre-Game" and f["startTime"] > now.isoformat())
print(f"{fx['participant1Name']} v {fx['participant2Name']} {fx['startTime'][:16]}Z\n")
board = get("odds", fixtureId=fx["fixtureId"], bookmakers=FEEDS)["bookmakerOdds"]
for slug in FEEDS.split(","):
b = board.get(slug)
if not b:
print(f"{slug:<18} not on this board")
continue
m = margin_1x2(b) if not b.get("suspended") else None
print(f"{slug:<18} event {b['bookmakerFixtureId']} {len(b['markets']):>3} markets "
f"suspended={b.get('suspended')!s:<5} 1X2 margin {f'{m:.2%}' if m is not None else '-'}")
FC CFR 1907 Cluj v FC Universitatea Cluj 2026-10-08T17:30Z
superbet event 14364939 120 markets suspended=False 1X2 margin 8.11%
superbet.ro event 14364939 120 markets suspended=False 1X2 margin 8.11%
superbet.pl event 14364939 112 markets suspended=False 1X2 margin 11.85%
superbet.rs event 14364939 124 markets suspended=False 1X2 margin 11.06%
superbet.bet.br event 14364939 135 markets suspended=True 1X2 margin -
napoleonsports.be event 14364939 63 markets suspended=False 1X2 margin 11.06%
Three things to read in that output.
- One event ID, three prices. All six rows carry
14364939. The Romanian feed charges 8.11% on a Romanian fixture. The Serbian feed and Napoleon Sports charge 11.06%, and the Polish feed 11.85%. superbet.bet.brparks European domestic boards. It shippedsuspended: truewith zero active prices on all 16 Romanian, Polish, Serbian and Belgian domestic boards we pulled, 8 to 11 days before kick-off. Filter on the book-levelsuspendedflag before you average anything. The price-levelactiveflag alone is not enough on every book; our Bwin API guide shows a feed that parks its board and keeps every price marked active.- Menu width follows the country. On this fixture
superbet.bet.brlists 135 markets and Napoleon Sports 63. On the Poland v Romania Nations League board,superbet.rslisted 278 markets against 203 onsuperbet.ro. Pick the feed for the price you need, then check that it lists the market.
Step 3: route each tournament to its home feed
This step runs Step 2 across five tournaments. For each one it takes the next three fixtures, reads the 1X2 margin from every Superbet feed, and prints the median. The star marks the feed from the tournament’s own country. It reuses FEEDS and margin_1x2() from Step 2.
# Reuses FEEDS and margin_1x2() from Step 2.
LEAGUES = { # tournamentId: (name, home feed)
152: ("Romania Superliga", "superbet.ro"),
202: ("Poland Ekstraklasa", "superbet.pl"),
210: ("Serbia Superliga", "superbet.rs"),
325: ("Brasileirao A", "superbet.bet.br"),
38: ("Belgium Pro League", "napoleonsports.be"),
}
COLS = ["superbet.ro", "superbet.pl", "superbet.rs", "superbet.bet.br", "napoleonsports.be"]
now = datetime.now(timezone.utc)
window = {"from": now.strftime("%Y-%m-%d"), "to": (now + timedelta(days=14)).strftime("%Y-%m-%d")}
print(f"{'league':<20}" + "".join(f"{c:>18}" for c in COLS))
for tid, (name, home) in LEAGUES.items():
upcoming = [f for f in sorted(get("fixtures", tournamentId=tid, **window) or [], key=lambda f: f["startTime"])
if f["statusName"] == "Pre-Game" and f["startTime"] > now.isoformat()][:3]
margins = {c: [] for c in COLS}
for fx in upcoming:
board = get("odds", fixtureId=fx["fixtureId"], bookmakers=FEEDS)["bookmakerOdds"]
for c in COLS:
b = board.get(c)
m = margin_1x2(b) if b and not b.get("suspended") else None
if m is not None:
margins[c].append(m)
time.sleep(1.0)
cells = [(f"{median(v):.2%}" if v else "parked") + ("*" if c == home else " ") for c, v in margins.items()]
print(f"{name:<20}" + "".join(f"{x:>18}" for x in cells) + f" ({len(upcoming)} fixtures)")
print("\n* = home feed; median 1X2 margin; 'parked' = board shipped suspended")
league superbet.ro superbet.pl superbet.rs superbet.bet.br napoleonsports.be
Romania Superliga 8.11%* 11.85% 11.04% parked 11.04% (3 fixtures)
Poland Ekstraklasa 11.04% 8.75%* 11.04% parked 11.04% (3 fixtures)
Serbia Superliga 11.26% 12.53% 8.64%* parked 11.26% (3 fixtures)
Brasileirao A 5.57% 9.38% 5.57% 7.10%* 5.57% (3 fixtures)
Belgium Pro League 9.07% 9.88% 9.07% parked 8.07%* (3 fixtures)
* = home feed; median 1X2 margin; 'parked' = board shipped suspended
On the four European leagues, the home feed sits 1.0 to 2.9 points under its closest sibling. The Romanian feed takes 8.11% on Romanian football and 11.04% on Polish football. The Polish feed flips that: 8.75% at home, 11.85% on Romanian fixtures. Napoleon Sports is the Belgian case, at 8.07% against 9.07% for the Romanian and Serbian feeds.
Brazil breaks the pattern on this pull. With the next three Brasileirao fixtures still days away, superbet.bet.br charged 7.10% and superbet.ro charged 5.57%. Step 4 shows the order flips by kick-off.
Two more cuts from the same session, outside the table:
- Neutral fixtures collapse to one price. On five Nations League boards,
superbet.roandsuperbet.rsquoted the same 1X2 at a median 5.52%. Napoleon Sports ran 5.02%,superbet.pl6.46% andsuperbet.bet.br7.72%. Poland v Romania shows the routing at work:superbet.rocharged 5.36% against 6.38% onsuperbet.pl, and Pinnacle charged 5.63% on the same board. - Tennis follows a different map. On two ATP Tokyo fixtures,
superbet.rsand Napoleon Sports ran 5.55% against 6.67% onsuperbet.ro. The home-feed rule is a domestic-football finding. Measure it again before you apply it to another sport.
Step 4: check the closing line on played fixtures
A live board 8 to 12 days out is not the price you bet into. /v4/historical-odds returns every snapshot a book posted on a played fixture, free, up to three bookmakers a call. This step takes three played fixtures per tournament from 20-21 September 2026, reads the last active 1X2 snapshot before kick-off from the home feed and two siblings, and compares.
# Reuses get() from Step 1.
PLAYED = { # home feed: played fixtures, 20-21 Sep 2026
"superbet.ro": ["id1000015272388004", "id1000015272388006", "id1000015272388002"],
"superbet.pl": ["id1000020272043176", "id1000020272043174", "id1000020272043178"],
"superbet.rs": ["id1000021072083054", "id1000021072083058", "id1000021072083066"],
"superbet.bet.br": ["id1000032566887034", "id1000032566887018", "id1000032566887032"],
"napoleonsports.be": ["id1000003872203068", "id1000003872203070", "id1000003872203072"],
}
KICKOFF = {"id1000015272388004": "2026-09-20T17:30", "id1000015272388006": "2026-09-21T15:00",
"id1000015272388002": "2026-09-21T18:00", "id1000020272043176": "2026-09-20T12:45",
"id1000020272043174": "2026-09-20T15:30", "id1000020272043178": "2026-09-20T18:15",
"id1000021072083054": "2026-09-20T15:00", "id1000021072083058": "2026-09-20T17:00",
"id1000021072083066": "2026-09-20T18:00", "id1000032566887034": "2026-09-20T19:00",
"id1000032566887018": "2026-09-20T21:30", "id1000032566887032": "2026-09-20T22:30",
"id1000003872203068": "2026-09-20T14:00", "id1000003872203070": "2026-09-20T16:30",
"id1000003872203072": "2026-09-20T17:15"}
def closing_margin(book, kickoff):
"""1X2 overround on the last active pre-kick-off snapshot of each leg."""
legs = book["markets"]["101"]["outcomes"] # 101 home, 102 draw, 103 away
close = []
for oid in ("101", "102", "103"):
snaps = [s for s in legs[oid]["players"]["0"]
if s["active"] and s["price"] > 1.0 and s["createdAt"] < kickoff]
close.append(snaps[-1]["price"])
return sum(1 / p for p in close) - 1
wins = 0
for home, fixture_ids in PLAYED.items():
rivals = [s for s in ("superbet.ro", "superbet.pl", "superbet.rs") if s != home][:2]
for fid in fixture_ids:
hist = get("historical-odds", fixtureId=fid, bookmakers=",".join([home] + rivals))["bookmakers"]
closes = {slug: closing_margin(book, KICKOFF[fid]) for slug, book in hist.items()}
best_rival = min(closes[r] for r in rivals)
wins += closes[home] < best_rival
print(f"{fid} {home:<18} {closes[home]:6.2%} best rival {best_rival:6.2%} "
f"gap {100 * (best_rival - closes[home]):4.2f} pts")
time.sleep(4.6) # /historical-odds is rate limited
print(f"\nhome feed closed tightest on {wins} of {sum(map(len, PLAYED.values()))} fixtures")
id1000015272388004 superbet.ro 4.52% best rival 7.23% gap 2.71 pts
id1000015272388006 superbet.ro 4.42% best rival 6.64% gap 2.22 pts
id1000015272388002 superbet.ro 4.33% best rival 7.28% gap 2.95 pts
id1000020272043176 superbet.pl 4.94% best rival 7.05% gap 2.11 pts
id1000020272043174 superbet.pl 5.24% best rival 6.72% gap 1.48 pts
id1000020272043178 superbet.pl 5.44% best rival 7.43% gap 1.99 pts
id1000021072083054 superbet.rs 5.08% best rival 8.16% gap 3.08 pts
id1000021072083058 superbet.rs 5.53% best rival 9.01% gap 3.48 pts
id1000021072083066 superbet.rs 5.11% best rival 8.36% gap 3.25 pts
id1000032566887034 superbet.bet.br 4.06% best rival 5.42% gap 1.36 pts
id1000032566887018 superbet.bet.br 4.06% best rival 5.41% gap 1.36 pts
id1000032566887032 superbet.bet.br 3.87% best rival 4.85% gap 0.99 pts
id1000003872203068 napoleonsports.be 3.57% best rival 5.25% gap 1.68 pts
id1000003872203070 napoleonsports.be 3.74% best rival 5.36% gap 1.62 pts
id1000003872203072 napoleonsports.be 3.47% best rival 4.98% gap 1.51 pts
home feed closed tightest on 15 of 15 fixtures
Fifteen of fifteen. The Serbian feed closed its own tournament at 5.08% to 5.53% while the Romanian and Polish feeds sat at 8.16% to 9.35%, the widest gap in the sample. The Brazilian feed, which ran wide on the live board in Step 3, closed the Brasileirao at 3.87% to 4.06%, 0.99 to 1.36 points under superbet.ro. Napoleon Sports closed three Belgian fixtures at 3.47% to 3.74%.
If you backtest on Superbet, this is the table to rebuild first. A model tested against superbet.pl closes on Romanian football will look better than it is, because it trades against a price 3.5 to 4.0 points worse than the one a Romanian account sees. Our backtesting guide walks through the rest of the loop.
Step 5: give each Superbet price one vote
Six feeds on one platform are not six opinions. On a neutral fixture the Romanian and Serbian feeds quote one price. On a domestic fixture they split. So a static rule such as “count the Superbet family once” is wrong on exactly the boards where the home feed matters. Dedupe on the price tuple, per fixture. This step reuses board from Step 2.
# Reuses FEEDS, margin_1x2() and board from Step 2.
def superbet_votes(board):
"""Collapse the Superbet feeds on one board to one vote per distinct 1X2 price tuple."""
groups = {}
for slug in FEEDS.split(","):
b = board.get(slug)
if not b or b.get("suspended") or margin_1x2(b) is None:
continue
legs = b["markets"]["101"]["outcomes"]
key = tuple(p["price"] for oid in ("101", "102", "103") for p in legs[oid]["players"].values())
groups.setdefault(key, []).append(slug)
return groups
for prices, slugs in superbet_votes(board).items():
print(f"{'/'.join(map(str, prices)):<18} {', '.join(slugs)}")
2.75/3.15/2.5 superbet, superbet.ro
2.65/3.05/2.42 superbet.pl
2.67/3.05/2.45 superbet.rs, napoleonsports.be
Five priced feeds become three votes on the Cluj derby, and the home feed’s price is the best of the three on every leg. Feed those three into the consensus calculator and Superbet gets three votes, not five. When you shop for the best price, the line-shopping script will find the home feed on its own; the dedupe stops it from counting that price twice.
For a feed-by-feed view of each slug, the sportsbook pages list what OddsPapi carries: Superbet RO, Superbet PL, Superbet RS, Superbet BR and Napoleon Sports BE. For the rest of the Brazilian board around superbet.bet.br, see the Brasileirao odds API guide.
Superbet API FAQ
Does Superbet have a public API?
OddsPapi returns Superbet odds as JSON through /v4/odds, under six slugs: superbet, superbet.ro, superbet.pl, superbet.rs, superbet.bet.br and napoleonsports.be. One API key reads all six, alongside 350+ other bookmakers.
Which Superbet feed should I use?
Use the feed from the tournament’s own country. On 15 played fixtures in Romania, Poland, Serbia, Brazil and Belgium (20-21 September 2026), the home feed closed the 1X2 tightest every time, 0.99 to 3.48 points under its closest sibling.
Is superbet the same feed as superbet.ro?
Yes. The catalogue marks superbet as a clone of superbet.ro, and the two matched on 12,720 of 12,720 shared prices across 25 boards measured on 30 September 2026. Read superbet.ro.
Is Napoleon Sports part of Superbet?
Napoleon Sports runs on the Superbet platform: its rows carry the same Superbet event ID and the same offer-event path. It closed three Belgian Pro League fixtures at 3.47% to 3.74%, under superbet.ro and superbet.pl on every one.
Can I get historical Superbet odds?
Yes. /v4/historical-odds returns every snapshot for any three Superbet feeds per call on a played fixture, on the free tier. On Crvena Zvezda v Radnicki Nis (20 September 2026), superbet.rs closed the 1X2 at 5.53%.
How many bookmakers can I compare against Superbet?
The OddsPapi catalogue lists 361 bookmakers. The Nations League and Brasileirao boards we pulled on 30 September 2026 carried 169 to 271 books each, all in one /v4/odds call.
Pull all six Superbet feeds
Every call in this guide runs on the free key: 350+ bookmakers in the catalogue, free historical odds for the closing-line check in Step 4, and a WebSocket feed when polling gets too slow. Get a free API key and run Step 3 on the next round.