Bots Over Ball: Fantasy Football Automated

Intro

If you know me, you know I know criminally little about football. Despite this shortcoming, I somehow found myself in a Fantasy League with friends who knew plenty.

So, after relying on autodraft, a little guessing, and a lot of luck, I turned to what I did know: Automation. With a little help from Tines and ChatGPT, I was able to build a system that managed my team for me - Updating my roster, offering suggestions, and keeping me competitive without me needing to know who any of these people actually are.

Check out the companion repo on GitHub for ready-to-use scripts and ready-to-import Tines Story: companion GitHub repository

Big thanks to Daniel Thibodeau for answering all my inane, probably-obvious fantasy football questions!

football-probably


Yahoo Fantasy Football API

The Yahoo Fantasy Football API is the backbone of these automations. Let’s take a look at the basics and how to gain access.

Overview

Yahoo’s API structure revolves around leagues, teams, and players, organized in a hierarchical fashion. At the top, you have your league information—key details about matchups, standings, etc. Below that, you have teams - Each has their own meta attached related to owner details and players. Finally, players provides a number of useful (and not-so-useful) player-specific statistics and information we'll need for management.

Endpoints of Interest

The Yahoo Fantasy Football API provides several key endpoints essential for building an automated fantasy manager. These endpoints will allow us to pull live data, view team standings, and move our players around programmatically.

For reference, https://fantasysports.yahooapis.com/fantasy/v2/ is the root URL, which is prepended to each of the resources below.

Here’s a rundown of the main endpoints we’ll be using:

  • Games Resource:
    Retrieves games for the logged-in user, filtered by the current year: /users;use_login=1/games;seasons={YEAR}

  • Leagues Resource:
    Lists leagues for the current user based on their game keys: /users;use_login=1/games;game_keys={GAME_KEY}/leagues

  • Teams Resource:
    Retrieves teams in the selected league, based on the league key: /league/{LEAGUE_KEY}/teams;use_login=1

  • League Standings:
    Fetches current standings for the league, which is useful for keeping track of rankings: /league/{LEAGUE_KEY}/standings

  • Roster Info and Updates:
    Retrieves or updates roster information for a specific team, allowing us to manage players on the roster: /team/{TEAM_KEY}/roster

Using these endpoints, Tines will be able to automate critical tasks for your team, from tracking player statistics to adjusting your roster in real time.

Setting up the Tines Credential

To access the Yahoo Fantasy Football API in Tines, we’ll first need to create and configure OAuth2 credentials through Yahoo’s Developer Network. Here’s a step-by-step guide:

Steps to Set Up Yahoo Fantasy Football API Key in Tines

  1. Create a Yahoo Developer Account
  2. Visit the Yahoo Developer Network and sign in with your Yahoo account, or create one if you don’t already have one.

  3. Create a New App in Yahoo Developer

  4. In the Yahoo Developer dashboard, go to My Apps.
  5. Select Create an App, then fill out the required details, such as your app name, description, and permissions.

  6. Set API Permissions

  7. Under API Permissions, select Fantasy Sports to ensure your app can access Fantasy Football data.

  8. Obtain OAuth2 Credentials

  9. Once your app is created, Yahoo will provide a Client ID and Client Secret—protect these as if they were your own username and password.

  10. Configure Redirect URI for Tines

  11. In your app’s settings, set the Redirect URI to: https://<your_tines_instance>.tines.com/oauth2/callback.

  12. Set Up OAuth2 Authentication in Tines

  13. In Tines, create a new OAuth 2.0 credential:
    • Go to CredentialsNew CredentialOAuth 2.0.
    • Client ID/Client Secret: Credentials obtained above.
    • Authorization URL: https://api.login.yahoo.com/oauth2/request_auth
    • Token URL: https://api.login.yahoo.com/oauth2/get_token.
    • Grant Type: 'Authorization Code'.

With these credentials configured, Tines can securely access the Yahoo Fantasy Football API on your behalf, allowing it to perform tasks programmatically without needing repeated re-authentication.

robots

ChatGPT

ChatGPT adds a layer of needed intelligence to our automations (recall I was limited to very poor intuition on my own). With carefully crafted prompts, we can get roster advice, add/drop suggestions, and even have it provide the XML we need to directly interact with Yahoo's API.

Please feel free to adjust these prompts to better suit your needs. I'm not going to pretend to be an expert and efficacy may vary based on your league's settings and scoring.

Role

You are an expert Fantasy Football manager with advanced knowledge of Yahoo Fantasy Football’s API, player statistics, injury reports, and roster optimization.

Roster Optimization Prompt

This prompt helps ChatGPT evaluate your roster for the week’s matchup, recommending lineup adjustments to maximize projected points while considering injuries and bye weeks. The prompt ensures that all starting positions are filled by players expected to play, with six players designated as bench ("BN").

Your task is to evaluate a Fantasy Football roster for this week’s matchup, recommend optimal swaps based on projections, injuries, and bye weeks, and output the updated full roster in XML format. Ensure that: - All starting positions (e.g., QB, WR1, WR2, RB1, RB2, TE, W/R/T, K, DEF) are filled with players expected to play. - Exactly six players are designated as "BN" (bench). - Avoid any duplicate positions, ensuring only one player per position to prevent API errors.

Starting Criteria:

  1. Projected Points Comparison: For each position, if a benched player has a higher projected weekly point total than a starter, recommend a swap.
  2. Injury Status Exclusion: Exclude any player from the starting lineup who has an injury status of "IR," "O," "NFI-R," or "SUS."
  3. Bye Week Exclusion: Ensure no starting players are on a bye week. If a starting player has a bye, replace them with the highest-projected, active bench player at the same position.

XML Output Requirements:

Generate XML for the entire roster, ensuring that each position is filled with one unique player. Here’s the template:

### XML_START ###
<?xml version="1.0"?>
<fantasy_content>
  <roster>
    <coverage_type>week</coverage_type>
    <week><<get_current_week.current_week>></week> 
    <players>
      {ROSTER TEMPLATE}
    </players>
  </roster>
</fantasy_content>
### XML_END ###

Example XML Output: To ensure accuracy, here’s a sample structure of the output format:

### XML_START ###
<?xml version="1.0"?>
<fantasy_content>
  <roster>
    <coverage_type>week</coverage_type>
    <week>10</week>
    <players>
      {ROSTER_EXAMPLE}
    </players>
  </roster>
</fantasy_content>
### XML_END ###
  • Current Week: {CURRENT_WEEK}
  • Current Roster: {ROSTER}

Add/Drop Suggestions Prompt

This prompt guides ChatGPT to review your current roster and identify potential add/drop actions for the week. By analyzing both rostered players and available free agents, it recommends swaps that increase projected points, reduce injury risks, and cover gaps from bye weeks.

Using the above criteria, ChatGPT returns structured suggestions in JSON format, specifying who to add, who to drop, and why each change improves your lineup.

Your task is to evaluate the current Fantasy Football roster for this week’s matchup, considering both players held in the roster and available free agents. Recommend add/drop actions where an available player has better projected points, fewer injury risks, or fills gaps (e.g., due to bye weeks). Provide recommendations in the following structured array:

[{"add_player_name": "{player_name}", "drop_player_name": "{player_name}", "rationale": ""}...]

Evaluation Criteria: 1. Projected Points Comparison: Identify available players with higher projected points than those on the current roster in similar positions. Recommend adding these players and dropping lower-projected, less valuable, or riskier roster players. 2. Injury Status Exclusion: Prioritize adding available players over roster players who are on "IR," "O," "NFI-R," or "SUS" statuses. Exclude these injured players from starting or bench roles. 3. Bye Week Replacement: For any roster player on a bye week, recommend an available player in the same position with the highest projected points. Input Variables: - Current Week: <<get_current_week.current_week>> - Current Roster: <<map_projected_points>> - Available Players: <<update_injury_status>>


Putting It All Together

With our credentials and prompts locked and loaded, it’s time to bring everything together into an automated workflow that manages your fantasy football team. We’ll use Tines to orchestrate the interactions between Yahoo’s API, ChatGPT, and Slack.

Roster Optimization

For this part of the workflow, Tines fetches our current roster, formats and enriches the data, and passes it off to ChatGPT for review. I let ChatGPT apply these changes directly (YOLO), but you can choose to review them first if you prefer.

Now, unfortunately Yahoo's API doesn't give us the full picture - It leaves out certain stats like projected points, which are crucial for making informed decisions. To get around this, we can scrape the Yahoo Fantasy Football website for this information.

Here's the URL of interest: https://football.fantasysports.yahoo.com/f1/{LEAGUE_ID}/{TEAM_ID}

And here's how we grab this information using Python and BeautifulSoup:

from bs4 import BeautifulSoup
import json

def main(input):
    # Parse the HTML content using BeautifulSoup
    soup = BeautifulSoup(input, 'html.parser')

    # Find the table with ID 'statTable0'
    roster_table = soup.find('table', {'id': 'statTable0'})

    # Initialize a list to hold player data
    players = []

    # Check if the table exists
    if roster_table:
        # Iterate over the table rows
        for row in roster_table.find_all('tr')[1:]:  # Skip the header row
            cols = row.find_all('td')
            if len(cols) > 1:
                # Extract the player name using regex
                player_name = cols[1].get_text(separator="\n").lstrip().split("\n")[0]
                player_data = {
                    'player_name': player_name,
                    'proj_points': cols[6].get_text(strip=True),
                    'started': cols[7].get_text(strip=True),
                    'rostered': cols[8].get_text(strip=True)
                }
                players.append(player_data)

        # Convert the list of players to JSON
        return players

    else:
        return None

Here's how a section of this workflow looks in Tines:

tines-optimize-roster

Add/Drop Suggestions

The Add/Drop workflow follows a similar structure and faces similar obstructions. Once again, Yahoo's API doesn't provide projected points for available players. Once again, we won't let that slow us down.

URL of interest: https://football.fantasysports.yahoo.com/f1/{LEAGUE_ID}/players

The below code should look familiar with only slight variations:

from bs4 import BeautifulSoup

def main(input):
    # Parse the page with BeautifulSoup
    soup = BeautifulSoup(input, 'html.parser')

    # Find the first table on the page
    table = soup.find('table')
    if not table:
        return {"error": "No table found on the page"}

    # Use the second header row as keys
    header_rows = table.find_all('thead')[0].find_all('tr')
    if len(header_rows) < 2:
        return {"error": "Insufficient header rows"}

    headers = [th.get_text(strip=True).replace('\ue002', '').lower() for th in header_rows[1].find_all('th') if th]
    headers[1] = 'player_name'  # Rename the second header as 'player_name'

    # Extract rows of data
    rows = []
    for tr in table.find('tbody').find_all('tr'):
        cells = [td.get_text(separator='\n').lstrip().split('\n')[0] for td in tr.find_all('td')]
        if cells:
            # Create a dictionary only with headers that have valid keys
            row_data = {headers[i]: cells[i] for i in range(len(headers)) if headers[i]}
            rows.append(row_data)

    return rows  # Return the list of dictionaries containing row data

And here's how a section of this workflow looks in Tines:

tines-add-drops

Slack Notifications

I've covered how to setup a Slack App using Tines in a previous article, so I won't get too deep into the background here - But here's a high-level overview.

We're going to take the JSON ChatGPT sends back, loop over it to format our Slack blocks, and relay it to a channel (or individual) of our choosing using the https://slack.com/api/chat.postMessage endpoint.

Here's an example of a formatted payload:

[
  {
    "type": "header",
    "text": {
      "type": "plain_text",
      "text": "Add/Drop Suggestions",
      "emoji": true
    }
  },
  {
    "type": "section",
    "fields": [
      {
        "type": "mrkdwn",
        "text": "*Add:* Tyler Lockett"
      },
      {
        "type": "mrkdwn",
        "text": "*Drop:* Khalil Shakir"
      },
      {
        "type": "mrkdwn",
        "text": "*Rationale:* Tyler Lockett is projected for higher points and presents higher upside with no injury risk."
      }
    ]
  },
  {
    "type": "divider"
  },
  {
    "type": "section",
    "fields": [
      {
        "type": "mrkdwn",
        "text": "*Add:* Cade Otton"
      },
      {
        "type": "mrkdwn",
        "text": "*Drop:* Pat Freiermuth"
      },
      {
        "type": "mrkdwn",
        "text": "*Rationale:* Cade Otton has better recent performances and projections, making him a stronger play this week."
      }
    ]
  },
  {
    "type": "divider"
  }
]

And here's the result:

ff-manager-slack

Injury Reports

You probably noticed that we're enriching our data with injury statuses in addition to projected points. As you might've guessed, this information isn't present in any of the responses I've found. You can already guess how we'll overcome this limitation.

URL of interest: https://football.fantasysports.yahoo.com/f1/gamedaycalls

And here's our script:

import re
from bs4 import BeautifulSoup

def main(input):
    soup = BeautifulSoup(input, 'lxml')
    table = soup.find('table', id='gamedayscalltable')
    headers = [heading.text for heading in table.find_all('th')]

    table_rows = [row for row in table.find_all('tr')]

    results = [{headers[index]: cell.text for index, cell in enumerate(row.find_all("td"))} for row in table_rows]
    results = [result for result in results if result]

    for result in results:
      for i, v in result.items():
        for substring in ['\n', ' No new player Notes', ' Player Note', ' New']:
          v = v.replace(substring, '')
        result[i] = v

        try:
            name_pattern = r'[A-Z][^A-Z]*'
            result['Name'] = ' '.join(re.findall(name_pattern, result['Name'])[:2]).strip()
            result['Name'] = re.sub(r'\s+', ' ', result['Name'])
            result['Team'] = result.pop('Depth Chart')
        except Exception as e:
            print(e)

    return results

We fetch this data once a day and store it in a Tines Resource for later use. This way, we can enrich our data with injury statuses without needing to re-scrape with every run.

Wrapping Up

And there you have it: an automated fantasy football manager that works tirelessly so you don’t have to. By leveraging Tines, ChatGPT, and Yahoo’s Fantasy Football API, you now have an assistant that handles the nitty-gritty of lineup and roster management while you sit back and… well, maybe get blamed by your league-mates for your suspiciously well-managed team.

For those ready to take the plunge, you can find the ready-to-import Tines story and the full set of Python scripts in the companion GitHub repository. Here’s to a hassle-free season and maybe—just maybe—a little less guessing before game day.