0.0
The project is in a healthy, maintained state
A comprehensive Ruby gem for interacting with Sleeper's fantasy football API. Features include league management, user profiles, draft data, matchups, transactions, player data with caching, and comprehensive error handling. Built with performance and reliability in mind for production applications.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 13.0
~> 3.13
~> 1.80
~> 0.21
~> 3.14

Runtime

~> 0.21
 Project Readme

SleeperApi

Gem Version

A comprehensive Ruby gem for interacting with Sleeper's fantasy football API. Built with performance, reliability, and developer experience in mind.

Features

  • Complete API Coverage - Users, leagues, drafts, players, matchups, transactions

  • Performance Optimized - Smart caching, connection pooling, rate limiting

  • Robust Error Handling - Automatic retries, timeout management, detailed error messages

  • Well Tested - 90%+ test coverage with RSpec

  • Highly Configurable - Custom timeouts, retries, logging

  • Production Ready - Type signatures, CI/CD, code quality tools

Installation

Add this line to your application's Gemfile:

gem 'sleeper_api'

Quick Start

require 'sleeper_api'

# Basic usage with default configuration
client = SleeperApi.client
league = client.league("123456789012345678")

# Access league information
puts league.name          # "My Fantasy League"
puts league.total_rosters # 12
puts league.status        # "in_season"

# Get rosters
rosters = league.rosters
rosters.each do |roster|
  puts "#{roster[:owner_display_name]}: #{roster[:wins]}-#{roster[:losses]}"
end

Configuration

Customize the gem's behavior:

SleeperApi.configure do |config|
  config.timeout = 45    # Request timeout in seconds (1-60, fractions allowed)
  config.retries = 5     # Number of retries on failure (0-5)
  config.logger = Logger.new(STDOUT)  # Custom logger
end

# Configuration is applied to all subsequent client instances
client = SleeperApi.client

API Coverage

Users

Get user information and their leagues/drafts:

# Find a user by username
user = SleeperApi.client.user('johndoe')

# Basic profile
user.username                  # => "johndoe"
user.display_name              # => "John Doe"
user.avatar_url                # => "https://sleepercdn.com/avatars/abc123"
user.avatar_url(thumb: true)   # => "https://sleepercdn.com/avatars/thumbs/abc123"
user.is_bot                    # => false

# All leagues for current season
leagues = user.leagues
leagues.each do |league|
  status = league[:status] == "finished" ? "🏆" : league[:status]
  puts "#{status} #{league[:name]} (#{league[:total_rosters]} teams)"
end

# Specific season
old_leagues = user.leagues(2023)
championships = old_leagues.select { |l| l[:status] == "finished" }

# Another sport (default "nfl"). An unknown sport answers [] rather than an error.
basketball = user.leagues(2026, sport: "nba")

# Get all my rosters across all leagues
rosters = user.rosters
rosters.each do |roster|
  puts "#{roster[:league_name]}: #{roster[:team_name]} (#{roster[:wins]}-#{roster[:losses]})"
end

# Best performing team this season
best_roster = user.rosters.max_by { |r| r[:wins].to_i - r[:losses].to_i }
puts "Top team: #{best_roster[:team_name]} (#{best_roster[:wins]}-#{best_roster[:losses]})"

# All drafts with enhanced info
drafts = user.drafts(2024)
drafts.each do |draft|
  status = draft[:status] == "complete" ? "✅" : draft[:status]
  puts "#{status} #{draft[:league_name]} - #{draft[:type]} (#{draft[:total_teams]} teams)"
end

# Live drafts only
live_drafts = user.drafts.select { |d| d[:status] == "live" }

# Season summary
summary = user.summary
puts "#{summary[:total_leagues]} leagues this season"
puts "Winning record in #{summary[:winning_record].length} leagues"
puts "Overall: #{summary[:total_wins]}-#{summary[:total_losses]}"

Leagues

Access league data, rosters, matchups, and transactions:

league = SleeperApi.client.league("123456")

# Basic league info
league.name                    # => "My Fantasy League"
league.season                  # => "2024"
league.total_rosters           # => 12
league.status                  # => "regular"
league.avatar_url              # => "https://sleepercdn.com/avatars/abc123"
league.avatar_url(thumb: true) # => "https://sleepercdn.com/avatars/thumbs/abc123"

# Formatted rosters (with team names and records)
rosters = league.rosters
roster = rosters.first
roster[:owner_display_name]    # => "John Doe"
roster[:team_name]             # => "Gridiron Gang"
roster[:wins]                  # => 7
roster[:losses]                # => 2
roster[:remaining_faab]        # => 45
roster[:starters]              # => ["player_id_1", "player_id_2", ...]
roster[:bench]                 # => ["player_id_4", "player_id_5", ...]

# My roster only
my_roster = league.rosters(user_id: my_user_id)

# Week-specific matchups
matchups = league.matchups_by_week(week: 5)
matchup = matchups.first
team = matchup[:rosters].first
team[:points]                  # => 142.5
team[:starter_points]          # => [{ "player_id" => 18.2 }, ...]

# Transactions
transactions = league.transactions(week: 5)
tx = transactions.first
tx[:type]                      # => "tr"
tx[:adds]                      # => [{ player_id: "123", roster_id: 1 }]
tx[:drops]                     # => [{ player_id: "456", roster_id: 1 }]
tx[:waiver_bid]                # => 12

# Users
users = league.users
user = users.find { |u| u[:commissioner] }
user[:display_name]            # => "Commish McGee"
user[:team_name]               # => "Champions"

# Playoffs
playoff_matchups = league.playoff_bracket
matchup = playoff_matchups.first
matchup[:team1_owner]          # => "Team Alpha"
matchup[:winner_owner]         # => "Team Alpha"

# Tourlet Bowl / Losers Bracket
toilet_bowl_matchups = league.toilet_bowl
matchup = toilet_bowl_matchups.first
matchup[:team1_owner]          # => "Team Beta"
matchup[:winner_owner]         # => "Team Beta"

# Traded picks (only picks that moved), with the same filters as Draft#traded_picks
picks = league.traded_picks(current_owner_id: 8)
picks.first                    # => { season: "2027", round: 1, roster_id: 1, previous_owner_id: 1, owner_id: 8 }

# Drafts, as hashes; open one with SleeperApi.client.draft(id)
league.drafts.map { |d| d[:status] } # => ["complete"]

Drafts

Access draft information, picks, and traded picks:

# Get a specific draft
draft = SleeperApi.client.draft("draft_123456")

# Basic draft info
draft.type                     # => "snake" or "auction"
draft.status                   # => "pre_draft", "live", "complete"
draft.season                   # => "2024"
draft.start_time              # => "2024-08-27T20:00:00.000Z"
draft.league.name             # => "My Fantasy League"

# All picks (180 picks for 15-round, 12-team draft)
picks = draft.picks
puts "#{picks.length} total picks made"

# Filter picks
round_1 = draft.picks(round: 1)
team_3_picks = draft.picks(roster_id: 3)

round_1.each do |pick|
  player = SleeperApi.client.get_player_by_id(pick[:player_id])
  puts "#{pick[:pick_no]}. #{player['full_name']} (#{player['position']}) - Team #{pick[:roster_id]}"
end

# Who drafted a specific player?
picker = draft.picked_by("4036") # User 1

# Traded picks
trades = draft.traded_picks
team_3_trades = draft.traded_picks(original_owner_id: 3)

team_3_trades.each do |trade|
  puts "Team 3 traded away #{trade[:round]}.#{trade[:draft_slot]} to Team #{trade[:owner_id]}"
end

# Live draft - next pick info
if draft.status == "live"
  next_pick = draft.next_pick
  puts "Next up: Round #{next_pick[:round]}, Pick #{next_pick[:pick_no]} (slot #{next_pick[:draft_slot]})"
end

# Quick summary with top picks
summary = draft.summary
puts "#{summary[:total_picks]} picks in #{summary[:total_rounds]} rounds"
summary[:top_picks].each do |pick|
  puts "#{pick[:pick_no]}. #{pick[:player_name]} (#{pick[:position]}, #{pick[:team]}) - #{pick[:roster_id]}"
end

# Grouped views
rounds = draft.rounds # { 1 => [picks], 2 => [picks], ... }
team_3_rounds = draft.team_picks[3] # Team 3's picks by round

Players

Access player data with automatic caching:

# Get all players (cached for 24 hours)
players = client.get_players

# Find specific player (reads the cached catalog above)
player = client.get_player_by_id("1234")
puts "#{player['first_name']} #{player['last_name']} - #{player['position']}"

# One player, live and uncached: ~1.2 KB instead of the whole catalog.
# Use it for a fresher injury_status or depth_chart_order than the catalog has.
fresh = client.player("1234")
puts "#{fresh['injury_status']} (#{fresh['injury_body_part']})"

# One player's recent news, newest first (at most 10, from api.sleeper.com)
client.player_news("1234").each do |item|
  puts "#{item['source']}: #{item.dig('metadata', 'title')}"
end

# Get trending players
trending_adds = client.trending_players(type: "add", limit: 10)
trending_drops = client.trending_players(type: "drop", limit: 10)

Player Helper

All resource classes (League, User, Draft) include a player_details helper method that returns formatted Sleeper player data. This eliminates repetitive player lookups and ensures consistent symbolization.

# In any resource (League, User, Draft)
league = SleeperApi.client.league("123456")

# Get player details with one method call
player = league.player_details("4036") # Christian McCaffrey
if player
  puts "#{player[:full_name]} (#{player[:position]}, #{player[:team]})"
  puts "Bye week: #{player[:bye]} | Injury status: #{player[:injury_status]}"
end

# Works in matchups
matchups = league.matchups_by_week(week: 5)
matchup = matchups.first
team = matchup[:rosters].first

# Get starter names and positions
team[:starters].each do |player_id|
  player = league.player_details(player_id)
  if player
    puts "#{player[:full_name]} (#{player[:position]}) - #{team[:starter_points].find { |p| p.keys.first == player_id }&.values&.first || 0} pts"
  end
end

# Works in rosters
roster = league.rosters.first
roster[:starters].each_with_index do |player_id, index|
  player = league.player_details(player_id)
  position = player ? player[:position] : "?"
  points = roster[:starter_points]&.dig(index, player_id) || 0
  puts "#{position}: #{player ? player[:full_name] : 'Unknown'} (#{points} pts)"
end

# Works in drafts
draft = SleeperApi.client.draft("draft_123")
round_1 = draft.picks(round: 1)

round_1.each do |pick|
  player = draft.player_details(pick[:player_id])
  if player
    puts "#{pick[:pick_no]}. #{player[:full_name]} (#{player[:position]}, #{player[:team]})"
  end
end

# Works across user's leagues
user = SleeperApi.client.user('johndoe')
rosters = user.rosters

rosters.each do |roster|
  puts "\n#{roster[:team_name]} starters:"
  roster[:starters].each do |player_id|
    player = roster.player_details(player_id) # Note: uses the League instance under the hood
    puts "  • #{player ? player[:full_name] : 'Unknown'} (#{player[:position] if player})"
  end
end

Additional Endpoints

# Get NFL state
state = client.get_nfl_state
puts "Current week: #{state['week']}"

# Per-player weekly stats and projections (undocumented endpoints).
# Keyed by player id, plus TEAM_XXX keys for team-level rows.
week_stats = client.stats(2025, week: 1)
puts week_stats["4046"]["pts_ppr"]   # => 21.4
puts week_stats["4046"]["off_snp"]   # raw counting stats too

projected = client.projections(2026, week: 1)

# Omit the week for season totals — a different resource, not a default.
season_stats = client.stats(2025)

# Nothing here 404s: an unplayed week, a week out of range and an unknown
# season type all answer 200 with {}. And a projections call for a season
# Sleeper has not projected still returns thousands of entries carrying only
# an `adp_dd_ppr` sentinel — a row count is not evidence of a projection, so
# filter on the field you actually want.
real = projected.parsed_response.select { |_id, row| row.key?("pts_ppr") }

# Get playoff brackets
winners_bracket = client.get_playoff_bracket("league_id")
losers_bracket = client.get_toilet_bowl("league_id")

# Get league drafts
league_drafts = client.get_league_drafts("league_id")

# Get traded picks
traded_picks = client.get_league_traded_picks("league_id")

Error Handling

The gem provides comprehensive error handling:

begin
  league = client.league("invalid_id")
  # Process league data
rescue SleeperApi::Error => e
  puts "API Error: #{e.message}"
rescue ArgumentError => e
  puts "Invalid parameter: #{e.message}"
end

Error Types

  • SleeperApi::Error - API-related errors (404, 500, timeouts, an unreachable host)

  • SleeperApi::UserNotFound - a subclass of SleeperApi::Error: no Sleeper user has that identifier. Permanent, so rescue it before SleeperApi::Error if you retry the rest

  • SleeperApi::PlayerNotFound - a subclass of SleeperApi::Error: client.player was given an id Sleeper does not have. Permanent, like UserNotFound

  • ArgumentError - Invalid parameters passed to methods

Performance Considerations

Caching

  • Player data is cached in memory for 24 hours, on the client instance — a new client downloads it again

  • League/User/Draft data is cached per instance

Rate Limiting

  • Be mindful of Sleeper's rate limits: stay under 1000 API calls per minute

  • The gem automatically handles timeouts and retries

  • Consider caching frequently accessed data in your application

Memory Usage

  • The all-players payload is several megabytes and held in memory; never fan it out

  • League rosters and matchups are fetched lazily

  • Use no_data: true when initializing leagues if you don't need immediate data

Testing

The gem includes comprehensive tests:

# Run all tests
bundle exec rspec
# Run with coverage
bundle exec rspec --coverage
# Run specific test file
bundle exec rspec spec/sleeper_api/client_spec.rb

Setup

git clone https://github.com/eruity1/sleeper_api.git
cd sleeper_api
bundle install

Code Quality

# Run all checks (tests + linting)
bundle exec rake ci

# Run RuboCop
bundle exec rubocop

# Auto-fix RuboCop issues
bundle exec rubocop -a

Contributing

  1. Fork the repository

  2. Create a feature branch (git checkout -b feature/amazing-feature)

  3. Commit your changes (git commit -m 'Add amazing feature')

  4. Push to the branch (git push origin feature/amazing-feature)

  5. Open a Pull Request

Development Dependencies

  • rspec - Testing framework

  • rubocop - Code style and quality

  • simplecov - Test coverage

  • webmock - HTTP request mocking

Requirements

  • Ruby 2.6.0 or higher

  • No external dependencies (HTTParty is bundled)

License

The gem is available as open source under the terms of the MIT License.

Changelog

See CHANGELOG.md for version history and updates.