Back to Blog
GuideFebruary 25, 202512 min read

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.

Share:

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

mkdir lol-fantasy

cd lol-fantasy

npm init -y

npm install axios dotenv

// api.js

const axios = require('axios');

require('dotenv').config();

const api = axios.create({

baseURL: 'https://api.citoapi.com/api/v1/lol',

headers: { 'Authorization': Bearer ${process.env.CITO_API_KEY} },

});

module.exports = api;

Fetching Fantasy Projections

The Cito API provides pre-calculated fantasy projections:

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:

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 stats

const 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:

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 roster

const 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:

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:

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:

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:

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:

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),

};

}

}

// Usage

const 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:

// !fantasy top — Show top projected players

// !fantasy lineup — Show optimal lineup

// !fantasy sleepers — Show undervalued players

// !fantasy player — 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:

    Ready to Build?

    Get your API key and start building with esports data in minutes.