Accessing ESPN’s New V3 API for Private Leagues: How We Got Here

This marks post 3 of n of my 2019 ESPN Fantasy Football blog posts. In the last few months, ESPN upgraded their API from V2 to V3, breaking all of our previous work accessing and analysing the data from the API.

In my last two posts, I explored how to access data from ESPN’s V3 API for public leagues. My best efforts to access data for private leagues in R fell short. Using the work from my friend Steve on his blog, I was able to execute Python code inside R chunks with the library reticulate. I wrote about that process in post 2.

My best efforts to access ESPN’s new API in R fell short, that is, until I asked my friend Nick. Nick crushed this and I’m extremely grateful that he figured it out. I’ll detail below what he did.

A Recap

For public league, you can simply request the data.

We’ll start with our URL. Of course, replace the league ID with your league ID. Though I explain the tail of the URL in detail on my previous post, different derivatives of this will return different data.

base = "http://fantasy.espn.com/apis/v3/games/ffl/seasons/"
year = "2019"
mid = "/segments/0/leagues/"
leagueID = "12345678"
tail = "?view=mDraftDetail&view=mLiveScoring&view=mMatchupScore&view=mPendingTransactions&view=mPositionalRatings&view=mSettings&view=mTeam&view=modular&view=mNav&view=mMatchupScore"
url = paste0(base,year,mid,leagueID,tail)
url
## [1] "http://fantasy.espn.com/apis/v3/games/ffl/seasons/2019/segments/0/leagues/12345678?view=mDraftDetail&view=mLiveScoring&view=mMatchupScore&view=mPendingTransactions&view=mPositionalRatings&view=mSettings&view=mTeam&view=modular&view=mNav&view=mMatchupScore"

Request Private League Data

Unsuccessful Attempts

When requesting the data as the code below shows, we pass the URL to the GET command.

url = "http://fantasy.espn.com/apis/v3/games/ffl/seasons/2019/segments/0/leagues/89417258?view=mDraftDetail&view=mLiveScoring&view=mMatchupScore&view=mPendingTransactions&view=mPositionalRatings&view=mSettings&view=mTeam&view=modular&view=mNav&view=mMatchupScore&scoringPeriodId=1"
ESPNGet <- httr::GET(url = url)
ESPNGet$status_code
## [1] 200

The status_code returns 401. This is good and bad news. Bad news because the API doesn’t return any data. In fact, we are not authorized to view this league

ESPNRaw <- rawToChar(ESPNGet$content)
ESPNFromJSON <- jsonlite::fromJSON(ESPNRaw)
ESPNFromJSON$message
## NULL

But a status_code of 401 is good news because it means the page exists (IE, we have our URL correct), but that its blocked.

Request Private League Data

Successful Attempts

Finding Cookies

To access private league data, you must pass your cookies in your API request to prove you belong to the league.

The ESPN fantasy football API requires two cookies; swid and espn_s2.

You can find both of these cookies in Chrome in the following path:

Settings >> Privacy and Security >> Site Settings >> Cookies >> See All Cookies and Site Data

They’ll be listed among the ‘ESPN’ cookies. You can find these two cookies similarly in other browsers.

swid = "{cookie-with-curly-brackets-included}"
espn_s2 =  "very%long%cookie%296%ish%characters%long%with%no%brackets%"
Passing Cookies

My previous attempts to access the API by passing cookies are detailed in my previous post. No need to detail unsuccessful attempts here. Lets get to the solution. Thanks again Nick!

We have previously built our URL.

url
## [1] "http://fantasy.espn.com/apis/v3/games/ffl/seasons/2019/segments/0/leagues/89417258?view=mDraftDetail&view=mLiveScoring&view=mMatchupScore&view=mPendingTransactions&view=mPositionalRatings&view=mSettings&view=mTeam&view=modular&view=mNav&view=mMatchupScore&scoringPeriodId=1"

Next, we save our cookies:

cookies <- c(`swid` = "{cookie-with-curly-brackets-included}",
             `espn_s2` =  "very%long%cookie%296%ish%characters%long%with%no%brackets%"
)

Then we pass the cookies with the GET command.

cookie <- paste(names(cookies), cookies, sep = "=", collapse = ";")
ESPNGet <- httr::GET(url = url, 
                     config = httr::config(cookie = cookie)
                     )

We can verify that the handshake was successful by checking the status_code.

ESPNGet$status_code
## [1] 200

200! Success!!

We can now access the data.

ESPNRaw <- rawToChar(ESPNGet$content)
ESPNFromJSON <- jsonlite::fromJSON(ESPNRaw)

Exploring the JSON

Now that we have our data, we need to explore the JSON to see what is available. The code below will help you explore what data is available through the API.

listviewer::jsonedit(ESPNFromJSON)

In the meantime, here is an example of the data you can pull from the JSON.

library(tidyverse)

tibble(
  AwayID = ESPNFromJSON$schedule$away$teamId,
  AwayPoints = ESPNFromJSON$schedule$away$totalPoints,
  HomeID =ESPNFromJSON$schedule$home$teamId,
  HomePoints = ESPNFromJSON$schedule$home$totalPoints,
  Winner =ESPNFromJSON$schedule$winner
  ) %>%
  left_join(
    tibble(
      id = ESPNFromJSON$teams$id,
      AwayTeam = paste(ESPNFromJSON$teams$location,ESPNFromJSON$teams$nickname)
    ), by = c("AwayID"="id")
    ) %>%
  left_join(
    tibble(
      id = ESPNFromJSON$teams$id,
      HomeTeam = paste(ESPNFromJSON$teams$location,ESPNFromJSON$teams$nickname)
    ), by = c("HomeID"="id")
    ) 
## # A tibble: 80 x 7
##    AwayID AwayPoints HomeID HomePoints Winner AwayTeam          HomeTeam        
##     <int>      <dbl>  <int>      <dbl> <chr>  <chr>             <chr>           
##  1      2      138.       9       97.9 AWAY   Philly Chapmania~ Dallas The boys 
##  2      3      124.       7      128.  HOME   The Plainsmen     Compute  This!  
##  3      6      135.       4      108.  AWAY   Team Ward         The OBJective F~
##  4      1      109.       5      120.  HOME   'R'm Chair Quart~ Analysis Paraly~
##  5     10      130.       8      156.  HOME   Palindrome Tikkit The Chief       
##  6      6      115.       3      143.  HOME   Team Ward         The Plainsmen   
##  7      9      136.       1      100.  AWAY   Dallas The boys   'R'm Chair Quar~
##  8      2       72.6     10      125.  HOME   Philly Chapmania~ Palindrome Tikk~
##  9      7      136.       5      120.  AWAY   Compute  This!    Analysis Paraly~
## 10      4      145.       8      109.  AWAY   The OBJective Fu~ The Chief       
## # ... with 70 more rows

Future Posts

In future posts, I plan on doing the following:

  • Evaluate measures of luck. Which teams win more than they should? Which teams out perform their projections?
  • Evaluate ESPN’s projections. How accurate are the projections? Are some positions easier to project than others?
  • Evaluate who is the best coach. Which managers are picking their optimal lineup?

Feedback

As always, I do this blog to learn new things, hopefully teach others from my experience, and hopefully receive (respectful and helpful) feedback.

Please leave your thoughts below!!