Esports API: Free Data Sources for Dota 2, CS2, League of Legends and Valorant
Free esports data sources, tested: OpenDota's endpoints answered with no key on 2 November 2026.
Last updated:
What Is an Esports API?
An esports API serves match results, teams, players, tournaments and live game state for competitive video games. This page covers that data side; for betting prices, see our esports odds API guide. The one source that serves full match data with no key and no account is OpenDota, a free, open-source Dota 2 API at https://api.opendota.com/api, run by volunteers rather than by Valve. Riot Games, Valve (the Steam Web API) and Faceit run official APIs, and each one gates most data behind a key tied to an account. Liquipedia's wiki API needs no key, but its terms ask for a named User-Agent.
We called every OpenDota endpoint on this page on 2 November 2026, at about 10:35 pm US Eastern. For the keyed APIs we read the docs and quote them, each fact marked "from the docs, checked November 2026". We did not sign up for keys, so no sample here calls them.
Base URLs and Access
| Host | Games | Access | Our call, 2 November 2026 |
|---|---|---|---|
https://api.opendota.com/api | Dota 2 | No key; a paid key raises the limits | 200 |
https://api.steampowered.com | Steam games, Dota 2 and CS2 included | Key for most methods | 200 on two methods, 403 on Dota 2 matches |
https://liquipedia.net/{wiki}/api.php | One wiki per esport | No key; a User-Agent with contact details | 200 |
https://na1.api.riotgames.com and other regions | League of Legends, TFT, LoR, Valorant | Riot account and key | 401 with no key |
https://open.faceit.com/data/v4 | Faceit matches and players | Faceit account and app key | 403 challenge page with no key |
hltv.org | CS2 | No API | 403 |
What Each Source Covers
OpenDota covers Dota 2 only, in depth. /leagues listed 10,412 leagues, each tagged with a tier (2,485 "professional", 214 "premium"). /proPlayers listed 5,738 accounts, and Team Spirit's match history went back to May 2019.
CS2 has no equivalent. Steam serves the live player count and patch news without a key, Faceit's Data API needs one, Liquipedia holds results as wiki pages, and HLTV offers no API. For League of Legends and Valorant, Riot's API serves accounts, matches and ranked ladders; its API list at developer.riotgames.com/apis shows no endpoint for pro league schedules (checked November 2026).
ESPN's free feed carries no esports: sports/esports/lol/scoreboard returned 400, "sport 'esports' and/or league 'lol' are invalid". The ESPN API guide covers the leagues it does carry, and our free sports APIs page lists more free feeds. None of the sources here serves betting odds.
OpenDota Key Endpoints
Paths sit under https://api.opendota.com/api, and the OpenAPI spec at that root lists 55. Every one below returned 200 on 2 November 2026.
| Endpoint | What it returns | Example |
|---|---|---|
/proMatches | The latest 100 pro matches | Newest: 9073699287, EPL Masters 2026 |
/matches/{match_id} | One match: draft, players, gold graph | 250 KB for 9073699287 |
/leagues | Every league with its tier | 10,412 leagues |
/leagues/{league_id}/matches | Matches in one league | 19944: 483 matches since 20 July |
/teams | 1,000 teams per page, by rating | Team Spirit first, 1591.77 |
/teams/{team_id} | One team's rating and record | 7119388: 974 wins, 651 losses |
/teams/{team_id}/matches | A team's match history | 1,625 matches |
/teams/{team_id}/players | Everyone who played for the team | 33 players, 5 current |
/proPlayers | Pro accounts with team and name | 5,738 accounts |
/players/{account_id}/recentMatches | A player's last 20 matches | kills, hero_id, gold_per_min |
/heroes, /heroStats | Hero ids and names; pro picks, wins and bans | 127 heroes |
/live | Top games in progress | 100 games |
/explorer?sql= | Your SQL query on OpenDota's database | See the cURL sample |
At the time of our call, 98 of the 100 games in /live had no league_id, and the 2 league games had empty team names.
OpenDota Match Endpoint
https://api.opendota.com/api/matches/{match_id}
This endpoint returns everything OpenDota holds on one game. Our test match, 9073699287, was CyberHero against GamerLegion in EPL Masters 2026 on 2 November. GamerLegion, on the Dire side, won 32 kills to 12 in 38 minutes, and the response held 10 players and 24 draft entries.
| Field | Type | What it holds |
|---|---|---|
match_id | integer | Valve's match id, 9073699287 |
start_time, duration | integer | Unix start time; length in seconds, 2288 |
radiant_win | boolean | false: Dire won |
radiant_score, dire_score | integer | Kills per side, 12 and 32 |
league | object | leagueid, name, tier |
radiant_team, dire_team | object | team_id, name, tag, logo_url |
series_id | integer | Groups the games of one series, 1163541 |
picks_bans | array | The draft: is_pick, hero_id, team, order |
radiant_gold_adv | array | Radiant's gold lead each minute, 39 values here |
players | array | name, hero_id, kills, deaths, assists, gold_per_min, net_worth |
od_data.has_parsed | boolean | true when OpenDota parsed the replay |
Parsed matches add teamfights, objectives and chat. Heroes come as numbers, so join hero_id to /heroes. In players, name holds the pro name (Yuma went 15/2/7 on Ursa) and personaname holds the Steam name.
Paging, Filters and SQL
| Parameter | What it did | Example |
|---|---|---|
less_than_match_id | The next 100 pro matches, older than that id | /proMatches?less_than_match_id=9061220091 |
page | The next 1,000 teams; pages start at 0 | /teams?page=1 |
limit, project | 3 rows, with the projected fields added | /players/321580662/matches?limit=3&project=hero_id&project=kills |
sql | Runs a URL-encoded query | /explorer?sql=SELECT... |
The player matches endpoint also filters on hero_id, lobby_type, date (days back) and win, per the spec. Skip /search?q= for pros: it matches Steam names, and our search for "Yatoro" returned 50 accounts in 2.8 seconds, none of them Team Spirit's player. Filter /proPlayers on name and team_id instead.
Code Examples
Each sample runs as written with no key. We ran all three on 2 November 2026.
Latest pro results in Python
import requests
matches = requests.get('https://api.opendota.com/api/proMatches', timeout=10).json()
for m in matches[:5]:
radiant = m['radiant_name'] or 'Radiant'
dire = m['dire_name'] or 'Dire'
winner = radiant if m['radiant_win'] else dire
print(m['match_id'], m['league_name'].strip(), '|', radiant, 'vs', dire, '| won:', winner)
The first line it printed: "9073699287 EPL Masters 2026 | CyberHero vs GamerLegion | won: GamerLegion". Some rows have null team names, which is why the sample falls back to the side.
A team and a SQL query with cURL
# One team's rating and record (Team Spirit)
curl 'https://api.opendota.com/api/teams/7119388'
# Your own SQL: the last three games of league 19944
curl -G 'https://api.opendota.com/api/explorer' \
--data-urlencode 'sql=SELECT match_id, radiant_win, duration FROM matches WHERE leagueid = 19944 ORDER BY match_id DESC LIMIT 3'
One match's player lines in JavaScript
// Node 18 or later
const base = 'https://api.opendota.com/api';
Promise.all([
fetch(`${base}/heroes`).then(r => r.json()),
fetch(`${base}/matches/9073699287`).then(r => r.json()),
]).then(([heroes, match]) => {
const hero = Object.fromEntries(heroes.map(h => [h.id, h.localized_name]));
console.log(match.league.name.trim(), '|', match.radiant_team.name, 'vs', match.dire_team.name);
for (const p of match.players) {
const side = p.isRadiant ? 'Radiant' : 'Dire';
console.log(side, p.name || p.personaname, hero[p.hero_id], `${p.kills}/${p.deaths}/${p.assists}`);
}
});
It printed the league and both teams, then ten player lines, from "Radiant sematary Windranger 2/8/5" to "Dire Bignum Hoodwink 6/3/14".
Riot, Steam and Faceit APIs
Riot Games API (from the docs, checked November 2026)
Signing in at developer.riotgames.com with a Riot account creates a development key, and those keys "deactivate every 24 hours". A personal key, meant for "just the developer or a small private community", allows "20 requests every 1 second" and "100 requests every 2 minutes" per region. Production keys start at 500 requests every 10 seconds. The docs give no separate number for the development key. Valorant is stricter: "personal keys are not offered for VALORANT", and Valorant apps need Riot Sign On, which only production keys get. The reference takes the key as a query or header parameter, and our call with neither returned 401.
Steam Web API
Valve's developer page says "All use of the Steam Web API requires the use of an API Key", issued at steamcommunity.com/dev/apikey after a Steam login. The partner docs say some methods "do not require authorization", and these two answered without a key:
| Endpoint | What it returns | Example |
|---|---|---|
/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=730 | Players in a game right now | CS2: player_count 399,360 |
/ISteamNews/GetNewsForApp/v2/?appid=570&count=3 | News and patch notes | Dota 2: "7.41f Gameplay Patch" |
Dota 2 match history and CS2 player stats need the key. A key goes in key= or the x-webapi-key header (from the docs, checked November 2026).
Faceit Data API (from the docs, checked November 2026)
The Data API at https://open.faceit.com/data/v4 serves "all publicly available information on faceit.com": championships, hubs, matches with /matches/{match_id}/stats, players, rankings and teams, by game_id. You create an app in Faceit's developer portal, add a server-side or client-side key, and send it as a Bearer token. The reference lists a 429 "Too many requests" response but states no number.
Liquipedia API and HLTV
Liquipedia runs one MediaWiki wiki per game, each with an API at https://liquipedia.net/{wiki}/api.php. It needs no key, and its API terms say generic agents such as "Python-requests" are "likely to be blocked". We sent User-Agent: sportsapis.dev-guide/1.0 (https://sportsapis.dev; info@sportsapis.dev) and Accept-Encoding: gzip, spaced 3 seconds apart.
| Endpoint | What it returns | Example |
|---|---|---|
dota2/api.php?action=query&list=search&srsearch=Team%20Spirit&srlimit=5&format=json | Full-text search | 1,974 hits, "Team Spirit" first |
counterstrike/api.php?action=opensearch&search=Team%20Vitality&limit=5&format=json | Title matches with URLs | 4 pages |
dota2/api.php?action=parse&page=Team_Spirit&prop=wikitext&format=json | A page's source | 53,492 characters |
Wikitext is raw markup, so you parse templates such as {{Infobox team}} yourself. Its teamid=7119388 is OpenDota's id for Team Spirit, a ready join between the two.
HLTV.org, a site with "CS2 news, CS2 match information", publishes no API. Its terms, effective 27 January 2025, forbid users to "conduct, facilitate or organize data mining or web scraping in relation to the Website". hltv.org answered our script and our fetch tool with 403, so we read the Internet Archive copy of 3 October 2026. This page documents no HLTV endpoint.
Errors You Will See
OpenDota 404 and 400
/matches/1, /matches/abc and an unknown path returned 404 with {"error":"Not Found"}. /players/abc returned 400, "invalid account id". /explorer with no sql returned 400, "sql is not a string", and a bad column returned 400 with the database error.
OpenDota 200 with an empty body
/teams/999999999 and /leagues/99999999 returned 200 and zero bytes, so response.json() throws. Check the length before you parse.
Keyed and blocked calls
Steam's IDOTA2Match_570/GetMatchHistory returned 403, "Please verify your key= parameter", and ISteamApps/GetAppList/v2/ returned 404, "Method 'GetAppList' not found". Riot returned 401, "apikey or authorization header is empty". Faceit's API answered a no-key call with a 403 challenge page. Liquipedia reports errors inside a 200: a search with no srsearch returned "code":"missingparam".
Limitations and Terms
OpenDota runs on a best-effort basis
OpenDota's FAQ says: "The service is provided on a best-effort basis. Since it relies on Valve infrastucture, we cannot make any guarantees of availability." Its code is MIT-licensed, and the data comes from the Steam Web API and match replays. We found no separate data licence.
- OpenDota limits: no key gives 60 calls a minute and 3,000 a day (
/api/metadata). Each data response we got carriedx-rate-limit-remaining-minuteandx-rate-limit-remaining-day. A key from opendota.com/api-keys costs $0.01 per 100 calls, needs a payment method and allows 3,000 calls a minute. We hit no 429. - Riot: the General Policies say "Your product cannot feature betting or gambling functionality."
- Steam: the API terms license Steam data to end users "for their personal use" and cap you at 100,000 calls a day.
- Liquipedia: 1 request per 2 seconds,
action=parseonce per 30 seconds, gzip, CC-BY-SA 3.0 with credit to Liquipedia, and no "automated access to non-API endpoints". Breaking the rules brings "automated temporary IP bans".
For an odds product, a paid app or anything with an SLA, use licensed esports data providers: compare licensed sports data providers and the esports odds APIs.
Community Resources
- odota/core on GitHub: the OpenDota server code under the MIT licence, with setup notes for running your own copy.
- odota/dotaconstants on GitHub: hero, item, game mode and lobby type ids, the lookup tables the OpenDota spec points to.
- python-opendota on PyPI: a Python SDK generated from the OpenDota spec, version 3.0.0 from February 2023.
- Riot-Watcher on GitHub: a Python wrapper for the Riot Games API (
riotwatcher3.3.1 on PyPI). It needs your own Riot key.
Frequently Asked Questions
https://api.opendota.com/api with no key and no account, up to 60 calls a minute and 3,000 a day. Liquipedia's wiki APIs need no key either, only a named User-Agent and one request every 2 seconds. Riot, Steam and Faceit need a key tied to an account.https://api.opendota.com/api/metadata. Each data call reports what is left in x-rate-limit-remaining-minute and x-rate-limit-remaining-day. A paid key allows 3,000 calls a minute at $0.01 per 100 calls, and OpenDota does not bill 404, 429 or 500 responses.https://api.opendota.com/api/proMatches for the latest 100 pro matches with teams, league and radiant_win. Pass a match_id to /matches/{match_id} for the draft and player stats, and add less_than_match_id to /proMatches to page back in time.Need Licensed Esports Data?
OpenDota and the wiki APIs suit hobby projects and research. A betting or paid product needs licensed data, support and an uptime promise. Compare providers in our directory.