Nothing
# espn_cfb_season.R -- ESPN college football season + structure wrappers
# Consolidated family file. Each function keeps its own
# roxygen block; edit the block above the function you want.
#' @name espn_cfb_season
#' @aliases espn_cfb_season season seasons standings rankings
#' @title
#' **ESPN College Football Season Endpoint Overview**
#' @description
#'
#' * `espn_cfb_groups()`: Get the full ESPN group hierarchy for a college football season -- division roll-ups and conferences flattened into one catalog table.
#' * `espn_cfb_rankings()`: Get the index of poll / ranking sources ESPN publishes for a college football season.
#' * `espn_cfb_season_info()`: Get the ESPN core-v2 detail record for a single college football season.
#' * `espn_cfb_season_types()`: Get the set of season types (preseason, regular season, postseason, off-season) ESPN tracks for a college football season.
#' * `espn_cfb_season_weeks()`: Get the calendar of weeks ESPN tracks for a college football season type.
#' * `espn_cfb_seasons()`: Get the index of every college football season ESPN tracks in its core-v2 API.
#' * `espn_cfb_standings()`: Get ESPN's standings for a college football group -- full record splits and standings statistics for every team.
#' * `espn_cfb_week_rankings()`: Get the ranked teams from every poll ESPN published in a single week of a college football season.
#'
#' @details
#' ## **Get ESPN College Football Groups & Conferences**
#'
#' ```r
#' espn_cfb_groups(year = 2024)
#' ```
#'
#' ## **Get ESPN College Football Ranking Sources**
#'
#' ```r
#' espn_cfb_rankings(year = 2024)
#' ```
#'
#' ## **Get ESPN College Football Season Detail**
#'
#' ```r
#' espn_cfb_season_info(year = 2024)
#' ```
#'
#' ## **Get ESPN College Football Season Types**
#'
#' ```r
#' espn_cfb_season_types(year = 2024)
#' ```
#'
#' ## **Get ESPN College Football Season Weeks**
#'
#' ```r
#' espn_cfb_season_weeks(year = 2024)
#' ```
#'
#' ## **Get ESPN College Football Seasons Index**
#'
#' ```r
#' espn_cfb_seasons()
#' ```
#'
#' ## **Get ESPN College Football Standings (Long Format)**
#'
#' ```r
#' espn_cfb_standings(year = 2024)
#'
#' espn_cfb_standings(year = 2024, team_detail = FALSE)
#' ```
#'
#' ## **Get ESPN College Football Weekly Rankings**
#'
#' ```r
#' espn_cfb_week_rankings(year = 2024, week = 8)
#'
#' espn_cfb_week_rankings(year = 2024, week = 8, team_detail = FALSE)
#' ```
#'
NULL
#' @title
#' **ESPN College Football Groups & Conferences**
#' @description Get the full ESPN group hierarchy for a college football
#' season -- the division roll-ups (Division I, FBS, FCS) and every
#' conference nested beneath them, flattened into one catalog table.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}/types/{season_type}/groups`.
#' ESPN's group index only exposes two top-level groups; the conferences
#' sit several levels deep under `children`. This wrapper walks the tree
#' recursively, dereferencing every node, and returns one row per group.
#' Filter `is_conference == TRUE` for the actual conferences (ACC, SEC,
#' Big Ten, ...); the `group_id` of a conference is what
#' [espn_cfb_standings()] accepts as `group_id`. The `parent_group_id`
#' column lets you reconstruct the division -> conference nesting.
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_groups', 'min_year']`
#' @param season_type (*Integer* default 2): ESPN season type id.
#' `2` = regular season is the sensible default.
#' @return A data frame with one row per group:
#'
#' |col_name |types |description |
#' |:---------------|:---------|:-------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |season_type |integer |ESPN season type id queried. |
#' |group_id |character |ESPN group id. |
#' |name |character |Group / conference name. |
#' |abbreviation |character |Group abbreviation. |
#' |short_name |character |Group short name. |
#' |is_conference |logical |`TRUE` for an actual conference, `FALSE` for a division roll-up. |
#' |parent_group_id |character |`group_id` of the parent node (`NA` at the root). |
#' |slug |character |Group slug. |
#' |group_ref |character |`$ref` URL to the group resource. |
#' |standings_ref |character |`$ref` URL to the group standings resource. |
#' |teams_ref |character |`$ref` URL to the group teams index. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Groups Conferences
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_groups(year = 2024))
#' }
espn_cfb_groups <- function(year = NULL,
season_type = 2) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN groups endpoint.")
}
validate_year(year)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}/types/{season_type}/groups",
"?limit=200&lang=en®ion=us"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
items <- raw[["items"]] %||% list()
if (length(items) == 0) {
return(df)
}
rows <- list()
# Recursively dereference a group node and its children.
walk_group <- function(group_ref, parent_id) {
g <- get_json(group_ref)
gid <- as.character(g[["id"]] %||% NA)
ref_of <- function(k) {
v <- g[[k]]
if (is.list(v)) v[["$ref"]] %||% NA_character_ else NA_character_
}
rows[[length(rows) + 1L]] <<- data.frame(
season = suppressWarnings(as.integer(year)),
season_type = suppressWarnings(as.integer(season_type)),
group_id = gid,
name = as.character(g[["name"]] %||% NA),
abbreviation = as.character(g[["abbreviation"]] %||% NA),
short_name = as.character(g[["shortName"]] %||% NA),
is_conference = isTRUE(g[["isConference"]]),
parent_group_id = parent_id,
slug = as.character(g[["slug"]] %||% NA),
group_ref = group_ref,
standings_ref = ref_of("standings"),
teams_ref = ref_of("teams"),
stringsAsFactors = FALSE
)
# Recurse into children.
ch <- g[["children"]]
ch_ref <- if (is.list(ch)) ch[["$ref"]] %||% NA_character_ else NA_character_
if (!is.na(ch_ref)) {
ch_idx <- get_json(ch_ref)
for (c_it in ch_idx[["items"]] %||% list()) {
c_ref <- c_it[["$ref"]] %||% NA_character_
if (!is.na(c_ref)) walk_group(c_ref, gid)
}
}
}
for (it in items) {
g_ref <- it[["$ref"]] %||% NA_character_
if (!is.na(g_ref)) walk_group(g_ref, NA_character_)
}
if (length(rows) == 0) {
return(df)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble() |>
make_cfbfastR_data("Groups and conferences from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN groups data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Ranking Sources**
#' @description Get the index of poll / ranking sources ESPN publishes for
#' a college football season -- the AP Top 25, the Coaches Poll, the CFP
#' committee rankings, and the computer-derived polls.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}/rankings`.
#' The index returns one `$ref` per ranking source (~6-7); this wrapper
#' dereferences each and returns one row per source, with a count of how
#' many weekly snapshots that source published in the season. To pull the
#' actual ranked teams for a given week, feed `season`, `season_type`, and
#' a `week` to [espn_cfb_week_rankings()].
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_rankings', 'min_year']`
#' @return A data frame with one row per ranking source:
#'
#' |col_name |types |description |
#' |:-----------|:---------|:----------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |ranking_id |character |ESPN ranking source id. |
#' |name |character |Ranking source name (e.g. `AP Top 25`). |
#' |short_name |character |Ranking source short name (e.g. `AP Poll`). |
#' |type |character |Ranking source type code (e.g. `ap`, `cfp`). |
#' |n_snapshots |integer |Number of weekly snapshots published by the source. |
#' |ranking_ref |character |`$ref` URL to the ranking source resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Rankings
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_rankings(year = 2024))
#' }
espn_cfb_rankings <- function(year = NULL) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN rankings endpoint.")
}
validate_year(year)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}/rankings?limit=100&lang=en®ion=us"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
items <- raw[["items"]] %||% list()
if (length(items) == 0) {
return(df)
}
rows <- list()
for (it in items) {
ranking_ref <- it[["$ref"]] %||% NA_character_
r <- get_json(ranking_ref)
rows[[length(rows) + 1L]] <- data.frame(
season = suppressWarnings(as.integer(year)),
ranking_id = as.character(r[["id"]] %||% NA),
name = as.character(r[["name"]] %||% NA),
short_name = as.character(r[["shortName"]] %||% NA),
type = as.character(r[["type"]] %||% NA),
n_snapshots = length(r[["rankings"]] %||% list()),
ranking_ref = ranking_ref,
stringsAsFactors = FALSE
)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble() |>
make_cfbfastR_data("Ranking sources from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN rankings data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Season Detail**
#' @description Get the ESPN core-v2 detail record for a single college
#' football season -- start / end dates, the active season type, and the
#' `$ref` links to that season's types, rankings, athletes, awards, and
#' futures sub-resources.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}`.
#' Returns a single-row tibble. The `*_ref` columns are the season-scoped
#' sub-resource entry points -- feed `types_ref` to [espn_cfb_season_types()],
#' `rankings_ref` to [espn_cfb_rankings()], and so on. `active_type_*`
#' describes the season type ESPN currently considers "live" for the year
#' (preseason / regular / postseason / off-season).
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_season_info', 'min_year']`
#' @return A single-row data frame:
#'
#' |col_name |types |description |
#' |:-----------------|:---------|:-------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |display_name |character |ESPN display name for the season. |
#' |start_date |character |Season start timestamp (ISO 8601, UTC). |
#' |end_date |character |Season end timestamp (ISO 8601, UTC). |
#' |active_type_id |integer |ESPN id of the currently active season type. |
#' |active_type |integer |Active season type code (1 pre, 2 reg, 3 post). |
#' |active_type_name |character |Active season type name (e.g. `Regular Season`). |
#' |types_ref |character |`$ref` URL to the season types index. |
#' |rankings_ref |character |`$ref` URL to the season rankings index. |
#' |athletes_ref |character |`$ref` URL to the season athletes index. |
#' |awards_ref |character |`$ref` URL to the season awards index. |
#' |futures_ref |character |`$ref` URL to the season betting-futures index. |
#' |leaders_ref |character |`$ref` URL to the season leaders resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Season Info
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_season_info(year = 2024))
#' }
espn_cfb_season_info <- function(year = NULL) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN season info endpoint.")
}
validate_year(year)
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}?lang=en®ion=us"
)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
ref_of <- function(k) {
v <- raw[[k]]
if (is.list(v)) v[["$ref"]] %||% NA_character_ else NA_character_
}
active <- raw[["type"]]
if (!is.list(active)) active <- list()
df <- data.frame(
season = suppressWarnings(as.integer(raw[["year"]] %||% year)),
display_name = as.character(raw[["displayName"]] %||% NA),
start_date = as.character(raw[["startDate"]] %||% NA),
end_date = as.character(raw[["endDate"]] %||% NA),
active_type_id = suppressWarnings(as.integer(active[["id"]] %||% NA)),
active_type = suppressWarnings(as.integer(active[["type"]] %||% NA)),
active_type_name = as.character(active[["name"]] %||% NA),
types_ref = ref_of("types"),
rankings_ref = ref_of("rankings"),
athletes_ref = ref_of("athletes"),
awards_ref = ref_of("awards"),
futures_ref = ref_of("futures"),
leaders_ref = ref_of("leaders"),
stringsAsFactors = FALSE
) |>
dplyr::as_tibble() |>
make_cfbfastR_data("Season detail from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN season info data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Season Types**
#' @description Get the set of season types (preseason, regular season,
#' postseason, off-season) ESPN tracks for a college football season,
#' including each type's date window and capability flags.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}/types`.
#' The index returns one `$ref` per season type; this wrapper dereferences
#' each and returns one row per type (typically four: `1` preseason,
#' `2` regular season, `3` postseason, `4` off-season). The `season_type`
#' ids returned here are the values other Batch 5 wrappers
#' ([espn_cfb_season_weeks()], [espn_cfb_groups()], [espn_cfb_standings()],
#' [espn_cfb_week_rankings()]) accept as their `season_type` argument.
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_season_types', 'min_year']`
#' @return A data frame with one row per season type:
#'
#' |col_name |types |description |
#' |:-------------|:---------|:----------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |season_type |integer |ESPN season type id (1 pre, 2 reg, 3 post, 4 off). |
#' |type |integer |Season type code (mirrors `season_type`). |
#' |name |character |Season type name (e.g. `Regular Season`). |
#' |abbreviation |character |Season type abbreviation (e.g. `reg`). |
#' |slug |character |Season type slug (e.g. `regular-season`). |
#' |start_date |character |Season type start timestamp (ISO 8601, UTC). |
#' |end_date |character |Season type end timestamp (ISO 8601, UTC). |
#' |has_groups |logical |Whether the season type exposes a groups resource. |
#' |has_standings |logical |Whether the season type exposes standings. |
#' |type_ref |character |`$ref` URL to the season type resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Season Types
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_season_types(year = 2024))
#' }
espn_cfb_season_types <- function(year = NULL) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN season types endpoint.")
}
validate_year(year)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}/types?lang=en®ion=us"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
items <- raw[["items"]] %||% list()
if (length(items) == 0) {
return(df)
}
rows <- list()
for (it in items) {
type_ref <- it[["$ref"]] %||% NA_character_
s <- get_json(type_ref)
rows[[length(rows) + 1L]] <- data.frame(
season = suppressWarnings(as.integer(s[["year"]] %||% year)),
season_type = suppressWarnings(as.integer(s[["id"]] %||% NA)),
type = suppressWarnings(as.integer(s[["type"]] %||% NA)),
name = as.character(s[["name"]] %||% NA),
abbreviation = as.character(s[["abbreviation"]] %||% NA),
slug = as.character(s[["slug"]] %||% NA),
start_date = as.character(s[["startDate"]] %||% NA),
end_date = as.character(s[["endDate"]] %||% NA),
has_groups = isTRUE(s[["hasGroups"]]),
has_standings = isTRUE(s[["hasStandings"]]),
type_ref = type_ref,
stringsAsFactors = FALSE
)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble() |>
make_cfbfastR_data("Season types from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN season types data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Season Weeks**
#' @description Get the calendar of weeks ESPN tracks for a college football
#' season type -- each week's number, label, and start / end dates.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}/types/{season_type}/weeks`.
#' The index returns one `$ref` per week; this wrapper dereferences each and
#' returns one row per week (~15-16 for a regular season). The `week`
#' numbers returned here are the values [espn_cfb_week_rankings()] accepts.
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_season_weeks', 'min_year']`
#' @param season_type (*Integer* default 2): ESPN season type id.
#' `2` = regular season (the only type with a meaningful week calendar),
#' `3` = postseason, `1` = preseason.
#' @return A data frame with one row per week:
#'
#' |col_name |types |description |
#' |:-----------|:---------|:-------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |season_type |integer |ESPN season type id queried. |
#' |week |integer |Week number. |
#' |text |character |Week label (e.g. `Week 1`, `Bowls`). |
#' |start_date |character |Week start timestamp (ISO 8601, UTC). |
#' |end_date |character |Week end timestamp (ISO 8601, UTC). |
#' |week_ref |character |`$ref` URL to the week resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Season Weeks
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_season_weeks(year = 2024))
#' }
espn_cfb_season_weeks <- function(year = NULL,
season_type = 2) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN season weeks endpoint.")
}
validate_year(year)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}/types/{season_type}/weeks",
"?limit=100&lang=en®ion=us"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
items <- raw[["items"]] %||% list()
if (length(items) == 0) {
return(df)
}
rows <- list()
for (it in items) {
week_ref <- it[["$ref"]] %||% NA_character_
w <- get_json(week_ref)
rows[[length(rows) + 1L]] <- data.frame(
season = suppressWarnings(as.integer(year)),
season_type = suppressWarnings(as.integer(season_type)),
week = suppressWarnings(as.integer(w[["number"]] %||% NA)),
text = as.character(w[["text"]] %||% NA),
start_date = as.character(w[["startDate"]] %||% NA),
end_date = as.character(w[["endDate"]] %||% NA),
week_ref = week_ref,
stringsAsFactors = FALSE
)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble() |>
make_cfbfastR_data("Season weeks from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN season weeks data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Seasons Index**
#' @description Get the index of every college football season ESPN tracks
#' in its core-v2 API, with each season's start and end dates.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons`.
#' The index is `$ref`-paginated; this wrapper walks every page, dereferences
#' each season `$ref`, and returns one row per season. ESPN's CFB season
#' coverage runs back to the early 1900s, so the table is long -- filter on
#' `season` for the years you need. Use [espn_cfb_season_info()] for the
#' full per-season detail (season types, rankings, awards, futures refs).
#' @return A data frame with one row per season:
#'
#' |col_name |types |description |
#' |:------------|:---------|:-----------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |display_name |character |ESPN display name for the season. |
#' |start_date |character |Season start timestamp (ISO 8601, UTC). |
#' |end_date |character |Season end timestamp (ISO 8601, UTC). |
#' |season_ref |character |`$ref` URL to the season resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Seasons
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_seasons())
#' }
espn_cfb_seasons <- function() {
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
# Fetch + parse one JSON resource with the shared ESPN headers.
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
base_url <- paste0(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons?limit=100&lang=en®ion=us"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(base_url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
page_count <- raw[["pageCount"]] %||% 1L
items <- raw[["items"]] %||% list()
# Walk remaining pages.
if (page_count > 1L) {
for (pg in 2:page_count) {
pg_raw <- get_json(paste0(base_url, "&page=", pg))
items <- c(items, pg_raw[["items"]] %||% list())
}
}
if (length(items) == 0) {
return(df)
}
rows <- list()
for (it in items) {
season_ref <- it[["$ref"]] %||% NA_character_
s <- get_json(season_ref)
rows[[length(rows) + 1L]] <- data.frame(
season = suppressWarnings(as.integer(s[["year"]] %||% NA)),
display_name = as.character(s[["displayName"]] %||% NA),
start_date = as.character(s[["startDate"]] %||% NA),
end_date = as.character(s[["endDate"]] %||% NA),
season_ref = season_ref,
stringsAsFactors = FALSE
)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble() |>
make_cfbfastR_data("Seasons index from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN seasons data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Standings (Long Format)**
#' @description Get ESPN's standings for a college football group -- the
#' full set of record splits (overall, home, away, conference, ...) and
#' every standings statistic (wins, losses, point differential,
#' streak, ...) for every team in the group.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}/types/{season_type}/groups/{group_id}/standings`.
#' The standings resource nests three levels deep -- team -> record split
#' -> statistic -- so this wrapper returns long format: one row per
#' (team x record split x statistic). The long shape absorbs ESPN's habit
#' of adding standings stats across seasons. Pivot wider keyed on
#' `stat_name` (within a `record_type`) when a wide table is wanted.
#'
#' `group_id = 90` (NCAA Division I) is the sensible default and yields
#' every D-I team in one call. Conference-level `group_id`s -- enumerate
#' them with [espn_cfb_groups()] -- restrict the table to that conference.
#'
#' When `team_detail = TRUE` (the default) the ESPN team catalog
#' ([espn_cfb_teams()]) is fetched once and friendly team fields are joined
#' in next to the `team_id` column -- `team_name`, `team_abbreviation`,
#' `team_location`, `team_display_name`, `team_short_display_name`,
#' `team_nickname`, `team_color`, `team_alternate_color`,
#' `team_logo_href`, and `team_logo_dark_href`, inserted immediately after
#' `team_id`.
#' A catalog failure degrades to `NA` rather than erroring the wrapper. Set
#' `team_detail = FALSE` to skip the catalog fetch and the join.
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_standings', 'min_year']`
#' @param group_id (*Integer* default 90): ESPN group id. `90` = NCAA
#' Division I (all D-I teams). Conference group ids also work.
#' @param season_type (*Integer* default 2): ESPN season type id.
#' `2` = regular season.
#' @param team_detail (*Logical*): when `TRUE` (default), fetch the ESPN
#' team catalog once and join friendly team fields next to the `team_id`
#' column (see *Details*). Set `FALSE` to skip the catalog fetch and the
#' join.
#' @return A data frame with one row per team-record-statistic:
#'
#' |col_name |types |description |
#' |:----------------------|:---------|:----------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |season_type |integer |ESPN season type id queried. |
#' |group_id |character |ESPN group id queried. |
#' |team_id |character |ESPN team id (parsed from `team_ref`). |
#' |team_name |character |Team nickname; `team_detail = TRUE` only. |
#' |team_abbreviation |character |Team abbreviation; `team_detail = TRUE` only. |
#' |team_location |character |Team location / school name; `team_detail = TRUE` only.|
#' |team_display_name |character |Full team display name; `team_detail = TRUE` only. |
#' |team_short_display_name|character |Short team display name; `team_detail = TRUE` only. |
#' |team_nickname |character |Team nickname label; `team_detail = TRUE` only. |
#' |team_color |character |Primary team color; `team_detail = TRUE` only. |
#' |team_alternate_color |character |Alternate team color; `team_detail = TRUE` only. |
#' |team_logo_href |character |Default team logo URL; `team_detail = TRUE` only. |
#' |team_logo_dark_href |character |Dark-variant team logo URL; `team_detail = TRUE` only.|
#' |record_type |character |Record split type code (e.g. `total`, `homerecord`). |
#' |record_name |character |Record split name (e.g. `Overall`, `Home`). |
#' |record_summary |character |Record split summary (e.g. `14-0`). |
#' |stat_name |character |Standings statistic key (e.g. `wins`, `streak`). |
#' |abbreviation |character |Statistic abbreviation. |
#' |display_name |character |Human-readable statistic name. |
#' |value |numeric |Statistic value. |
#' |display_value |character |Display-formatted statistic value. |
#' |team_ref |character |`$ref` URL to the per-season team resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Standings
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_standings(year = 2024))
#' try(espn_cfb_standings(year = 2024, team_detail = FALSE))
#' }
espn_cfb_standings <- function(year = NULL,
group_id = 90,
season_type = 2,
team_detail = TRUE) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN standings endpoint.")
}
validate_year(year)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}/types/{season_type}/groups/",
"{group_id}/standings?lang=en®ion=us"
)
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
items <- raw[["items"]] %||% list()
if (length(items) == 0) {
return(df)
}
# The first standings item is the "overall" table.
first <- items[[1]]
overall <- if (!is.null(first[["$ref"]])) {
get_json(first[["$ref"]])
} else {
first
}
entries <- overall[["standings"]] %||% list()
if (length(entries) == 0) {
return(df)
}
rows <- list()
for (en in entries) {
team_ref <- if (is.list(en[["team"]])) {
en[["team"]][["$ref"]] %||% NA_character_
} else {
NA_character_
}
team_id <- if (!is.na(team_ref)) {
sub(".*/teams/([0-9]+).*", "\\1", team_ref)
} else {
NA_character_
}
for (rec in en[["records"]] %||% list()) {
rec_type <- as.character(rec[["type"]] %||% NA)
rec_name <- as.character(rec[["name"]] %||% NA)
rec_summary <- as.character(rec[["summary"]] %||% NA)
for (s in rec[["stats"]] %||% list()) {
rows[[length(rows) + 1L]] <- data.frame(
season = suppressWarnings(as.integer(year)),
season_type = suppressWarnings(as.integer(season_type)),
group_id = as.character(group_id),
team_id = team_id,
record_type = rec_type,
record_name = rec_name,
record_summary = rec_summary,
stat_name = as.character(s[["name"]] %||% NA),
abbreviation = as.character(s[["abbreviation"]] %||% NA),
display_name = as.character(s[["displayName"]] %||% NA),
value = suppressWarnings(as.numeric(s[["value"]] %||% NA)),
display_value = as.character(s[["displayValue"]] %||% NA),
team_ref = team_ref,
stringsAsFactors = FALSE
)
}
}
}
if (length(rows) == 0) {
return(df)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble()
# Join the ESPN team catalog onto the team_id column once, before
# the make_cfbfastR_data() class/attribute stamp.
if (isTRUE(team_detail)) {
df <- .espn_cfb_attach_team_detail(df, .espn_cfb_team_lookup())
}
df <- df |>
make_cfbfastR_data("Standings data from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN standings data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
#' @title
#' **ESPN College Football Weekly Rankings**
#' @description Get the ranked teams from every poll ESPN published in a
#' single week of a college football season -- the AP Top 25, the Coaches
#' Poll, the CFP committee rankings, and the computer polls, with each
#' team's rank, points, and first-place votes.
#' @details Wraps the ESPN core-v2 endpoint
#' `sports.core.api.espn.com/v2/sports/football/leagues/college-football/seasons/{year}/types/{season_type}/weeks/{week}/rankings`.
#' The week index returns one `$ref` per poll; this wrapper dereferences
#' each poll and returns one row per (poll x ranked team). Both the
#' top-25 ranked teams (`rank_type = "ranked"`) and the receiving-votes
#' teams ESPN lists below the cutoff (`rank_type = "others"`) are included.
#'
#' When `team_detail = TRUE` (the default) the ESPN team catalog
#' ([espn_cfb_teams()]) is fetched once and friendly team fields are joined
#' in next to the `team_id` column -- `team_name`, `team_abbreviation`,
#' `team_location`, `team_display_name`, `team_short_display_name`,
#' `team_nickname`, `team_color`, `team_alternate_color`,
#' `team_logo_href`, and `team_logo_dark_href`, inserted immediately after
#' `team_id`.
#' A catalog failure degrades to `NA` rather than erroring the wrapper. Set
#' `team_detail = FALSE` to skip the catalog fetch and the join; teams are
#' then returned as ESPN team ids only.
#' @param year (*Integer* required): Season, 4 digit format (*YYYY*). \cr
#' Minimum value accepted: `r min_year_map_df[min_year_map_df$function_name == 'espn_cfb_week_rankings', 'min_year']`
#' @param week (*Integer* required): Week number within the season type.
#' @param season_type (*Integer* default 2): ESPN season type id.
#' `2` = regular season, `3` = postseason.
#' @param team_detail (*Logical*): when `TRUE` (default), fetch the ESPN
#' team catalog once and join friendly team fields next to the `team_id`
#' column (see *Details*). Set `FALSE` to skip the catalog fetch and the
#' join.
#' @return A data frame with one row per poll-team:
#'
#' |col_name |types |description |
#' |:----------------------|:---------|:-------------------------------------------------|
#' |season |integer |Season (4-digit year). |
#' |season_type |integer |ESPN season type id queried. |
#' |week |integer |Week number queried. |
#' |ranking_id |character |ESPN poll id. |
#' |ranking_name |character |Poll name (e.g. `AP Top 25`). |
#' |ranking_type |character |Poll type code (e.g. `ap`, `coaches`, `cfp`). |
#' |occurrence |character |Poll occurrence label (e.g. `Week 8`, `Preseason`). |
#' |rank_type |character |`ranked` for ranked teams, `others` for receiving-votes teams. |
#' |current_rank |integer |Current rank (`0` for receiving-votes teams). |
#' |previous_rank |integer |Rank in the previous poll (`0` if unranked). |
#' |points |numeric |Poll points awarded to the team. |
#' |first_place_votes |integer |Number of first-place votes received. |
#' |trend |character |Movement vs the previous poll (e.g. `-`, `+3`). |
#' |record_summary |character |Team record at the time of the poll (e.g. `7-1`). |
#' |team_id |character |ESPN team id (parsed from `team_ref`). |
#' |team_name |character |Team nickname; `team_detail = TRUE` only. |
#' |team_abbreviation |character |Team abbreviation; `team_detail = TRUE` only. |
#' |team_location |character |Team location / school name; `team_detail = TRUE` only.|
#' |team_display_name |character |Full team display name; `team_detail = TRUE` only.|
#' |team_short_display_name|character |Short team display name; `team_detail = TRUE` only.|
#' |team_nickname |character |Team nickname label; `team_detail = TRUE` only. |
#' |team_color |character |Primary team color; `team_detail = TRUE` only. |
#' |team_alternate_color |character |Alternate team color; `team_detail = TRUE` only. |
#' |team_logo_href |character |Default team logo URL; `team_detail = TRUE` only. |
#' |team_logo_dark_href |character |Dark-variant team logo URL; `team_detail = TRUE` only.|
#' |team_ref |character |`$ref` URL to the per-season team resource. |
#'
#' @importFrom jsonlite fromJSON
#' @importFrom httr2 request req_headers req_retry req_perform resp_body_string
#' @importFrom cli cli_abort
#' @importFrom dplyr as_tibble bind_rows
#' @importFrom glue glue
#' @importFrom rlang "%||%"
#' @keywords ESPN CFB Week Rankings
#' @family ESPN CFB Functions
#' @export
#' @examples
#' \donttest{
#' try(espn_cfb_week_rankings(year = 2024, week = 8))
#' try(espn_cfb_week_rankings(year = 2024, week = 8, team_detail = FALSE))
#' }
espn_cfb_week_rankings <- function(year = NULL,
week = NULL,
season_type = 2,
team_detail = TRUE) {
# Validation ----
if (is.null(year)) {
cli::cli_abort("{.arg year} is required for the ESPN week rankings endpoint.")
}
if (is.null(week)) {
cli::cli_abort("{.arg week} is required for the ESPN week rankings endpoint.")
}
validate_year(year)
headers <- c(
`User-Agent` = paste0(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
),
`Accept` = "application/json, text/plain, */*",
`Origin` = "https://www.espn.com",
`Referer` = "https://www.espn.com/"
)
get_json <- function(u) {
httr2::request(u) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform() |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
}
url <- glue::glue(
"https://sports.core.api.espn.com/v2/sports/football/leagues/",
"college-football/seasons/{year}/types/{season_type}/weeks/",
"{week}/rankings?lang=en®ion=us"
)
# Pull one poll's ranks[] / others[] arrays into rows.
poll_rows <- function(poll) {
ranking_id <- as.character(poll[["id"]] %||% NA)
ranking_name <- as.character(poll[["name"]] %||% NA)
ranking_type <- as.character(poll[["type"]] %||% NA)
occ <- poll[["occurrence"]]
occurrence <- if (is.list(occ)) {
as.character(occ[["displayValue"]] %||% NA)
} else {
NA_character_
}
one_entry <- function(en, rank_type) {
team_ref <- if (is.list(en[["team"]])) {
en[["team"]][["$ref"]] %||% NA_character_
} else {
NA_character_
}
team_id <- if (!is.na(team_ref)) {
sub(".*/teams/([0-9]+).*", "\\1", team_ref)
} else {
NA_character_
}
rec <- en[["record"]]
rec_summary <- if (is.list(rec)) {
as.character(rec[["summary"]] %||% NA)
} else {
NA_character_
}
data.frame(
season = suppressWarnings(as.integer(year)),
season_type = suppressWarnings(as.integer(season_type)),
week = suppressWarnings(as.integer(week)),
ranking_id = ranking_id,
ranking_name = ranking_name,
ranking_type = ranking_type,
occurrence = occurrence,
rank_type = rank_type,
current_rank = suppressWarnings(as.integer(en[["current"]] %||% NA)),
previous_rank = suppressWarnings(as.integer(en[["previous"]] %||% NA)),
points = suppressWarnings(as.numeric(en[["points"]] %||% NA)),
first_place_votes = suppressWarnings(as.integer(en[["firstPlaceVotes"]] %||% NA)),
trend = as.character(en[["trend"]] %||% NA),
record_summary = rec_summary,
team_id = team_id,
team_ref = team_ref,
stringsAsFactors = FALSE
)
}
out <- list()
for (en in poll[["ranks"]] %||% list()) {
out[[length(out) + 1L]] <- one_entry(en, "ranked")
}
for (en in poll[["others"]] %||% list()) {
out[[length(out) + 1L]] <- one_entry(en, "others")
}
out
}
df <- data.frame()
tryCatch(
expr = {
res <- httr2::request(url) |>
httr2::req_headers(!!!headers) |>
httr2::req_retry(max_tries = 3, backoff = ~ 2) |>
httr2::req_perform()
check_status(res)
raw <- res |>
httr2::resp_body_string(encoding = "UTF-8") |>
jsonlite::fromJSON(simplifyVector = FALSE)
items <- raw[["items"]] %||% list()
if (length(items) == 0) {
return(df)
}
rows <- list()
for (it in items) {
poll <- if (!is.null(it[["$ref"]])) get_json(it[["$ref"]]) else it
rows <- c(rows, poll_rows(poll))
}
if (length(rows) == 0) {
return(df)
}
df <- dplyr::bind_rows(rows) |>
dplyr::as_tibble()
# Join the ESPN team catalog onto the team_id column once, before
# the make_cfbfastR_data() class/attribute stamp.
if (isTRUE(team_detail)) {
df <- .espn_cfb_attach_team_detail(df, .espn_cfb_team_lookup())
}
df <- df |>
make_cfbfastR_data("Weekly rankings from ESPN", Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments or no ESPN week rankings data available!"))
},
warning = function(w) {
},
finally = {
}
)
return(.attach_query_meta_auto(df))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.