Official Python SDK for RapidOddsAPI. Bookmaker odds from 100+ books, live scores and player stats, over REST and WebSocket.
pip install rapidoddsapiRequires Python 3.9 or newer. Get a key at rapidoddsapi.com; the free tier is 250 credits with no card.
- 100+ bookmakers across Australia, the US and Europe, on 25 odds feeds — Bet365, Pinnacle, Sportsbet, TAB, Ladbrokes, DraftKings, BetMGM, Unibet, Bovada, Fanatics and more
- Every market — head to head, totals, handicaps, team totals and player props, full time and by period
- Live scores and player stats, broken down by period
- A link straight to the game on the bookmaker's own site, so a price you find is one click from being placed
- Streaming over WebSocket, pushed as each scrape finishes, reconnecting and re-subscribing on its own
- Arbitrage and value bet finders built in, over any two-sided market
- Typed throughout — hints on every response, typed exceptions, automatic retry with backoff
from rapidoddsapi import RapidOddsAPI
client = RapidOddsAPI(api_key="oa_your_api_key_here")
odds = client.get_odds("AFL", ["head_to_head"], ["Sportsbet", "TAB", "Ladbrokes"])
for entry in odds["games"]:
game = entry["game"]
print(f"{game['away_team']} at {game['home_team']}")
for book in entry["bookmakers"]:
for market in book["markets"]:
for outcome in market["outcomes"]:
print(f" {book['name']:<12} {outcome['name']:<20} {outcome['price']}")Odds are pushed to you the moment a scrape cycle finishes, so you are never polling and never working off a stale price. Included on Pro and Elite at no extra cost.
for update in client.stream_odds("AFL", ["head_to_head"], ["Sportsbet", "TAB"]):
for entry in update["data"]["games"]:
print(entry["game"]["home_team"], update["credits_charged"])That is the whole thing. The connection is kept alive, and if it drops (laptop sleeps, network cuts out, server restarts) the client reconnects with backoff and replays your subscription automatically.
Live scores stream the same way:
for update in client.stream_results("AFL", status="live"):
for entry in update["data"]["games"]:
game, score = entry["game"], entry["score"]
print(game["home_team"], score["home"]["points"],
game["away_team"], score["away"]["points"])Async is identical, with async for:
from rapidoddsapi import AsyncRapidOddsAPI
async with AsyncRapidOddsAPI(api_key="oa_your_api_key_here") as client:
async for update in client.stream_odds("NBA", ["head_to_head"], ["Pinnacle"]):
print(update["data"]["games"])Tuning, if you need it:
client.stream_odds(
"AFL", ["head_to_head"], ["Sportsbet"],
reconnect=True, # False to raise on the first drop
max_reconnect_attempts=None, # consecutive failures before giving up
initial_backoff=1.0,
max_backoff=30.0,
)find_arbitrage and find_value_bets work on a response you already have, so
they cost no extra credits.
from rapidoddsapi import find_arbitrage, find_value_bets
odds = client.get_odds("MLB", ["head_to_head"],
["Bet365", "Sportsbet", "TAB", "Pinnacle", "DraftKings"])
for arb in find_arbitrage(odds, stake=100):
print(f"{arb['away_team']} at {arb['home_team']} +{arb['profit_percent']:.2f}%")
for leg in arb["legs"]:
print(f" {leg['team']} {leg['price']} @ {leg['bookmaker']} stake ${leg['stake']}")Toronto Blue Jays at Boston Red Sox +4.85%
Toronto Blue Jays 2.18 @ Bet365 stake $48.10
Boston Red Sox 2.02 @ Sportsbet stake $51.90
find_value_bets strips the margin out of the odds to get a fair price, then
reports every book paying more than that.
for bet in find_value_bets(odds, devig="Pinnacle", min_edge=1.0):
print(f"+{bet['edge_percent']:.1f}% {bet['selection']} {bet['price']} "
f"@ {bet['bookmaker']} (fair {bet['fair_price']:.2f})")find_arbitrage(odds, ...)
| Default | ||
|---|---|---|
market |
"head_to_head" |
which market key to read |
stake |
100.0 |
total to split across the two legs |
min_profit |
0.0 |
percent. negative shows near misses |
find_value_bets(odds, ...)
| Default | ||
|---|---|---|
devig |
"Pinnacle" |
where the fair price comes from, below |
market |
"head_to_head" |
which market key to read |
min_edge |
1.0 |
percent |
min_books |
4 |
books needed before an average is trusted |
devig="Pinnacle" # one book, excluded from its own results
devig="all" # every book, fair odds averaged
devig={"Sportsbet": 0.7, "TAB": 0.3} # only these, at these weightsBoth work on any market with two sides: head to head, totals, handicaps, team
totals and player props. Three-way markets raise ValidationError. Legs and
value bets carry point, player_name and team_name where they apply.
Games are matched across books on team names plus a time window set by the sport, 1.8 hours for MLB because of doubleheaders and six for everything else.
| Method | Returns | Credits |
|---|---|---|
get_odds(sport, market_types, bookmakers) |
OddsResponse |
market_types x ceil(bookmakers / 5) |
get_results(sport, status=, include=, game_id=, round_number=, days=) |
ResultsResponse |
1 |
list_sports(markets=) |
list[SportInfo] |
0 |
get_sport(sport, markets=) |
SportInfo |
0 |
list_results_sports() |
list[SportInfo] |
0 |
get_usage() |
Usage |
0 |
stream_odds(sport, market_types, bookmakers) |
iterator of OddsUpdate |
same as get_odds, per push |
stream_results(sport, status=, include=, days=) |
iterator of ResultsUpdate |
1 per push |
credits_used |
int |
— |
Credits are only charged when games come back, so a query that matches nothing is free.
credits_used counts what this client has spent since you created it. It knows
nothing about other processes or earlier runs. For the real balance, ask the
server:
usage = client.get_usage()
print(f"{usage['credits_remaining']} of {usage['credits_limit']} left")On the free tier usage["resets"] is False: those credits are a one off
allowance, not a monthly one.
AsyncRapidOddsAPI has the same methods, awaited.
Helpers: find_arbitrage, find_value_bets, group_games, parse_time.
Pass the sport id as a string.
from rapidoddsapi import SPORTS, RESULTS_SPORTS
SPORTS
# ('NFL', 'NBA', 'WNBA', 'MLB', 'NHL', 'AFL', 'NRL', 'EPL', 'MLS',
# 'WORLD_CUP', 'MENS_AO', 'MENS_RG', 'MENS_WIMBLEDON', 'MENS_USO')
RESULTS_SPORTS
# ('AFL', 'MLB', 'WNBA', 'NRL')Tennis reuses the team fields for player names, so home_team and away_team
hold players on the four MENS_ ids. One slam is in season at a time.
Six more soccer leagues (La Liga, Serie A, Bundesliga, Ligue 1, Champions
League, A-League) are accepted as ids but return no games yet. They are in
UPCOMING_SPORTS rather than SPORTS.
Those tuples are a snapshot shipped with the package. To ask the API itself, which costs nothing:
client.list_sports()
# [{'id': 'NBA', 'name': 'NBA'}, {'id': 'AFL', 'name': 'AFL'}, ...]Market keys vary by sport, and get_sport returns the ones a sport carries:
afl = client.get_sport("AFL")
afl["markets"]["game"]
# ['alternate_lines', 'alternate_total_points', 'head_to_head', ...]
client.get_odds("AFL", afl["markets"]["game"][:1], ["Sportsbet"])The coverage page is the same information in a browser.
Many brands share one odds feed and so quote identical prices. Requests name
the feed, not the brand: "Ladbrokes" covers Neds, "Betmakers" covers the 26
brands running on it. Asking for a brand name returns nothing.
from rapidoddsapi import BOOKMAKERS, AU_BOOKMAKERS, US_BOOKMAKERS, EU_BOOKMAKERS
client.get_odds("AFL", ["head_to_head"], list(AU_BOOKMAKERS))Every feed costs credits, so request the ones you need rather than all of them. Which feeds carry a given sport and market varies; the coverage page is the current picture.
from rapidoddsapi import (
RapidOddsAPI, AuthenticationError, InsufficientCreditsError,
RateLimitError, NotFoundError,
)
try:
odds = client.get_odds("AFL", ["head_to_head"], ["Sportsbet"])
except InsufficientCreditsError as exc:
print(f"Out of credits, {exc.credits_remaining} left")
except AuthenticationError:
print("Bad key")| Exception | When |
|---|---|
AuthenticationError |
401, key missing or not recognised |
SubscriptionError |
403, subscription not active |
NotFoundError |
404, unknown sport |
ValidationError |
400 or 422, bad parameter |
RateLimitError |
429, over 30 requests per second |
InsufficientCreditsError |
429, out of credits. Carries credits_remaining |
ServerError |
5xx |
NetworkError |
timeout, DNS failure, connection reset |
StreamAuthError |
stream rejected: bad key, or plan without WebSocket |
StreamError |
stream failed for another reason |
All inherit RapidOddsAPIError. The two 429s share a QuotaError parent, so
you can catch either separately or both at once.
Requests are retried three times with backoff on 5xx, rate limits and network failures. Insufficient credits is never retried, since retrying cannot help.
Timestamps are naive UTC strings with no offset, like 2026-07-23T09:30:00.
Anything that reads a missing offset as local time will be wrong, so use
parse_time when you need a real datetime.
from rapidoddsapi import parse_time
start = parse_time(entry["game"]["commence_time"]) # aware, UTCThe odds and results APIs can disagree on a game's start time by several
minutes, since one comes from bookmaker feeds and the other from the official
league feed. Don't join them on an exact timestamp. group_games matches on
teams plus a time window instead.
score.totals.full_time is None until a game is CONCLUDED, and each other
named total is None until the periods it covers have finished. That is
deliberate, so a live scoreline can never be mistaken for a final one. For a
live running total, sum by_period.
totals = entry["score"]["totals"]
if totals["full_time"] is not None:
settle(total_points=totals["full_time"]["points"])
else:
running = sum(period["points"] for period in totals["by_period"])Do not hardcode how many periods a half is: half_time waits for period 2 in a
sport played in quarters but only period 1 in a sport played in halves, and MLB
carries first_5_innings instead. Let the None tell you.
- Arbitrage and positive EV scanners, using the helpers above
- Odds comparison screens and line-shopping tools
- Line movement tracking and alerting off the stream
- Automatic bet settlement from the results API
- Bonus bet conversions
- Model backtesting against live prices
Worked examples for each are in the guides.
- rapidoddsapi.com
- API documentation
- Coverage: sports, bookmakers, market keys
- Guides
- support@rapidoddsapi.com
MIT