Rugby Odds API: NRL, Super League and Union From 123 Books
Rugby hides two sports behind one sport ID. Point a parser at sportId=26 and you get the NRL, Super League, Pro D2 and a New Zealand provincial competition in the same list, priced by different bookmakers, with the match winner filed under two different market IDs that are not the same bet.
The result is a parser that runs clean and reports the wrong board. It drops a third of the match-winner quotes, computes a negative margin on Kalshi, and reads a rugby union fixture as three times deeper than it is. This guide walks the rugby board with live data pulled on 24 August 2026 across 40 fixtures with odds, then gives you code that survives it.
What the rugby board holds
A ten-day window on sportId=26, sampled on 24 August 2026, returned 40 fixtures with odds, 123 distinct bookmakers and 88,380 prices. Books per fixture ran from 3 to 107, with a median of 52. The first split is the one nobody warns you about: categoryName carries the code, not the country.
| categoryName | Fixtures with odds | Competitions priced |
|---|---|---|
| Rugby League | 21 | NRL Premiership, Super League, NRL Women |
| Rugby Union | 19 | Rugbys Greatest Rivalry, Pro D2, National Provincial Championship, Int. Friendly Games, Queensland Premier Rugby |
| Rugby Union Sevens | 0 | None priced in this window |
Depth per competition varies by a factor of thirty-five:
| Competition | Code | Fixtures | Books per fixture | Pinnacle live |
|---|---|---|---|---|
| NRL Premiership | League | 8 | 102 to 107 | 0 of 8 |
| Rugbys Greatest Rivalry | Union | 2 | 83 to 96 | 1 of 2 |
| Super League | League | 7 | 55 | 0 of 7 |
| Pro D2 | Union | 8 | 38 to 54 | 0 of 8 |
| National Provincial Championship | Union | 7 | 42 to 51 | 0 of 7 |
| Int. Friendly Games | Union | 1 | 47 | 0 of 1 |
| NRL, Women | League | 6 | 3 | 0 of 6 |
| Queensland Premier Rugby | Union | 1 | 3 | 0 of 1 |
Half the board is posted and suspended
Only 44.4% of the 88,380 prices came back active: true, which is close to the 41.9% we measured on early-season college football. That average hides the more useful finding, because the split runs along the code:
| Competition | Books | Prices active |
|---|---|---|
| NRL Premiership | 102 to 107 | 86.7% to 92.8% |
| Super League | 55 | 70.5% to 71.6% |
| Rugbys Greatest Rivalry | 96 | 37.6% |
| National Provincial Championship | 42 to 51 | 30.6% to 32.2% |
Rugby league boards are live. Rugby union boards are mostly posted and suspended. A National Provincial Championship fixture carries 42 to 51 books and roughly a third of its prices are actually taking bets, so a parser that skips the price-level active flag reads that board as three times deeper than it is. Filter on the flag before you count anything.
The sharp and the depth sit in different codes
Pinnacle was live on 1 of the 40 upcoming fixtures. Read that too fast and you conclude Pinnacle has left rugby union. It has not. Pinnacle opens rugby late, so a live census taken on an upcoming fixture is measuring the clock rather than the coverage.
To measure coverage instead, query fixtures that have already been played: /historical-odds retains a book’s full menu after the live prices are dropped, so it answers whether a book covers a market at all.
| Competition | Fixture date | Pinnacle | SBOBet |
|---|---|---|---|
| NRL Premiership | 23 Aug | never quoted | never quoted |
| NRL Premiership | 23 Aug | never quoted | never quoted |
| Super League | 23 Aug | never quoted | Total 4, Handicap 3 |
| Super League | 23 Aug | never quoted | Total 2, Handicap 2 |
| National Provincial Championship | 23 Aug | Winner 1, Total 5, Handicap 8 | never quoted |
| National Provincial Championship | 23 Aug | Winner 1, Total 8, Handicap 9 | Total 7, Handicap 8 |
| Currie Cup | 23 Aug | Winner 1, Total 7, Handicap 6 | Total 4, Handicap 3 |
| Currie Cup | 21 Aug | Winner 1, Total 11, Handicap 6 | never quoted |
Pinnacle prices rugby union and does not price rugby league. That is a real coverage gap rather than a timing artefact, because the fixtures above had already kicked off when they were queried and their full price history was still there. Pinnacle quoted the National Provincial Championship and the Currie Cup on every union fixture tested, and quoted nothing at all on the NRL or Super League.
SBOBet does not follow it. SBOBet quoted totals and handicaps on both Super League fixtures tested, so it is present on rugby league in a way Pinnacle is not, while skipping some union fixtures Pinnacle priced. Two sharp books, two different maps.
The NRL is where the depth is. 102 to 107 books on every fixture in the sample, up to 222 distinct market IDs on one board. It has no sharp benchmark on it at all.
| You want | Go to | What you give up |
|---|---|---|
| A sharp benchmark to de-vig against | Rugby union: the National Provincial Championship or the Currie Cup | Pinnacle opens late, so it is usually absent from an upcoming fixture, and its menu is one Winner market plus 5 to 11 total rungs and 6 to 9 handicap rungs |
| Book count and alt ladders | NRL Premiership (102 to 107 books) | Neither Pinnacle nor SBOBet quoted either NRL fixture tested |
| A prediction-market quote | Kalshi or Polymarket | Both are placeholder-priced on rugby, and their ladders hold single-digit to low-double-digit dollars |
Old way vs OddsPapi
| Task | Scraping or a generic sports API | OddsPapi |
|---|---|---|
| Cover both codes | Two scrapers, two schemas, two league taxonomies | One sportId=26 call, split on categoryName |
| Reach a sharp price | Pinnacle has no public API | bookmakers=pinnacle on the free tier |
| Cover the NRL board | Up to 107 separate bookmaker sites, each rate-limiting you | One /odds call returns all 107 |
| Get price history | Store it yourself from today, or buy it | /historical-odds, free tier, back to the day each book opened |
| Read prediction-market depth | Kalshi and Polymarket, separate auth, separate formats | exchangeMeta back and lay ladders in the same payload |
Step 1: Authenticate and split the two codes
The key is a query parameter. It is never a header.
import requests, time, collections
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
RUGBY = 26
def api(path, **params):
"""One call, with the rate limit and the empty-window 404 handled."""
params["apiKey"] = API_KEY
for _ in range(5):
r = requests.get(f"{BASE_URL}/{path}", params=params, timeout=120)
if r.status_code == 429:
time.sleep(r.json()["error"].get("retryMs", 1500) / 1000 + 0.3)
continue
if r.status_code == 404:
return [] # an empty fixture window 404s, it does not return []
r.raise_for_status()
return r.json()
raise RuntimeError("rate limited")
fixtures = api("fixtures", sportId=RUGBY, **{"from": "2026-08-24", "to": "2026-09-03"})
priced = [f for f in fixtures if f["hasOdds"]]
by_code = collections.Counter(f["categoryName"] for f in priced)
print(len(priced), "fixtures with odds")
for code, n in by_code.most_common():
print(f" {code:20s} {n}")
40 fixtures with odds
Rugby League 21
Rugby Union 19
Two rules come out of that call. An empty date window returns HTTP 404 with a FIXTURE_NOT_FOUND body, so raise_for_status() on its own kills a season loop. And to is a midnight-UTC instant, so set it to the day after the last day you want.
Find the competitions
comp = collections.Counter((f["categoryName"], f["tournamentName"]) for f in priced)
for (code, name), n in comp.most_common():
print(f" {code:14s} {name:34s} {n}")
Rugby League NRL Premiership 8
Rugby Union Pro D2 8
Rugby Union National Provincial Championship 7
Rugby League Super League 7
Rugby League NRL, Women 6
Rugby Union Rugbys Greatest Rivalry 2
Rugby Union Int. Friendly Games 1
Rugby Union Queensland Premier Rugby 1
Filter on the pair. Filtering on tournamentName alone mixes codes, and the same trap bites in soccer, where 35 competitions are called “Premier League”.
Step 2: The match winner ships under two market IDs
Rugby carries four market families and nothing else. No player props, no try-scorer market, no first-scorer market, zero player-keyed prices anywhere in the window.
| Family | Market ID | Outcomes | Books quoting it |
|---|---|---|---|
| 1X2 | 263 | 263 home, 264 draw, 265 away | 116 |
| Total | one ID per line | Over, Under | 111 |
| Handicap | one ID per line | two-sided | 106 |
| Winner | 261 | 261 home, 262 away | 90 |
Market 261 and market 263 are not duplicates. They are different bets. 261 is a two-way price with no draw. 263 is a three-way that prices the draw. Rugby league games can be drawn, and books price both, so both appear in the same payload for the same fixture. Of the 40 fixtures sampled on 24 August 2026, 33 carried both families, 6 carried only 1X2, and none carried only Winner.
Which one a book uses is a property of the book. Five books quote 261 and never touch 263:
| Book | Winner (261) | 1X2 (263) |
|---|---|---|
| pinnacle | quotes it | never |
| hardrockbet | quotes it | never |
| paddypower | quotes it | never |
| tab.com.au | quotes it | never |
| tabtouch | quotes it | never |
Key on 263 and you drop all five, Pinnacle included. Across the 40 fixtures that costs 1,161 of 3,245 match-winner quotes, or 35.8%. Losing a third of your board is bad. Losing the only sharp on it changes every fair price you compute.
WINNER_2WAY = "261" # 261 home, 262 away
WINNER_3WAY = "263" # 263 home, 264 draw, 265 away
def live_prices(market):
"""Active prices only. The active flag lives on the price, not the outcome."""
out = {}
for outcome_id, outcome in market.get("outcomes", {}).items():
price = outcome.get("players", {}).get("0")
if price and price.get("active"):
out[outcome_id] = price["price"]
return out
def read_board(books):
board = {}
for slug, book in books.items():
if book.get("suspended"):
continue
markets = book.get("markets", {})
two = live_prices(markets.get(WINNER_2WAY, {}))
three = live_prices(markets.get(WINNER_3WAY, {}))
entry = {}
if len(two) == 2:
entry["two_way"] = {"home": two["261"], "away": two["262"]}
if len(three) == 3:
entry["three_way"] = {"home": three["263"], "draw": three["264"],
"away": three["265"]}
elif three:
entry["partial"] = three # see step 3
if entry:
board[slug] = entry
return board
# swap in the fixture ID from your own /fixtures call
FIXTURE = "id2600029465687684" # Brisbane Broncos v Melbourne Storm, NRL Premiership
odds = api("odds", fixtureId=FIXTURE)
board = read_board(odds["bookmakerOdds"])
for slug in sorted(board):
print(f" {slug:20s} {board[slug]}")
# each row is
# slug: {'two_way': {'home': .., 'away': ..},
# 'three_way': {'home': .., 'draw': .., 'away': ..},
# 'partial': {outcome_id: price}}
#
# Brisbane Broncos v Melbourne Storm, NRL Premiership, kickoff 27 Aug 09:50 UTC.
# The deepest board in the 24 August 2026 sample: 107 books, which produce
# 103 Winner and 1X2 quotes between them, and 40 independent prices.
Step 3: Count outcomes before you compute a margin
Six books shipped market 263 with fewer than three priced outcomes. The market ID says three-way. The payload does not.
| Book | Outcomes shipped on 263 | Fixtures |
|---|---|---|
| pokerstars.uk | 0 priced legs | 17 |
| polymarket | 2 of 3 | 16 |
| pointsbet.com.au | 2 of 3 | 9 |
| betmgm.bet.br | 1 of 3 | 9 |
| expekt.dk | 1 of 3 | 9 |
| kalshi | 1 of 3 (the draw leg only) | 8 |
Run a naive margin over those and the numbers lie in both directions. Kalshi’s single draw leg inverts to a fraction of 1.0 on its own, which reads as a large negative margin and trips every arbitrage alert you own. PointsBet’s two legs sum to a little over 1.0, which reads as the tightest three-way on the board when it is really a two-way missing its draw.
A single book never offers you a free arbitrage. If the inverse prices of a market sum to 1.0 or less, you are looking at an incomplete market, not an edge.
def margin(prices):
return (sum(1 / p for p in prices.values()) - 1) * 100
rows = [(margin(e["two_way"]), slug) for slug, e in board.items() if "two_way" in e]
for m, slug in sorted(rows):
print(f" {slug:20s} {m:5.2f}%")
Run that across the sample rather than one fixture and you get a ranking. These are median margins per book on fixtures where it quoted the family, for books present on at least four fixtures. Market 261, the two-way Winner, 77 ranked books:
| Rank | Book | Median margin | Fixtures |
|---|---|---|---|
| 1 | paddypower | 4.51% | 9 |
| 2 | fanduel | 5.09% | 7 |
| 3 | netbet | 5.26% | 7 |
| 4 to 28 | the 25-slug Unibet family (atg.se, ballybet, betcity.nl, betparx, betplay, bingoal.be, casumo, expekt.se, fourwinds, jacks.nl, leovegas, paf, prolineplus, scooore.be, svenskaspel, tabtouch, unibet and its regionals) | 5.33% | 8 to 10 |
| 29 | hardrockbet | 5.79% | 31 |
| 47 | betmgm | 7.54% | 15 |
| 77 | kalshi | 70.07% | 8 |
Market 263, the three-way 1X2, 107 ranked books:
| Rank | Book | Median margin | Fixtures |
|---|---|---|---|
| 1 | betfair-ex | 3.89% | 9 |
| 2 | netbet | 4.95% | 7 |
| 3 | draftkings | 6.49% | 26 |
| 4 | fanduel | 6.92% | 14 |
| 5 | winbet.ro | 7.23% | 27 |
| 38 | 1xbet | 8.08% | 37 |
| 53 | bet365 | 8.50% | 33 |
| 76 | betmgm | 9.76% | 15 |
| 107 | polymarket | 194.12% | 9 |
Pinnacle and SBOBet appear in neither table. Each was live on 1 of the 40 fixtures, which is not enough to rank a book on, and quoting a one-fixture margin as if it were a rate is how the previous version of this table went wrong.
Compare like with like. A two-way margin and a three-way margin are not the same measurement, and the same book prices them differently: FanDuel’s median is 5.09% on the two-way and 6.92% on the three-way, BetMGM’s is 7.54% and 9.76%. Our vig calculator guide covers the normalisation if you need to rank across both.
Both prediction markets are placeholder-priced, in opposite families
Kalshi splits the game across three contracts, so its bookmakerOutcomeId values show three separate Kalshi tickets on one game:
market 261, outcome 261: home price ...-<HOME>:yes
market 261, outcome 262: away price ...-<AWAY>:yes
market 263, outcome 264: draw price ...-TIE:yes
To build a Kalshi three-way, merge its market 261 with outcome 264 of market 263. Its two-way margin on its own always understates what it is really charging on the game.
Then screen it. Across the 8 fixtures where Kalshi priced the two-way, its median margin was 70.07%, the worst of the 77 books ranked on market 261. Polymarket does the same thing in the other family: across the 9 fixtures where it priced the three-way, its median margin was 194.12%, the worst of 107. Neither number is a price. Both are placeholders sitting behind a real-looking ladder, and nothing in the payload flags them. Screen a prediction-market quote on its margin and its ladder depth before you use it, the same screen we applied to Kalshi and Polymarket on the NFL.
Step 4: Deduplicate, then read the count
Several slugs ship identical prices while /bookmakers reports cloneOf: null for all of them. On a 107-book board that is not a rounding detail. Brisbane Broncos against Melbourne Storm produced 103 Winner and 1X2 quotes that collapse to 40 independent prices, a collapse of 61.2%. The largest groups:
| Slugs | Price | Members |
|---|---|---|
| 24 | 1.84 / 1.96 | atg.se, ballybet, betcity.nl, betparx, betplay, bingoal.be, casumo, expekt.se, fourwinds, jacks.nl, leovegas, leovegas.es, paf, pointsbet.com.au and 10 more |
| 14 | 1.80 / 1.90 | betmgm, borgata, bwin, bwin.dk, bwin.es, napoleonsports.be, partypoker, sportingbet, sportsinteraction, superbet.bet.br, superbet.ro, superbet.rs, winbet.bg, winbet.ro |
| 7 | 1.91 / 1.91 / 21.0 | bet365 and its six regional slugs |
| 6 | 1.909 / 1.909 | betinia.dk, estrelabet, fezbet, goldenpalacesports.be, lottoland, winpot.mx |
| 4 | 1.80 / 1.91 | betmgm.co.uk, betuk, coral, ladbrokes |
| 3 | 1.90 / 2.00 / 21.0 | betway, betway.de, betway.es |
seen = {}
for slug, entry in board.items():
if "two_way" not in entry:
continue
key = (entry["two_way"]["home"], entry["two_way"]["away"])
seen.setdefault(key, []).append(slug)
print(len([s for s in board if "two_way" in board[s]]), "slugs ->", len(seen), "independent")
for slugs in seen.values():
if len(slugs) > 1:
print(" identical:", slugs)
best_home = max((e["two_way"]["home"], s) for s, e in board.items() if "two_way" in e)
best_away = max((e["two_way"]["away"], s) for s, e in board.items() if "two_way" in e)
print(" best home", best_home, " best away", best_away)
# Brisbane Broncos v Melbourne Storm, 24 August 2026:
# 103 Winner and 1X2 quotes -> 40 independent prices (61.2% collapse)
# Across all 40 fixtures: 1,116 slug-quotes -> 387 independent (65.3%)
Two in every three bookmaker rows on the rugby board are a duplicate feed. Deduplicate per fixture rather than from a fixed clone list, because the groups shift and books sometimes land on the same price by coincidence at round numbers.
Then check what is behind the best price. On an exchange, exchangeMeta.back is a list of price levels with the best first, and each level carries a size, a cents share price and a limit, where the limit is the stake that takes the level. On rugby those ladders are close to empty: across the sample the median top-rung stake capacity was $5.10 on Kalshi (highest $6.00, 24 quotes) and $25.02 on Polymarket (highest $99.71, 59 quotes). On boxing the equivalent Kalshi number reached $24,705. Score an exchange quote on ladder depth for the sport in front of you, never on the price and never on a rule you carried over from another sport. Line shopping is only real if the best price will take your stake.
Step 5: Resolve the handicap line yourself
Rugby ladders are enormous, and the books that walk them are not the sharp ones. On Brisbane against Melbourne, FanDuel walked 50 handicap rungs. On the union fixtures it priced, Pinnacle walked 6 to 9 handicap rungs and 5 to 11 total rungs, and SBOBet 2 to 8 and 2 to 7.
FanDuel flags every rung it quotes as mainLine: true. All 102 of its 102 prices on that fixture, across all 50 handicap rungs. The flag fails on rugby the same way it failed on NFL team totals and on college football, so treat it as unusable.
The flag is not even consistent between books. Across the whole 24 August 2026 sample only 17.0% of the 88,380 prices carried it, 15,038 in total. On the same fixture where FanDuel flagged 100% of its prices, bet365 and its regional slugs flagged 4 of 104 (3.8%), 22bet 12 of 102 (11.8%) and 1xbet 4 of 64 (6.2%). One field, five different meanings.
What works is asking each book which of its own rungs it priced closest to even money, then taking the mode across books:
catalog = api("markets", sportId=RUGBY)
lines = {str(m["marketId"]): m["handicap"]
for m in catalog if m["marketName"] == "Handicap"}
def most_balanced(book, lines):
"""The rung this book priced closest to 50/50 is the one it believes."""
best = None
for market_id, market in book.get("markets", {}).items():
if market_id not in lines:
continue
prices = list(live_prices(market).values())
if len(prices) != 2:
continue
skew = abs(1 / prices[0] - 1 / prices[1])
if best is None or skew < best[0]:
best = (skew, lines[market_id], prices)
return best
votes = collections.Counter()
for slug, book in odds["bookmakerOdds"].items():
pick = most_balanced(book, lines)
if pick:
votes[pick[1]] += 1
print(" consensus:", votes.most_common(1))
# Brisbane Broncos v Melbourne Storm, 81 books with a two-sided handicap rung
consensus: [(-1.5, 46)]
Eighty-one books voted and 46 of them landed on -1.5. Run the same function over the Total family and it returns 49.5 from 65 of the 88 books that quoted a total. Take the mode, never one book.
One more flag to ignore while you are here. Across 43,410 market records in the sample, 2,031 carried marketActive: true with every price underneath them dead, and 517 carried marketActive: false with all of their prices live. It is wrong in both directions and the dominant direction is the one that hands you a market with nothing behind it. We hit the same thing on boxing. Trust the price-level active flag and nothing above it.
Step 6: What Pinnacle's rugby limits tell you
Pinnacle publishes a stake limit on every price, and the limit caps the maximum win rather than the stake, so base = limit if price >= 2 else limit * (price - 1) recovers the figure the book actually set.
| Market | Pinnacle base, rugby union | For comparison |
|---|---|---|
| Winner | $25 | MLB moneyline $7,500 |
| Handicap | $62 | La Liga opener 1X2 $1,500 |
| Total | $24 to $25 | Boxing winner $125 |
Two things fall out. Rugby union carries the smallest Pinnacle base we have measured on any sport, so read its rugby prices as a quote rather than a position it wants. And the ranking inverts: Pinnacle takes two and a half times more on the handicap than on the moneyline, where football runs the match market first and the side markets far behind. Limits are the book's own confidence signal, and here they point at the handicap.
Step 7: Free historical odds, and one Polymarket rule
/historical-odds returns the full snapshot history for a fixture on the free tier, three bookmakers per call:
hist = api("historical-odds", fixtureId=FIXTURE,
bookmakers="bet365,hardrockbet,draftkings")
for slug, book in hist["bookmakers"].items():
firsts, snaps, changes = [], 0, 0
for market in book["markets"].values():
for outcome in market["outcomes"].values():
series = outcome["players"]["0"] # a LIST here, not a dict
if not series:
continue
firsts.append(series[0]["createdAt"])
snaps += len(series)
changes += sum(1 for i in range(1, len(series))
if series[i]["price"] != series[i - 1]["price"])
print(f" {slug:14s} opened {min(firsts)[:16]} {snaps} snapshots {changes} changes")
That prints the day each book opened its board on the fixture and how often it moved the price afterwards. Count price changes rather than snapshots, because the feed records on a cadence and most snapshots repeat the previous price.
Test coverage on a played fixture, not an upcoming one
This is the most useful trick in the guide, and it is the one that settles the Pinnacle question at the top of this post.
A live /odds call on an upcoming fixture tells you what is open right now, which on rugby is mostly a statement about the clock. /historical-odds tells you what a book covers, because the price history is retained after the fixture is played and the live prices are dropped. Point the call above at last weekend's fixtures instead of next weekend's and you get each book's full menu. That is how the eight-fixture table at the top of this post was built, and it is why "Pinnacle prices union and not league" is a coverage finding rather than a snapshot.
Polymarket rejects the normal call. Ask for it without an outcome and you get HTTP 400: "When using 'polymarket', you must provide only one bookmaker and exactly one outcomeId". Kalshi has no such rule, and answers a plain call with 15.7 MB.
hist = api("historical-odds", fixtureId=FIXTURE,
bookmakers="polymarket", outcomeId=263)
Read that series before you trust the slug. Polymarket was present on rugby all week and still posted a median three-way margin of 194.12% across the 9 fixtures it priced. A prediction market being on the board tells you nothing about whether its price means anything yet.
What this gets you
Rugby is a board that punishes assumptions. The code split lives in categoryName. The match winner lives in two market IDs that are different bets. Six books file an incomplete market under the three-way ID. More than half the prices are posted and suspended. The sharp prices one code and the deep board sits in the other.
Handle those five and you have both codes, up to 107 books on an NRL board, a Pinnacle benchmark on southern-hemisphere union, and free snapshot history back to the day each book opened. OddsPapi aggregates 350+ bookmakers across 69 sports, including sharps and prediction markets, with historical odds on the free tier.
Get your key
Stop scraping a hundred bookmaker sites for one NRL game. Get your free API key and pull the whole board in one call. New to the API? Start with the free odds API guide.
FAQ
Is there a free rugby odds API?
Yes. OddsPapi's free tier covers sportId=26, which returned 40 priced fixtures across the NRL, Super League, Pro D2 and the National Provincial Championship in a ten-day window sampled on 24 August 2026, with a median of 52 bookmakers per fixture, 107 on the deepest board, and full historical snapshots.
Why does my parser find fewer books than the payload holds?
You are probably keying on market 263 only. The match winner ships under market 261 as a two-way and market 263 as a three-way, and books pick one or both. Pinnacle, Hard Rock Bet, Paddy Power, TAB and TabTouch quote 261 and never 263. In the 24 August 2026 sample, 90 books quoted 261 and 116 quoted 263, so reading only 263 loses 1,161 of 3,245 match-winner quotes, or 35.8%.
Does Pinnacle price rugby?
Pinnacle prices rugby union and does not price rugby league. Tested on fixtures that had already been played, where the historical endpoint keeps a book's full menu, Pinnacle quoted the National Provincial Championship and the Currie Cup in full and quoted nothing at all on the NRL or Super League. It also opens late: it was live on only 1 of the 40 upcoming fixtures sampled on 24 August 2026, so a census taken days out will usually miss it even on the union boards it covers.
Why does Kalshi show a negative margin on rugby?
Kalshi lists the draw as a separate contract, which arrives as outcome 264 of market 263 on its own. One leg inverted by itself always sums to less than 1.0, which reads as a negative margin. Merge Kalshi's market 261 with outcome 264 of market 263 to get its real three-way, and reject any single book whose two-sided prices sum to 1.0 or less.
Are there rugby player props or try-scorer odds?
No. Rugby carried four market families across the 40 fixtures and 88,380 prices sampled on 24 August 2026: 1X2 on 116 books, Total on 111, Handicap on 106 and Winner on 90. Every price sat under the single default player slot, so no player-level market appeared on either code.
Can I trust the mainLine flag to find the main handicap?
No. On Brisbane Broncos against Melbourne Storm, FanDuel flagged 102 of its 102 prices as mainLine, across all 50 handicap rungs it quoted. Across the whole 24 August 2026 sample only 17.0% of the 88,380 prices carried the flag, and bet365 flagged 3.8% of its own on the same fixture. Resolve the line by taking each book's most balanced rung and using the mode across books.