Skip to main content

ESPN Fantasy Football 2020 Part 1: Accessing the API

·1345 words·7 mins
Dusty Turner
Author
Dusty Turner
Army Officer • Operations Research Systems Analyst • Statistician

Its that time of year!

Its that time of year again. The time when we dust off the old ESPN fantasy football API R code and fix everything that broke in the last year.

Here’s what I hope to show over the next few posts.

  1. How to access your ESPN public fantasy football league’s data.
  2. How to organize that data and create a few interesting displays.
  3. How to create a dashboard to supplement your league’s fun.

My ultimate goal is to build this into a package on Github. I’ve got that started, but its currently a work in progress.

All this code and more is located at my github.

And a special thanks to my friend and collaborator Jim Pleuss who really enhanced so much of this project!

A little background

This ESPN API exploration is a fun annual process. I first started doing this in 2018. In 2019, after some hacking and some updates, I posted about how to access public leagues, how to access private leagues using reticulate and python, and how to access private leagues in R. I went so far last year as to build a dashboard with the data to further analyze my league.

Well, in 2020, ESPN changed their some of their end points. After a lot of exploration and the help of Chrome’s inspect tool, I present code to access ESPN’s API. This should work for any league. If you find it does not work for your league, I’d be happy to take a pull request or two to handle more nuances.

How to access the API

In the function below, you provide arguments for your leagueID and the week of the information you would like to extract.

To find your leagueID, look at the URL of your fantasy football home page on ESPN.

The output of this function will be a JSON file. I provide code to explore the JSON file as well.

library(tidyverse)
library(gt)

get_data <- function(leagueID = leagueID, per_id = per_id){
base = "http://fantasy.espn.com/apis/v3/games/ffl/seasons/"
year = "2020"
mid = "/segments/0/leagues/"
tail = str_c("?view=mDraftDetail",
             "&view=mLiveScoring",
             "&view=mMatchupScore,",
             "&view=mPendingTransactions",
             "&view=mPositionalRatings",
             "&view=mRoster",
             "&view=mSettings",
             "&view=mTeam",
             "&view=modular",
             "&view=mNav",
             "&view=mMatchupScore",
             "&scoringPeriodId="
             )

url = paste0(base,year,mid,leagueID,tail,per_id)

ESPNGet <- httr::GET(url = url)
ESPNRaw <- rawToChar(ESPNGet$content)
ESPNFromJSON <- jsonlite::fromJSON(ESPNRaw)

return(ESPNFromJSON)

}

leagueID <- 89417258
per_id <- 2

ESPNFromJSON <- get_data(leagueID = leagueID, per_id = per_id)

We’ll can explore the data using the listviewer package. You can peruse the data from this JSON below.

ESPNFromJSON %>% listviewer::jsonedit()

What can we do with this data?

I’m glad you asked. In subsequent posts, I’ll provide more examples, but here are a few quick things:

Extract one player’s information

number_of_teams <- length(ESPNFromJSON$teams$id)
team_ids <- ESPNFromJSON$teams$id

player_extract <- function(team_number = 1, player_number = 1, per_id = per_id, ESPNFromJSON = ESPNFromJSON){
  player_week <-
    tibble(
      team = str_c(ESPNFromJSON$teams$location[team_number]," ",ESPNFromJSON$teams$nickname[team_number]),
      teamId = ESPNFromJSON$teams$id[team_number],
      fullName = ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$fullName[player_number],
      appliedTotal = ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$stats[[player_number]]$appliedTotal,
      seasonId = ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$stats[[player_number]]$seasonId,
      scoringPeriodId = ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$stats[[player_number]]$scoringPeriodId,
      statsplitTypeId = ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$stats[[player_number]]$statSplitTypeId,
      externalId = ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$stats[[player_number]]$externalId,
      lineupSlot_id = ESPNFromJSON$teams$roster$entries[[team_number]]$lineupSlotId[player_number],
      eligibleSlots = list(ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$eligibleSlots[[player_number]])
    ) %>%
    filter(seasonId==2020) %>%
    filter(scoringPeriodId != 0) %>%
    filter(scoringPeriodId == per_id)
  return(player_week)
}

player_extract(ESPNFromJSON = ESPNFromJSON,team_number = 1,player_number = 1,per_id = per_id) %>% gt()
teamteamIdfullNameappliedTotalseasonIdscoringPeriodIdstatsplitTypeIdexternalIdlineupSlot_ideligibleSlots
'R'm Chair Quarterback1Alvin Kamara38.4000020202140122023122, 3, 23, 7, 20, 21
'R'm Chair Quarterback1Alvin Kamara20.442412020212020222, 3, 23, 7, 20, 21

Get all players from all teams

To extract the players for each team, we first need to know how many players are on each team each week. One might think this is consistent, but with an IR slot, this may change from week to week.

First we create a function to determine how many roster spots are on a given team.

get_roster_slots <- function(team_number=1){
  return(tibble(team_number = team_number, player_slot = 1:length(ESPNFromJSON$teams$roster$entries[[team_number]]$playerPoolEntry$player$stats)))
}

Then we map that over all teams.

team_player_slots <- purrr::map_dfr(1:number_of_teams,~get_roster_slots(team_number = .x))

team_player_slots
## # A tibble: 182 x 2
##    team_number player_slot
##          <int>       <int>
##  1           1           1
##  2           1           2
##  3           1           3
##  4           1           4
##  5           1           5
##  6           1           6
##  7           1           7
##  8           1           8
##  9           1           9
## 10           1          10
## # ... with 172 more rows

Now, we map the team_number and player_slots data to the player_extract() function to get every player from every team.

team_list <- 
  purrr::map2_dfr(
    team_player_slots$team_number,
    team_player_slots$player_slot, 
    ~player_extract(
      ESPNFromJSON = ESPNFromJSON,
      team_number = .x,
      player_number = .y, 
      per_id = per_id
      )
    )

team_list %>% 
  group_by(teamId) %>% 
  slice_head(n = 1) %>% 
  ungroup() %>% 
  gt() %>% 
  tab_header("First player on each team")
First player on each team
teamteamIdfullNameappliedTotalseasonIdscoringPeriodIdstatsplitTypeIdexternalIdlineupSlot_ideligibleSlots
'R'm Chair Quarterback1Alvin Kamara38.4000020202140122023122, 3, 23, 7, 20, 21
Twenty Twenty2Miles Sanders15.504222020212020222, 3, 23, 7, 20, 21
The Plainsmen3Austin Ekeler18.8000020202140122023522, 3, 23, 7, 20, 21
Mother Hen4Christian McCaffrey24.8000020202140122032922, 3, 23, 7, 20, 21
Analysis Paralysis5Lamar Jackson17.5600020202140122018100, 7, 20, 21
ForWard Progress6Derrick Henry8.4000020202140122020422, 3, 23, 7, 20, 21
Syntax Error7Travis Kelce24.0000020202140122023565, 6, 23, 7, 20, 21
Chief of Chiefs8Clyde Edwards-Helaire13.00000202021401220235225, 2, 3, 23, 7, 20, 21
Monkey King9Ezekiel Elliott22.2000020202140122024922, 3, 23, 7, 20, 21
Palindrome Tikkit10Dalvin Cook17.1000020202140122019222, 3, 23, 7, 20, 21
Enemy of the Stat #111Saquon Barkley2.8000020202140122028122, 3, 23, 7, 20, 21
The Mandalorian12Michael Thomas0.00000202021401220231203, 4, 5, 23, 7, 20, 21

To get some really good information, we need to join this to the teams and their weekly schedule.

So lets extract the schedule….

schedule <-
  tibble(
    home = ESPNFromJSON$schedule$away$teamId,
    away = ESPNFromJSON$schedule$home$teamId,
    scoringPeriodId = ESPNFromJSON$schedule$matchupPeriodId,
    gameId = ESPNFromJSON$schedule$id
  ) %>%
  pivot_longer(cols = c(home,away), values_to = "teamId")

Then join the players and schedule.

team_list <-
team_list %>% 
  left_join(schedule) %>%
  mutate(points_type = if_else(str_length(externalId) > 6, "actual", "projected")) %>%
  relocate(team:appliedTotal, points_type)

team_list %>% 
  group_by(teamId) %>% 
  slice_head(n = 1) %>% 
  ungroup() %>% 
  gt() %>% 
  tab_header("First player on each team")
First player on each team
teamteamIdfullNameappliedTotalpoints_typeseasonIdscoringPeriodIdstatsplitTypeIdexternalIdlineupSlot_ideligibleSlotsgameIdname
'R'm Chair Quarterback1Alvin Kamara38.40000actual20202140122023122, 3, 23, 7, 20, 217home
Twenty Twenty2Miles Sanders15.50422projected2020212020222, 3, 23, 7, 20, 2111away
The Plainsmen3Austin Ekeler18.80000actual20202140122023522, 3, 23, 7, 20, 219home
Mother Hen4Christian McCaffrey24.80000actual20202140122032922, 3, 23, 7, 20, 2110home
Analysis Paralysis5Lamar Jackson17.56000actual20202140122018100, 7, 20, 217away
ForWard Progress6Derrick Henry8.40000actual20202140122020422, 3, 23, 7, 20, 2112home
Syntax Error7Travis Kelce24.00000actual20202140122023565, 6, 23, 7, 20, 219away
Chief of Chiefs8Clyde Edwards-Helaire13.00000actual202021401220235225, 2, 3, 23, 7, 20, 2110away
Monkey King9Ezekiel Elliott22.20000actual20202140122024922, 3, 23, 7, 20, 2112away
Palindrome Tikkit10Dalvin Cook17.10000actual20202140122019222, 3, 23, 7, 20, 218home
Enemy of the Stat #111Saquon Barkley2.80000actual20202140122028122, 3, 23, 7, 20, 218away
The Mandalorian12Michael Thomas0.00000actual202021401220231203, 4, 5, 23, 7, 20, 2111home

Now, we can really start analyzing our league! But that’s for the next post!