Welcome folks,
I’m Saiem Gilani, one of the authors of oddsapiR,
and I hope to give the community a high-quality resource for accessing
The Odds API.
oddsapiR provides a tidy R interface to The Odds API, covering sports
discovery, events, live and historical odds, scores, and quota
management. All functions return oddsapiR_data
tibbles with quota metadata attached as attributes.
Install oddsapiR
# You can install using the pacman package using the following code:
if (!requireNamespace('pacman', quietly = TRUE)){
install.packages('pacman')
}
pacman::p_load_current_gh("sportsdataverse/oddsapiR", dependencies = TRUE, update = TRUE)
pacman::p_load_current_gh("jthomasmock/gtExtras", dependencies = TRUE, update = TRUE)
pacman::p_load(dplyr, knitr, gt)Odds API Keys
The Odds API requires an API key, here’s a quick run-down:
-
Persistent usage: Save the key for consistent usage
by adding
ODDS_API_KEY=XXXX-YOUR-API-KEY-HERE-XXXXXto your.Renvironfile (easily accessed viausethis::edit_r_environ()). Runusethis::edit_r_environ(), a new script will pop open named.Renviron, THEN paste the following in the new script that pops up (without quotations):
ODDS_API_KEY = XXXX-YOUR-API-KEY-HERE-XXXXXSave the script and restart your RStudio session, by
clicking Session (in between Plots and
Build) and click Restart R (there also exists
the shortcut Ctrl + Shift + F10 to restart your session).
If set correctly, from then on you should be able to use any of the
functions without any other changes.
-
Session-level usage: At the beginning of every
session or within an R environment, save your API key as the environment
variable
ODDS_API_KEY(with quotations) using a command like the following.
Sys.setenv(ODDS_API_KEY = "XXXX-YOUR-API-KEY-HERE-XXXXX")The helper functions toa_key(),
has_toa_key(), and check_toa_key() let you
inspect the key at any point. has_toa_key() returns
TRUE/FALSE; check_toa_key() stops
with a helpful message when no key is found.
has_toa_key() # TRUE / FALSE
toa_key() # the raw key string (or NA)
check_toa_key() # errors loudly if key is missingHow oddsapiR talks to The Odds API
Every toa_*() function calls the same underlying HTTP
layer in R/utils.R:
-
toa_api_request(url, query)— builds anhttr2request, attaches a standard user-agent (oddsapiR/...), applies a 3-try retry policy on transient network failures, and callshttr2::req_perform(). -
toa_api_call(url, query)— wrapstoa_api_request(), reads the response body as a string, and parses it withjsonlite::fromJSON(). Used by every odds/events/scores endpoint. -
toa_api_headers(url, query)— wrapstoa_api_request()and reads only thex-requests-remaining/x-requests-usedheaders. Used bytoa_requests().
The base URL pattern for v4 is:
https://api.the-odds-api.com/v4/sports/{sport_key}/odds
/events
/participants
/scores
https://api.the-odds-api.com/v4/historical/sports/{sport_key}/odds
/events
The API key is passed as the apiKey query parameter in
every request.
Quota tracking
Every successful response from The Odds API includes three headers:
| Header | Meaning |
|---|---|
x-requests-remaining |
Credits remaining until the monthly quota resets |
x-requests-used |
Credits consumed since the last quota reset |
x-requests-last |
Cost of the most recent call |
oddsapiR caches these after every call in a
package-private environment (.oddsapiR). They are also
attached as attributes to every returned tibble so they travel with the
data:
odds <- toa_sports_odds(sport_key = 'basketball_nba', regions = 'us', markets = 'h2h')
# Inspect quota without making another call
toa_quota()
# # A tibble: 1 x 3
# requests_remaining requests_used requests_last
# <int> <int> <int>
# 9850 150 1
attr(odds, "oddsapiR_requests_remaining")
attr(odds, "oddsapiR_requests_used")
attr(odds, "oddsapiR_requests_last")The print.oddsapiR_data() method echoes these values
when you print any result.
Key parameters: regions, markets, odds_format
Most odds endpoints accept these three shared parameters:
-
regions— which bookmakers to include:"us","us2","uk","eu","au". Comma-separate for multiple ("us,uk"). Each region counts as 1 unit in the usage quota multiplier. -
markets— which betting lines to return:"h2h"(moneyline),"spreads"(point handicap),"totals"(over/under),"outrights"(futures). Comma-separate for multiple ("h2h,spreads"). Each market counts as 1 unit in the quota multiplier. -
odds_format—"decimal"(default) or"american".
Quota cost for toa_sports_odds() =
number of markets × number of regions. So pulling
"h2h,spreads,totals" across "us,uk" costs 3 ×
2 = 6 credits.
Historical endpoints and the snapshot model
toa_sports_odds_history(),
toa_sports_events_history(), and
toa_event_odds_history() operate on a snapshot model: you
supply an ISO 8601 date timestamp and the API returns the
state of the data at the closest snapshot equal to or earlier than that
time. The response always includes timestamp,
previous_timestamp, and next_timestamp columns
so you can page through the archive programmatically.
The included data
toa_sports_keys is a bundled data frame listing all
sports the API covers. This data requires no API key and no network
access — it is included in the package:
oddsapiR::toa_sports_keys %>%
gt() %>%
gtExtras::gt_theme_538(table.width = px(650))
#> Table has no assigned ID, using random ID 'mwlefoodfi' to apply `gt::opt_css()`
#> Avoid this message by assigning an ID: `gt(id = '')` or `gt_theme_538(quiet = TRUE)`| key | group | title | description | has_outrights |
|---|---|---|---|---|
| americanfootball_ncaaf | American Football | NCAAF | US College Football | FALSE |
| americanfootball_nfl | American Football | NFL | US Football | FALSE |
| americanfootball_nfl_super_bowl_winner | American Football | NFL Super Bowl Winner | Super Bowl Winner 2022/2023 | TRUE |
| aussierules_afl | Aussie Rules | AFL | Aussie Football | FALSE |
| baseball_mlb | Baseball | MLB | Major League Baseball | FALSE |
| baseball_mlb_world_series_winner | Baseball | MLB World Series Winner | World Series Winner 2022 | TRUE |
| basketball_euroleague | Basketball | Basketball Euroleague | Basketball Euroleague | FALSE |
| basketball_nba | Basketball | NBA | US Basketball | FALSE |
| basketball_nba_championship_winner | Basketball | NBA Championship Winner | Championship Winner 2021/2022 | TRUE |
| basketball_ncaab | Basketball | NCAAB | US College Basketball | FALSE |
| basketball_wnba | Basketball | WNBA | US Basketball | FALSE |
| cricket_big_bash | Cricket | Big Bash | Big Bash League | FALSE |
| cricket_icc_world_cup | Cricket | ICC World Cup | ICC World Cup | FALSE |
| cricket_ipl | Cricket | IPL | Indian Premier League | FALSE |
| cricket_odi | Cricket | One Day Internationals | One Day Internationals | FALSE |
| cricket_test_match | Cricket | Test Matches | International Test Matches | FALSE |
| golf_masters_tournament_winner | Golf | Masters Tournament Winner | 2023 Winner | TRUE |
| golf_pga_championship_winner | Golf | PGA Championship Winner | 2022 Winner | TRUE |
| golf_the_open_championship_winner | Golf | The Open Winner | 2022 Winner | TRUE |
| golf_us_open_winner | Golf | US Open Winner | 2022 Winner | TRUE |
| icehockey_nhl | Ice Hockey | NHL | US Ice Hockey | FALSE |
| icehockey_nhl_championship_winner | Ice Hockey | NHL Championship Winner | Stanley Cup Winner 2021/2022 | TRUE |
| icehockey_sweden_allsvenskan | Ice Hockey | HockeyAllsvenskan | Swedish Hockey Allsvenskan | FALSE |
| icehockey_sweden_hockey_league | Ice Hockey | SHL | Swedish Hockey League | FALSE |
| mma_mixed_martial_arts | Mixed Martial Arts | MMA | Mixed Martial Arts | FALSE |
| politics_us_presidential_election_winner | Politics | US Presidential Elections Winner | 2024 US Presidential Election Winner | TRUE |
| rugbyleague_nrl | Rugby League | NRL | Aussie Rugby League | FALSE |
| soccer_africa_cup_of_nations | Soccer | Africa Cup of Nations | Africa Cup of Nations | FALSE |
| soccer_argentina_primera_division | Soccer | Primera División - Argentina | Argentine Primera División | FALSE |
| soccer_australia_aleague | Soccer | A-League | Aussie Soccer | FALSE |
| soccer_belgium_first_div | Soccer | Belgium First Div | Belgian First Division A | FALSE |
| soccer_brazil_campeonato | Soccer | Brazil Série A | Brasileirão Série A | FALSE |
| soccer_china_superleague | Soccer | Super League - China | FALSE | |
| soccer_conmebol_copa_libertadores | Soccer | Copa Libertadores | CONMEBOL Copa Libertadores | FALSE |
| soccer_denmark_superliga | Soccer | Denmark Superliga | FALSE | |
| soccer_efl_champ | Soccer | Championship | EFL Championship | FALSE |
| soccer_england_efl_cup | Soccer | EFL Cup | League Cup | FALSE |
| soccer_england_league1 | Soccer | League 1 | EFL League 1 | FALSE |
| soccer_england_league2 | Soccer | League 2 | EFL League 2 | FALSE |
| soccer_epl | Soccer | EPL | English Premier League | FALSE |
| soccer_fa_cup | Soccer | FA Cup | Football Association Challenge Cup | FALSE |
| soccer_fifa_world_cup | Soccer | FIFA World Cup | FIFA World Cup 2022 | FALSE |
| soccer_fifa_world_cup_winner | Soccer | FIFA World Cup Winner | FIFA World Cup Winner 2022 | TRUE |
| soccer_finland_veikkausliiga | Soccer | Veikkausliiga - Finland | FALSE | |
| soccer_france_ligue_one | Soccer | Ligue 1 - France | FALSE | |
| soccer_france_ligue_two | Soccer | Ligue 2 - France | French Soccer | FALSE |
| soccer_germany_bundesliga | Soccer | Bundesliga - Germany | German Soccer | FALSE |
| soccer_germany_bundesliga2 | Soccer | Bundesliga 2 - Germany | German Soccer | FALSE |
| soccer_italy_serie_a | Soccer | Serie A - Italy | Italian Soccer | FALSE |
| soccer_italy_serie_b | Soccer | Serie B - Italy | Italian Soccer | FALSE |
| soccer_japan_j_league | Soccer | J League | Japan Soccer League | FALSE |
| soccer_korea_kleague1 | Soccer | K League 1 | Korean Soccer | FALSE |
| soccer_league_of_ireland | Soccer | League of Ireland | Airtricity League Premier Division | FALSE |
| soccer_mexico_ligamx | Soccer | Liga MX | Mexican Soccer | FALSE |
| soccer_netherlands_eredivisie | Soccer | Dutch Eredivisie | Dutch Soccer | FALSE |
| soccer_norway_eliteserien | Soccer | Eliteserien - Norway | Norwegian Soccer | FALSE |
| soccer_portugal_primeira_liga | Soccer | Primeira Liga - Portugal | Portugese Soccer | FALSE |
| soccer_russia_premier_league | Soccer | Premier League - Russia | Russian Soccer | FALSE |
| soccer_spain_la_liga | Soccer | La Liga - Spain | Spanish Soccer | FALSE |
| soccer_spain_segunda_division | Soccer | La Liga 2 - Spain | Spanish Soccer | FALSE |
| soccer_spl | Soccer | Premiership - Scotland | Scottish Premiership | FALSE |
| soccer_sweden_allsvenskan | Soccer | Allsvenskan - Sweden | Swedish Soccer | FALSE |
| soccer_sweden_superettan | Soccer | Superettan - Sweden | Swedish Soccer | FALSE |
| soccer_switzerland_superleague | Soccer | Swiss Superleague | Swiss Soccer | FALSE |
| soccer_turkey_super_league | Soccer | Turkey Super League | Turkish Soccer | FALSE |
| soccer_uefa_champs_league | Soccer | UEFA Champions | European Champions League | FALSE |
| soccer_uefa_europa_league | Soccer | UEFA Europa | European Europa League | FALSE |
| soccer_uefa_nations_league | Soccer | UEFA Nations League | UEFA Nations League | FALSE |
| soccer_usa_mls | Soccer | MLS | Major League Soccer | FALSE |
| tennis_atp_aus_open_singles | Tennis | ATP Australian Open | Men's Singles | FALSE |
| tennis_atp_french_open | Tennis | ATP French Open | Men's Singles | FALSE |
| tennis_atp_us_open | Tennis | ATP US Open | Men's Singles | FALSE |
| tennis_atp_wimbledon | Tennis | ATP Wimbledon | Men's Singles | FALSE |
| tennis_wta_aus_open_singles | Tennis | WTA Australian Open | Women's Singles | FALSE |
| tennis_wta_french_open | Tennis | WTA French Open | Women's Singles | FALSE |
| tennis_wta_us_open | Tennis | WTA US Open | Women's Singles | FALSE |
| tennis_wta_wimbledon | Tennis | WTA Wimbledon | Women's Singles | FALSE |
Function reference by family
Sports & Events
toa_sports() — Discover active sports
Returns all sports the API covers (or only in-season ones). The
key column is the sport_key parameter used by
every other function. Free — does not count against your quota.
sports <- toa_sports(all_sports = TRUE)
# Returns a tibble with columns:
# key, group, title, description, active, has_outrights
#
# key group title active
# basketball_nba Basketball NBA TRUE
# soccer_epl Soccer EPL TRUE
# ...
toa_sports_events() — List upcoming events for a
sport
Returns upcoming and in-play events (without odds). The
id column from this result is the event_id
used by toa_event_odds() and
toa_event_markets(). Free — does not count against your
quota.
events <- toa_sports_events(
sport_key = 'basketball_nba',
date_format = 'iso',
commence_time_from = '2025-01-15T00:00:00Z',
commence_time_to = '2025-01-16T00:00:00Z'
)
# Returns a tibble: id, sport_key, sport_title, commence_time, home_team, away_team
#
# id sport_key home_team away_team
# 48db9c3293a52baab881d95d38f37a98 basketball_nba Los Angeles Lakers Boston Celtics
toa_sports_participants() — List participants (teams or
players) for a sport
Returns the whitelist of teams or individual competitors for a sport. Cost: 1 credit.
participants <- toa_sports_participants(sport_key = 'basketball_nba')
# Returns a tibble: sport_key, id, full_name
#
# sport_key id full_name
# basketball_nba par_01hqmkq6fdf1pvq7jgdd7hdmpf Los Angeles Lakers
# basketball_nba par_01hqmkq6fdf1pvq7jgdd7hdmpe Boston Celtics
toa_sports_scores() — Live and recent scores
Returns live and recently completed games with scores (updated ~30s
for live games; covers completed games within the last 3 days). Cost: 1
credit without days_from, 2 credits with it.
scores <- toa_sports_scores(
sport_key = 'basketball_nba',
days_from = 1, # include games completed in the last 1 day
date_format = 'iso'
)
# Returns a tibble:
# id, sport_key, sport_title, commence_time, completed, home_team, away_team,
# scores (list-col of name/score pairs), last_updateCurrent Odds & Markets
toa_sports_odds() — Featured-market odds across all
events for a sport
The primary odds endpoint. Returns a long-format tibble — one row per (event, bookmaker, market, outcome). Cost = markets × regions.
odds <- toa_sports_odds(
sport_key = 'basketball_nba',
regions = 'us',
markets = 'h2h,spreads,totals',
odds_format = 'decimal',
date_format = 'iso'
)
# Returns columns:
# id, sport_key, sport_title, commence_time, home_team, away_team,
# bookmaker_key, bookmaker, bookmaker_last_update,
# market_key, market_last_update,
# outcomes_name, outcomes_price, outcomes_point
#
# Note: spreads and totals have TWO rows per event per bookmaker
# (one for each side / Over / Under).
toa_event_odds() — Full market depth for a single
event
Like toa_sports_odds() but scoped to one game and
supports all available market keys, including player
props (player_points, player_pass_tds,
player_shots_on_goal, etc.) and alternate lines. Look up
event_id first with toa_sports_events().
event_odds <- toa_event_odds(
sport_key = 'basketball_nba',
event_id = '48db9c3293a52baab881d95d38f37a98',
regions = 'us',
markets = 'player_points', # player props
odds_format = 'decimal',
date_format = 'iso'
)
# Adds outcomes_description column (player name for props)You can also target specific bookmakers with the
bookmakers parameter (comma-separated slugs like
"draftkings,fanduel"). When bookmakers and
regions are both supplied, bookmakers takes
precedence.
toa_event_markets() — Discover available market keys
for a game
Returns the recently-seen market keys per bookmaker for a single
event (no odds). Use the returned market_key values to then
request odds via toa_event_odds(). Cost: 1 credit.
markets <- toa_event_markets(
sport_key = 'basketball_nba',
event_id = '48db9c3293a52baab881d95d38f37a98',
regions = 'us'
)
# Returns: id, sport_key, sport_title, commence_time, home_team, away_team,
# bookmaker_key, bookmaker, market_key, market_last_update
#
# Typical market_key values: h2h, spreads, totals, player_points,
# player_rebounds, player_assists, ...Historical Odds & Events (paid plans)
Historical endpoints return the API state at a point in time. All
three accept an ISO 8601 date string and respond with the
snapshot closest to (and not after) that time. The
timestamp, previous_timestamp, and
next_timestamp columns in the result let you page through
the archive.
Historical data covers featured markets back to June 2020, and additional markets (props, alternate lines) from 2023-05-03 at 5-minute intervals.
toa_sports_odds_history() — Historical featured-market
odds for a sport
hist_odds <- toa_sports_odds_history(
sport_key = 'basketball_nba',
date = '2024-01-15T12:15:00Z',
regions = 'us',
markets = 'spreads',
odds_format = 'decimal',
date_format = 'iso'
)
# Returns same columns as toa_sports_odds() plus:
# timestamp, previous_timestamp, next_timestamp
#
# Cost: 10 x markets x regions (10x historical multiplier)
toa_sports_events_history() — Historical event list (no
odds)
hist_events <- toa_sports_events_history(
sport_key = 'basketball_nba',
date = '2024-01-15T12:15:00Z'
)
# Returns same columns as toa_sports_events() plus:
# timestamp, previous_timestamp, next_timestamp
#
# Cost: 1 credit
toa_event_odds_history() — Historical odds for a single
event
Like toa_event_odds() for a specific snapshot time.
Accepts player props and alternate markets.
hist_event_odds <- toa_event_odds_history(
sport_key = 'basketball_nba',
event_id = '93af4b300a4c0dded909234ea32e9abd',
date = '2024-01-15T12:15:00Z',
regions = 'us',
markets = 'h2h',
odds_format = 'decimal',
date_format = 'iso'
)
# Returns same columns as toa_event_odds() plus:
# timestamp, previous_timestamp, next_timestamp
#
# Cost: markets x regions (standard rate, no 10x multiplier for event-level)Account & Usage
toa_quota() — Inspect your current quota balance
Returns a one-row tibble read from the headers of the most
recent API call made in this R session. Calling
toa_quota() does not itself use a quota credit.
# After any toa_* call:
toa_quota()
# # A tibble: 1 x 3
# requests_remaining requests_used requests_last
# <int> <int> <int>
# 9850 150 1
toa_requests() — Fetch quota from the API directly
Hits the free /v4/sports endpoint specifically to read
the current quota balance from the response headers. Use this at the
start of a session to check your budget before pulling odds.
toa_requests()
# # A tibble: 1 x 2
# requests_remaining requests_used
# <int> <int>
# 9850 150Key helpers: toa_key(), has_toa_key(),
check_toa_key()
toa_key() # Returns the ODDS_API_KEY env var or NA_character_
has_toa_key() # Returns TRUE if key is set, FALSE otherwise
check_toa_key() # Stops with a clear error if key is missingWorked example — pulling moneyline odds
This minimal example shows the basic pattern: check quota first, pull odds, inspect the result.
library(oddsapiR)
library(dplyr)
# 1. Verify a key exists and check starting budget
check_toa_key()
toa_requests()
# requests_remaining = 500, requests_used = 0
# 2. Pull moneyline (h2h) odds for the NBA from US books
nba_h2h <- toa_sports_odds(
sport_key = "basketball_nba",
regions = "us",
markets = "h2h",
odds_format = "decimal",
date_format = "iso"
)
# 3. Inspect: one row per (event x bookmaker x outcome)
glimpse(nba_h2h)
# Rows: ~120
# Columns: id, sport_key, sport_title, commence_time, home_team, away_team,
# bookmaker_key, bookmaker, bookmaker_last_update,
# market_key, market_last_update, outcomes_name, outcomes_price
# 4. Find the best moneyline price for each team across all books
best_lines <- nba_h2h %>%
group_by(home_team, away_team, outcomes_name) %>%
slice_max(outcomes_price, n = 1) %>%
select(home_team, away_team, outcomes_name, bookmaker, outcomes_price)
best_lines
# home_team away_team outcomes_name bookmaker outcomes_price
# Los Angeles Lakers Boston Celtics Los Angeles Lakers DraftKings 2.05
# Los Angeles Lakers Boston Celtics Boston Celtics FanDuel 1.87
# 5. Check how much that cost
toa_quota()
# requests_remaining = 499, requests_used = 1, requests_last = 1