Build a League of Legends Fantasy Esports App with Real-Time Data

How to build a fantasy LoL platform with player projections, live scoring, and optimal lineup suggestions using the Cito API.

Author:
Cito Team

Introduction: Fantasy LoL Is Massive

Fantasy esports is one of the fastest-growing segments in gaming. Millions of LoL fans already pick their favorite pros for weekly fantasy matchups, and the demand for better tools keeps growing.

In this guide, you'll learn how to build a fantasy League of Legends platform with:

  • Player projections based on historical stats
  • Live scoring that updates during matches
  • Optimal lineup suggestions
  • Salary cap and draft mechanics

Whether you're building a full fantasy platform or adding fantasy features to your Discord server, this guide covers the API integration you need.

The Fantasy LoL Data Model

Every fantasy platform needs these core data points:

Player Fantasy Stats

The stats that generate fantasy points:

  • Kills: +3 points
  • Deaths: -1 point
  • Assists: +2 points
  • CS (per 100): +1 point
  • Triple kills: +2 bonus
  • Quadra kills: +5 bonus
  • Penta kills: +10 bonus
  • Win: +2 points

What You Need From the API

  • Player career stats (for projections)
  • Live game stats (for scoring)
  • Upcoming schedule (for lineup deadlines)
  • Team matchups (for strength-of-schedule)

The Cito API has dedicated fantasy endpoints for exactly this.

Getting Started

bash
mkdir lol-fantasycd lol-fantasynpm init -ynpm install axios dotenv

JavaScript
// api.jsconst axios = require('axios');require('dotenv').config(); const api = axios.create({  baseURL: 'https://api.citoapi.com/api/v1/lol',  headers: { 'x-api-key': process.env.CITO_API_KEY },}); module.exports = api;

Fetching Fantasy Projections

The Cito API provides pre-calculated fantasy projections:

JavaScript
const api = require('./api'); async function getProjections() {  const { data } = await api.get('/fantasy/projections/players');  return data;} async function showTopProjections() {  const projections = await getProjections();   console.log('Top Fantasy Projections This Week:\n');  console.log('Player          | Team    | Proj  | Salary');  console.log('----------------|---------|-------|-------');   projections.data    .sort((a, b) => b.projectedPoints - a.projectedPoints)    .slice(0, 20)    .forEach(p => {      console.log(        `${p.name.padEnd(16)}| ${p.team.padEnd(8)}| ${p.projectedPoints.toFixed(1).padStart(5)} | $${p.salary}`      );    });} showTopProjections();

Building the Scoring Engine

Calculate fantasy points from live game data:

JavaScript
const SCORING = {  kills: 3,  deaths: -1,  assists: 2,  cs100: 1,       // per 100 CS  tripleKill: 2,  quadraKill: 5,  pentaKill: 10,  win: 2,}; function calculatePoints(gameStats) {  let points = 0;   points += (gameStats.kills || 0) * SCORING.kills;  points += (gameStats.deaths || 0) * SCORING.deaths;  points += (gameStats.assists || 0) * SCORING.assists;  points += Math.floor((gameStats.cs || 0) / 100) * SCORING.cs100;  points += (gameStats.tripleKills || 0) * SCORING.tripleKill;  points += (gameStats.quadraKills || 0) * SCORING.quadraKill;  points += (gameStats.pentaKills || 0) * SCORING.pentaKill;  if (gameStats.win) points += SCORING.win;   return points;} // Example: Faker's game statsconst fakerGame = {  kills: 5, deaths: 1, assists: 8,  cs: 312, tripleKills: 1, quadraKills: 0,  pentaKills: 0, win: true}; console.log(`Fantasy points: ${calculatePoints(fakerGame)}`);// kills(15) + deaths(-1) + assists(16) + cs(3) + triple(2) + win(2) = 37

Live Scoring During Matches

Update fantasy scores in real-time while games are being played:

JavaScript
async function getLiveFantasyScores(rosterPlayerIds) {  const { data } = await api.get('/live');   if (!data.data || data.data.length === 0) {    return { live: false, scores: [] };  }   const scores = [];   for (const match of data.data) {    // Get detailed game stats    const gameData = await api.get(`/live/${match.gameId}/stats`);     for (const player of gameData.data.players) {      if (rosterPlayerIds.includes(player.id)) {        scores.push({          playerId: player.id,          name: player.name,          team: player.team,          currentPoints: calculatePoints(player.stats),          stats: player.stats,          gameStatus: match.status,        });      }    }  }   return { live: true, scores };} // Check scores for your rosterconst myRoster = ['faker', 'chovy', 'zeus', 'keria', 'peyz'];const scores = await getLiveFantasyScores(myRoster); if (scores.live) {  console.log('LIVE Fantasy Scores:\n');  scores.scores.forEach(s => {    console.log(`${s.name} (${s.team}): ${s.currentPoints} pts`);    console.log(`  K/D/A: ${s.stats.kills}/${s.stats.deaths}/${s.stats.assists}\n`);  });}

Optimal Lineup Generator

Find the best lineup under a salary cap:

JavaScript
async function getOptimalLineup() {  const { data } = await api.get('/fantasy/optimal');  return data;} async function showOptimalLineup() {  const optimal = await getOptimalLineup();   console.log('Optimal Fantasy Lineup:\n');  console.log(`Total Projected: ${optimal.data.totalProjected.toFixed(1)} pts`);  console.log(`Salary Used: $${optimal.data.salaryUsed} / $${optimal.data.salaryCap}\n`);   optimal.data.players.forEach(p => {    console.log(`[${p.role}] ${p.name} (${p.team}) - ${p.projected.toFixed(1)} pts - $${p.salary}`);  });} showOptimalLineup();

Custom Lineup Optimizer

If you want to build your own optimizer with constraints:

JavaScript
function optimizeLineup(players, salaryCap = 50000) {  const roles = ['TOP', 'JNG', 'MID', 'ADC', 'SUP'];  const byRole = {};   // Group players by role  roles.forEach(role => {    byRole[role] = players      .filter(p => p.role === role)      .sort((a, b) => (b.projectedPoints / b.salary) - (a.projectedPoints / a.salary));  });   // Greedy approach: pick best value per role  let lineup = [];  let remainingSalary = salaryCap;   roles.forEach(role => {    const affordable = byRole[role].filter(p => p.salary <= remainingSalary);    if (affordable.length > 0) {      lineup.push(affordable[0]);      remainingSalary -= affordable[0].salary;    }  });   return {    players: lineup,    totalProjected: lineup.reduce((sum, p) => sum + p.projectedPoints, 0),    salaryUsed: salaryCap - remainingSalary,  };}

Fantasy Value Rankings

Find undervalued players — high projected points relative to salary:

JavaScript
async function getValueRankings() {  const { data } = await api.get('/fantasy/value');  return data;} async function showSleepers() {  const value = await getValueRankings();   console.log('Top Fantasy Sleepers (Best Value):\n');  value.data.slice(0, 10).forEach((p, i) => {    console.log(`${i + 1}. ${p.name} (${p.team}) - ${p.projectedPoints.toFixed(1)} pts @ $${p.salary}`);    console.log(`   Value Score: ${p.valueScore.toFixed(2)} | Ownership: ${p.ownership}%\n`);  });} showSleepers();

Player Matchup Analysis

Factor in opponent strength for better projections:

JavaScript
async function analyzeMatchup(playerId) {  const [stats, schedule] = await Promise.all([    api.get(`/players/${playerId}/stats`),    api.get('/schedule/week'),  ]);   const player = stats.data.data;   // Find this player's upcoming match  const nextMatch = schedule.data.data.find(m =>    m.team1 === player.team || m.team2 === player.team  );   if (!nextMatch) return null;   const opponent = nextMatch.team1 === player.team    ? nextMatch.team2 : nextMatch.team1;   // Get opponent's defensive stats  const opponentStats = await api.get(`/teams/${opponent}/stats`);   return {    player: player.name,    opponent,    playerKDA: player.kda,    opponentGamesAllowed: opponentStats.data.data.avgKillsAllowed,    projection: player.projectedPoints,    matchupBoost: opponentStats.data.data.avgKillsAllowed > 15 ? 'GOOD' : 'TOUGH',  };}

Putting It All Together

A complete fantasy dashboard class:

JavaScript
class FantasyDashboard {  constructor(api) {    this.api = api;  }   async getWeeklyOverview() {    const [projections, optimal, value, schedule] = await Promise.all([      this.api.get('/fantasy/projections/players'),      this.api.get('/fantasy/optimal'),      this.api.get('/fantasy/value'),      this.api.get('/schedule/week'),    ]);     return {      topProjections: projections.data.data.slice(0, 20),      optimalLineup: optimal.data,      sleepers: value.data.data.slice(0, 10),      matchesThisWeek: schedule.data.data.length,    };  }   async getPlayerCard(playerId) {    const [stats, fantasy, champions] = await Promise.all([      this.api.get(`/players/${playerId}/stats`),      this.api.get(`/fantasy/stats/players/${playerId}`),      this.api.get(`/players/${playerId}/champions`),    ]);     return {      stats: stats.data,      fantasy: fantasy.data,      topChampions: champions.data.data?.slice(0, 5),    };  }} // Usageconst dashboard = new FantasyDashboard(api);const overview = await dashboard.getWeeklyOverview();console.log(`${overview.matchesThisWeek} matches this week`);console.log(`Best pick: ${overview.topProjections[0].name}`);

Fantasy for Discord

Add fantasy features to your Discord bot:

JavaScript
// !fantasy top — Show top projected players// !fantasy lineup — Show optimal lineup// !fantasy sleepers — Show undervalued players// !fantasy player <name> — Show player fantasy card

Check out our Discord bot tutorial for the full bot setup, then add these fantasy commands on top.

Get Started

The Cito API's fantasy endpoints make it straightforward to build a fantasy LoL platform:

  • Sign up for free — 500 calls/month to prototype
  • Test the fantasy endpoints in the dashboard sandbox
  • Build your scoring engine using the code above
  • Deploy and share with your league
  • The fantasy endpoints are available on all plans, including the free tier.

    Get your free API key and start building your fantasy LoL platform today.

    ---

    Related reading:

    Start building with esports data today

    Free tier available. Same API shape across Call of Duty, Fortnite, LoL, Dota 2, and more.