This release adds a 65-function ESPN college-football API layer, expanding cfbfastR's ESPN surface from 8 wrappers to 73. The new wrappers expose ESPN's core-v2 endpoints in ESPN's own ID space — complementary to the CollegeFootballData (cfbd_*) wrappers, and the natural join partners for espn_cfb_pbp() / espn_cfb_scoreboard(). Every wrapper was verified live against the 2023, 2024, and 2025 seasons.
Naming alignment with the sportsdataverse convention (this dev cycle, never on CRAN): espn_cfb_player_statistics() is renamed to espn_cfb_player_career_stats() (the core-v2 /athletes/{id}/statistics career view, matching hoopR/wehoop/sportsdataverse-py). New espn_cfb_player_stats_v3() wraps the comprehensive web-common-v3 /athletes/{id}/stats payload (all categories, long format) — the _v3 companion to espn_cfb_player_stats() (core-v2 season statistics).
espn_cfb_powerindex() — ESPN's College Football Power Index (FPI): every predictive metric and efficiency component, in long format.espn_cfb_qbr() — Total Quarterback Rating (QBR) and the full set of clutch-weighted EPA components, one row per qualified passer.espn_cfb_futures() — the season betting-futures board (national championship, conference, and award markets) with each sportsbook's American odds.espn_cfb_recruits() — ESPN's recruiting board for a class, one row per recruit with grade, position/state/region rank, committed school, and hometown.espn_cfb_players(), espn_cfb_player(), espn_cfb_player_eventlog(), espn_cfb_player_gamelog(), espn_cfb_player_statistics(), espn_cfb_player_splits(), espn_cfb_player_overview(), and espn_cfb_player_seasons() — player index, biographical detail, per-game logs, season statistics, and split breakdowns. The season-level wrappers resolve athlete_id to human-readable name/position columns via an athlete_detail argument.espn_cfb_teams(), espn_cfb_team(), espn_cfb_team_roster(), espn_cfb_team_schedule(), espn_cfb_team_record(), and espn_cfb_team_leaders() — team index, team-in-season detail, roster, schedule, records, and statistical leaders.espn_cfb_team_ats(), espn_cfb_team_powerindex(), espn_cfb_team_events(), espn_cfb_team_ranks(), espn_cfb_team_awards(), and espn_cfb_team_coaches() — team against-the-spread records, single-team power index, season event log, poll-rank history, player awards, and coaching staff.espn_cfb_game_teams(), espn_cfb_game_team_linescores(), espn_cfb_game_team_leaders(), espn_cfb_game_team_roster(), espn_cfb_game_team_statistics(), and espn_cfb_game_team_records() — per-game team breakdowns.espn_cfb_game_odds(), espn_cfb_game_broadcasts(), espn_cfb_game_predictor(), espn_cfb_game_probabilities(), espn_cfb_game_powerindex(), and espn_cfb_game_pbp() — per-game odds, broadcasts, pre-game predictor, live win-probability, matchup power index, and play-by-play.espn_cfb_game_drives(), espn_cfb_game_drive_plays(), espn_cfb_game_play(), espn_cfb_game_leaders(), espn_cfb_game_situation(), espn_cfb_game_status(), espn_cfb_game_player_statistics(), and espn_cfb_game_player_box() — drive log, drive-scoped plays, single-play detail, game statistical leaders, situation, status, and per-player game box lines.espn_cfb_game_pbp(), espn_cfb_game_drive_plays(), espn_cfb_game_play()) extract every field ESPN returns for a play and expose its nested child collections through opt-in parameters: participants ("none"/"wide"/"long") and participants_list surface per-play athlete involvement (passer, rusher, tackler, …), and team_participants / team_participants_list surface the offense/defense team participants — "wide" modes pivot to one row per play, the *_list flags keep the raw detail as a list-column.espn_cfb_game_team_records(detail = TRUE) unpacks each record's full statistic breakdown; espn_cfb_game_odds(line_history = TRUE) returns the open/close/current line-movement history. Roster and player-stats wrappers join ESPN position-catalog detail when position_detail = TRUE.espn_cfb_game_drives() gains a plays argument — "list" nests each drive's plays (full play-by-play schema, with the participant pass-through options) as a list-column, "expand" returns the flat one-row-per-play table with drive_* context columns. The new espn_cfb_unnest_plays() performs the same drives-to-play-by-play transform on an already-fetched nested frame.team_detail = TRUE (default): every team-id column (team_id, home_team_id, start_team_id, leader_team_id, …) gains sibling *_name, *_abbreviation, *_location, *_display_name, *_color, *_logo_href, … columns from the ESPN team catalog. espn_cfb_game_teams(format = "wide") collapses the two competitor rows into a single per-game row with home_* / away_* columns for direct joining onto one-row-per-game tables.espn_cfb_pbp_v2() — a core-v2-sourced successor to espn_cfb_pbp(): assembles play-by-play in one structured request (vs. the legacy site-v2 summary parse) and, with epa_wpa = TRUE, runs cfbfastR's full EPA/WPA model pipeline — producing EPA/WPA columns identical to the legacy modeled feed.espn_cfb_seasons(), espn_cfb_season_info(), espn_cfb_season_types(), espn_cfb_season_weeks(), espn_cfb_groups(), and espn_cfb_standings() — season structure, conferences, and standings.espn_cfb_coaches(), espn_cfb_coach(), espn_cfb_venues(), espn_cfb_positions(), espn_cfb_awards(), espn_cfb_rankings(), and espn_cfb_week_rankings() — league catalogs and poll rankings.espn_cfb_coach_record(), espn_cfb_franchises(), espn_cfb_franchise(), espn_cfb_venue(), espn_cfb_position(), and espn_cfb_award() — coach season win/loss records, the league franchise catalog, and single-record venue / position / award detail.cfbd_betting_ats() — season against-the-spread (ATS) summary records by team, wrapping the CollegeFootballData /teams/ats endpoint.cfbd_stats_game_havoc() — per-game havoc statistics (total / front-seven / defensive-back havoc events and rates, offense and defense), wrapping the CollegeFootballData /stats/game/havoc endpoint.cfbd_pbp_data_v2() is a new public function: a modular successor to cfbd_pbp_data() that runs the same EPA/WPA pipeline through a single shared engine (.run_epa_wpa()) and a canonical play-type taxonomy (.pbp_play_types()). The legacy cfbd_pbp_data() is unchanged.espn_cfb_pbp_v2() now sources play-by-play and meta through the shared engine, requests participants = "wide" and team_participants = "wide" from espn_cfb_game_drives(), and adds the meta columns home_team_name, home_team_color, home_team_alternate_color, home_team_rank (and away_*) via the new .espn_pbp_game_meta() bridge. Output is a strict superset of legacy espn_cfb_pbp() on the meta columns.espn_cfb_pbp() now builds its request URL with the ?event= query separator (previously concatenated as summaryevent=, which returned HTTP 404 for every game) and initializes its return frame before the tryCatch so an upstream failure no longer throws object 'plays_df' not found.cfbd_pbp_data_v2() and espn_cfb_pbp_v2() preserve character id_play precision through the EPA/WPA pipeline. The legacy shared helper used unquoted numeric literals in two ifelse calls (a historical id_play swap for one game), which silently coerced character id_play to numeric and then lost precision past 2^53 — breaking the play-id join-back in espn_cfb_pbp_v2(). The modular .pbp_clean_pbp_dat() quotes those literals so id_play stays character; the legacy clean_pbp_dat() is unchanged.httr2 package (>= 1.0.0) instead of the legacy httr. End users running existing wrapper calls (cfbd_*, espn_cfb_*) should see no behavioural change -- the migration is internal. Custom code that calls get_req() or check_status() directly must update from httr::content(res, as = "text") to httr2::resp_body_string(res) and from httr::status_code(res) to httr2::resp_status(res).get_req() now resolves a proxy in the order: explicit proxy argument -> getOption("cfbfastR.proxy") -> http_proxy / https_proxy env vars. The proxy value accepts either a URL string or a named list with url / port / username / password / auth for authenticated proxies.lubridate, progressr, memoise, cachem, and magrittr have moved out of Imports (21 -> 16). lubridate is gone entirely -- its two ymd_hm() |> with_tz() calls in espn_cfb_schedule.R are now base-R as.POSIXct(format = "%Y-%m-%dT%H:%M", tz = "UTC") + attr(., "tzone"). progressr, memoise, and cachem moved to Suggests and the helpers degrade gracefully when missing: load_cfb_pbp() / cfbd_pbp_data() / pbp_epa_wpa_engine() run without a progress bar when progressr is absent; ESPN catalog wrappers run uncached when memoise / cachem are absent (espn_cfb_clear_cache() becomes a no-op). Drops the Imports count below the >20 R CMD check NOTE threshold.%>% chains in R/, plus 137 across vignettes/ and tests/, were converted to the base-R native pipe |>. magrittr is no longer an Imports; downstream consumers that load cfbfastR purely for its functions don't get %>% re-exported anymore. User-visible impact is minimal -- the public API is unchanged and dplyr (which is in Imports) still re-exports %>% for users who want to keep writing it. Two non-mechanical fixes were needed during the sweep: three |> [[("url") chains in cfbd_betting.R and cfbd_coaches.R (rejected as RHS in R 4.1's |>) became |> purrr::pluck("url"); seven |> tibble::tibble(col = .data$.) constructs were a magrittr quirk that silently duplicated the LHS into both a . and the named column -- rewritten to tibble::tibble(col = <lhs>), which drops the redundant . column.tests/testthat/setup-cfbd-throttle.R adds a 1-second sleep before every CFBD request made by devtools::test() / R CMD check. It works by monkey-patching cfbfastR:::get_req() for the duration of the test session (restored via withr::defer(., teardown_env())) -- the package code is unchanged, so interactive and production calls pay no penalty. Tunable via options(cfbfastR.test_request_delay = N) (default 1; set to 0 for unthrottled local runs). Resolves the cascading HTTP 429 skip-if-empty results that were turning otherwise-green test runs into "all green, mostly skipped." withr joins Suggests to declare the test-side dependency cleanly (it was already a transitive dep of testthat).validate_week() utility function where some inputs were not being handled correctly (i.e. week 16). Fixes trickle down to cfbd_pbp_data() and other functions.season_type parameter in cfbd_game_info() and cfbd_play_stats_player() function changed from "regular" to "both" to align with other functions in the package.cfbd_pbp_data() where play-by-play data for some games were not as expected.add_yardage() where plays with missing yardage values were not being handled correctly.load_cfb_*() functions now use sportsdataverse-data releases or the CollegeFootballData.com API as their underlying data source to remain in compliance with CFBD API terms and conditions (See Note below).load_cfb_pbp() dataset to include various team- and game-level ID's and flags that were not being included, like home_team_id, away_team_id, season_type, venue_id, some drive_* columns, a half-dozen player stat columns, etc. Essentially, all the leg-work users have undoubtedly had to do while using these datasets is mostly just included now. The downside: this means end users need to check their pipelines which build off these datasets to ensure behavior is as expected and all your joins are doing what is intended.Special thanks are in order for our newest contributor, Brad Hill (@bradisbrad) for providing most of the v2 upgrade via his first PR to cfbfastR!! 🙌🏽 👑 🥇 Your contributions are most appreciated by the community.
Note: The free-tier API key for the CFBD v2 API has a strict 1k calls/month limit, so plan your workflows accordingly! If you receive errors mentioning r Request failed [429], you have most likely run out of API calls for the month in your membership tier.
Added all new cfbd_*() functions accommodated by the new College Football Data API v2. This includes the following functions:
cfbd_metrics_fg_ep() function to access the new field goal expected points added metric from the API.cfbd_metrics_wepa_team_season() function to access the new opponent adjusted team season predicted points added metric from the API.cfbd_metrics_wepa_players_passing() function to access the new opponent adjusted players passing predicted points added metric from the API.cfbd_metrics_wepa_players_rushing() function to access the new opponent adjusted players rushing predicted points added metric from the API.cfbd_metrics_wepa_players_kicking() function to access the new Points Added Above Replacement (PAAR) ratings for kickers from the API.cfbd_ratings_fpi() function to access the new FPI ratings from the API.cfbd_live_scoreboard() function to access live scoreboard data from the API.cfbd_live_plays() function to access live play-by-play data from the API.cfbd_api_key_info() function to get information about your API key, including your Patreon level and usage limits.Minor changes to the existing cfbd_*() functions under the hood to accommodate the new API v2 structure. Please see below for a list of all updated functions:
cfbd_betting_lines() functioncfbd_coaches() functioncfbd_conferences() functioncfbd_drives() functioncfbd_calendar() functioncfbd_game_box_advanced() functioncfbd_game_info() functioncfbd_game_media() functioncfbd_game_player_stats() functioncfbd_game_records() functioncfbd_game_team_stats() functioncfbd_metrics_ppa_games() functioncfbd_metrics_ppa_players_games() functioncfbd_metrics_ppa_players_season() functioncfbd_metrics_ppa_predicted() functioncfbd_metrics_ppa_teams() functioncfbd_metrics_wp() functioncfbd_metrics_wp_pregame() functioncfbd_pbp_data() functioncfbd_play_stats_player() functioncfbd_play_stats_types() functioncfbd_play_types() functioncfbd_plays() functioncfbd_player_info() functioncfbd_player_returning() functioncfbd_player_usage() functioncfbd_rankings() functioncfbd_ratings_sp() functioncfbd_ratings_sp_conference() functioncfbd_ratings_srs() functioncfbd_recruiting_player() functioncfbd_recruiting_position() functioncfbd_recruiting_team() functioncfbd_stats_categories() functioncfbd_stats_game_advanced() functioncfbd_stats_season_advanced() functioncfbd_stats_season_player() functioncfbd_stats_season_team() functioncfbd_team_info() functioncfbd_team_matchup() functioncfbd_team_matchup_records() functioncfbd_team_roster() functioncfbd_team_talent() functioncfbd_venues() functionFixed the following functions and/or documentation:
cfbd_team_info() addressing #97cfbd_stats_game_advanced() returns an empty data frame when there are no resultscfbd_game_team_stats() updated to reflect all parameter requirement scenarios.athlete_id parameter cfbd_player_usage() so that it works as users would expect. There was an API query-parameter mismatchathlete_id parameter for cfbd_play_stats_player() function and added more thorough documentation.position to correct value (instead of NA) from cfbd_stats_season_player()season_type parameter documentation across many functionscfbd_pbp_data() to substitute 3 timeouts per half when the data is missing from the API.stringi v1.8 update in cfbd_play_pbp_data() EPA and WPA processingespn_cfb_scoreboard() and espn_cfb_schedule() functions while adding lubridate dependencyespn_ratings_fpi() functionespn_cfb_player_stats() function added.cfbd_game_team_stats() function return data in a long formatespn_cfb_calendar()espn_cfb_schedule()espn_cfb_pbp()espn_cfb_team_stats()furrr, future dependencies, adds Rcpp, RcppParallel, and purrr dependenciesespn_ratings_fpi() to exports.cfbd_game_team_stats() with _allowed columns duplicating team stats instead of showing opponent stats.cfbd_game_team_stats().cfbd_pbp_data() to account for additional timeout cases (namely, kickoffs/extra point attempts).cfbd_betting_lines()espn_ratings_fpi() now requires headers in httr requestcfbd_ratings_elo() functionupdate_cfb_db() where the function failed when trying to load recent games from the data repo. (#35)cfbfastR.dbdirectory that allows to set the database directory in update_cfb_db() globally.cfbd_stats_season_team() that were not behaving correctlyonly_fbs input in cfbd_team_info() was ignored. It is now possible to get the team info for all the colleges in the API instead of only FBS schools.cfbd_metrics_ppa_teams. cfbd_metrics_ppa_teams and cfbd_metrics_ppa_players_season now require one of team or year to be specifiedcfbd_draft_teams() - Get list of NFL teams
cfbd_draft_positions() - Get list of NFL positions for mapping to collegiatecfbd_draft_picks() - Get list of NFL Draft picksAdded headshot_url to outputs of cfbd_team_roster()
Renamed returns in cfbd_game_box_advanced():
rushing_line_yd_avg to plural rushing_line_yds_avgrushing_second_lvl_yd_avg to plural rushing_second_lvl_yds_avgrushing_open_field_yd_avg to plural rushing_open_field_yds_avgCompleted documentation for all returns except cfbd_pbp_data()
Continued work on intro vignette
Added mini-vignettes pertaining to CFB Data functionality:
id variable to team_id in espn_ratings_fpi()espn_game_id variable to game_id in espn_metrics_wp(), corrected the away_win_percentage calculation and added tie_percentage to the returns.id variable to athlete_id in cfbd_metrics_ppa_players_season()load_cfb_pbp() and update_cfb_db() functions. Pretty much cherry-picking the nflfastR methodology of loading data from the cfbfastR-data repository.furrr, future, and progressr dependencies to the package to allow for parallel processing of the play-by-play data with progress updates if desired.All functions sourced from the College Football Data API will start with cfbd_ as opposed to cfb_ (as in cfbscrapR). One additional cfbd_ function has been added that corresponds to the result when cfbd_pbp_data() has the parameter epa_wpa=FALSE. It has now been separated into its own function for clarity cfbd_plays(). The parameter and functionality still exists in cfbd_pbp_data() but we expect this function will still exist but made obsolete in favor of a function more closely matching nflfastR's naming conventions.
Similarly, data and metrics sourced from ESPN will begin with espn_ as opposed to cfb_. In particular, the two functions are now espn_ratings_fpi() and espn_metrics_wp()
Data generated from any of the cfbfastR methods will use cfb_
The CollegeFootballData API now requires an API key, here's a quick run-down:
To get an API key, follow the directions here: College Football Data Key Registration.
Using the key: You can save the key for consistent usage by adding CFBD_API_KEY=XXXX-YOUR-API-KEY-HERE-XXXXX to your .Renviron file (easily accessed via usethis::edit_r_environ()). Run usethis::edit_r_environ(), a new script will pop open named .Renviron, THEN paste the following in the new script that pops up (without quotations)
CFBD_API_KEY = XXXX-YOUR-API-KEY-HERE-XXXXX
Save the script and restart your RStudio session, by clicking Session (in between Plots and Build) and click Restart R (n.b. 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 cfbd_ functions without any other changes.
CFBD_API_KEY (with quotations) using a command like the following.Sys.setenv(CFBD_API_KEY = "XXXX-YOUR-API-KEY-HERE-XXXXX")