HLTV data in JavaScript, without the scraper.
There is no official HLTV API for JavaScript. The hltv npm package works by downloading hltv.org pages, and scripted requests now get a Cloudflare 403 "Just a moment..." page instead. In Node.js 18+, fetch from a CS2 API: results, HLTV-style ratings, rankings and live scores are one call each, with a free key.
- Node 18+ fetch
- TypeScript types
- 500 free requests / month
{ "success": true, "data": [ { "id": "cs2-match-2399131", "eventName": "Stake Pulse Beat III", "bestOf": 3, "team1Name": "Ninjas in Pyjamas", "team2Name": "Betclic", "score": { "team1": 2, "team2": 1 } }, { "id": "cs2-match-2399130", "eventName": "Stake Pulse Beat III", "bestOf": 3, "team1Name": "Inner Circle", "team2Name": "Sangal", "score": { "team1": 2, "team2": 0 } }, "__more__" ]}Seamless Integration with our MCP Server
Give Claude, Cursor, and ChatGPT the CS2 data. One command writes the MCP config; agents call live scoreboard, team map stat, opening duel, and transfer tools instead of inventing match IDs, and your key stays on your machine.
MCPInstall the Cito MCP serverWhy does the hltv npm package fail?
It is a scraper: each function requests an hltv.org page and parses the HTML. On 7 October 2026 a scripted request to hltv.org got 403 Forbidden with cf-mitigated: challenge, a Cloudflare check that only a real browser passes, so the parser receives a challenge page instead of data.
How to detect the challenge in your own code: HLTV scraper alternative and the 403 fix.
- Latest release: 3.5.0, published 20 September 2023; about 1,000 downloads a week (npm, read 7 October 2026).
- When challenged it throws: Error: Access denied | www.hltv.org used Cloudflare to restrict access.
- Its live scorebot connection has an open report of 403s since January 2025.
- HLTV's terms of use, effective 27 January 2025, prohibit scraping the website (clause 2.2).
Step 1: How do I call the API from Node.js?
Node 18 and later ship fetch, so there is nothing to install. Keep the key on the server in an environment variable and send it in the x-api-key header; never put it in browser code.
const API = "https://api.citoapi.com/api/v1/cs2"
export async function get(path, params = {}) {
const url = new URL(API + path)
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v))
const res = await fetch(url, { headers: { "x-api-key": process.env.CITO_API_KEY } })
if (!res.ok) throw new Error(`${path}: HTTP ${res.status}`)
return res.json()
}Step 2: How do I get results and upcoming matches?
GET /cs2/matches/results returns finished matches newest first; GET /cs2/matches/upcoming returns the schedule. Both carry team names, the event and the format.
import { get } from "./cito.mjs"
const { data: results } = await get("/matches/results", { limit: 10 })
for (const m of results) console.log(`${m.eventName}: ${m.team1Name} ${m.team1Score}-${m.team2Score} ${m.team2Name}`)
const { data: upcoming } = await get("/matches/upcoming", { limit: 5 })
for (const m of upcoming) console.log(m.startsAt, m.team1Name, "vs", m.team2Name)Step 3: How do I type the responses in TypeScript?
Declare the fields you use. Every response has success, data and meta; lists add paging fields to meta. The shapes below match production responses.
type Envelope<T> = { success: boolean; data: T; meta?: Record<string, unknown> }
type RankingRow = { rank: number; teamName: string; points: number; change: number | null; region: string | null }
type MapLine = { matchId: string; kills: number; deaths: number; adr: number | null; kast: number | null; rating: number | null }
const API = "https://api.citoapi.com/api/v1/cs2"
async function get<T>(path: string): Promise<T> {
const res = await fetch(API + path, { headers: { "x-api-key": process.env.CITO_API_KEY ?? "" } })
if (!res.ok) throw new Error(`${path}: HTTP ${res.status}`)
return ((await res.json()) as Envelope<T>).data
}
const top10 = await get<RankingRow[]>("/rankings/world?limit=10")
const lines = await get<MapLine[]>("/players/zywoo/matches?limit=5")
console.log(top10.map((t) => `${t.rank}. ${t.teamName}`).join("\n"), lines.length)Step 4: How do I get live scores pushed instead of polling?
Open the Server-Sent Events stream at /cs2/live/stream and the server pushes score changes as they happen. Every plan can use it; it counts one request when it connects and one per 15 seconds open.
const res = await fetch("https://api.citoapi.com/api/v1/cs2/live/stream", {
headers: { "x-api-key": process.env.CITO_API_KEY, accept: "text/event-stream" },
})
const decoder = new TextDecoder()
let buffer = ""
for await (const chunk of res.body) {
buffer += decoder.decode(chunk, { stream: true })
let i
while ((i = buffer.indexOf("\n\n")) >= 0) {
const event = buffer.slice(0, i)
buffer = buffer.slice(i + 2)
const data = event.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5)).join("")
if (data) console.log(JSON.parse(data))
}
}Can I call it from the browser?
Not directly with your key: anything in browser code is public. Put a small server route in front (a Next.js route handler, an Express endpoint or a Cloudflare Worker) that adds the key and caches responses for a few seconds.
Endpoints you can ship with
Every endpoint the Node.js examples call.
Live matches
Poll the live list for every match in progress, open one match for its round, timer, bomb and players, or keep an SSE stream open and get each update pushed.
- GET
/api/v1/cs2/liveMatches in progress: series score, map in play, map score, current round.
- GET
/api/v1/cs2/live/{matchId}/stateOne live match: score, round, round timer, bomb status and players.
- GET
/api/v1/cs2/live/{matchId}/scoreboardSeries score, current map and round, and each player's kills, deaths, money and equipment.
- GET
/api/v1/cs2/live/{matchId}/roundsRound-by-round results so far: winner, side and how each round ended.
- GET
/api/v1/cs2/live/streamServer-Sent Events push of every live update. Every plan; counts like polling.
- GET
/api/v1/cs2/live/wsWebSocket with a room per match. Scale and Enterprise.
Results
Finished matches with the series score and winner, every map and its score, and the player lines behind them.
- GET
/api/v1/cs2/matches/recentFinished matches with final series score and winner, newest first.
- GET
/api/v1/cs2/matches/{matchId}One match: event, teams, series score, and maps.
- GET
/api/v1/cs2/matches/{matchId}/mapsEach map: name, score, winner, and the team that picked it.
- GET
/api/v1/cs2/matches/{matchId}/player-statsPer-player stats for each map: kills, deaths, ADR, KAST and rating.
- GET
/api/v1/cs2/teams/{teamIdOrSlug}/resultsOne team's finished matches, newest first.
- GET
/api/v1/cs2/events/{eventIdOrSlug}/resultsFinal placements and prize money of a tournament.
Schedule
What is on next: upcoming matches soonest first, today's slate, one team's fixtures, and the tournament calendar.
- GET
/api/v1/cs2/matches/upcomingScheduled matches, soonest first.
- GET
/api/v1/cs2/matches/todayMatches scheduled, live or finished today (UTC).
- GET
/api/v1/cs2/teams/{teamIdOrSlug}/upcomingOne team's scheduled matches.
- GET
/api/v1/cs2/events/upcomingScheduled tournaments, soonest first.
- GET
/api/v1/cs2/events/calendarTournaments in a date range, with from and to.
- GET
/api/v1/cs2/matches/{matchId}/streamsBroadcast links for a match, when known.
Player stats
Rating, ADR, KAST and K/D for every map a player has played, rolled up into career, yearly and recent-form views.
- GET
/api/v1/cs2/players/{playerIdOrSlug}Nickname, real name, photo, country, team, role and headline stats.
- GET
/api/v1/cs2/players/{playerIdOrSlug}/statsCareer totals plus a stat line for every map played.
- GET
/api/v1/cs2/players/{playerIdOrSlug}/seasonsA player's stats by year.
- GET
/api/v1/cs2/players/{playerIdOrSlug}/formThe most recent maps with kills, deaths, ADR, KAST and rating.
- GET
/api/v1/cs2/leaderboards/{metric}Top players by rating, ADR, KAST, K/D, kills or opening kills.
- GET
/api/v1/cs2/players/search?q={name}Find a player by nickname or real name.
Teams & rosters
Team profiles with the current roster, every past lineup, player moves between teams, and how each team plays each map.
- GET
/api/v1/cs2/teams/{teamIdOrSlug}Name, logo, country, region, world ranking and current roster.
- GET
/api/v1/cs2/teams/{teamIdOrSlug}/roster-historyEvery player who has played for the team, with start and end dates.
- GET
/api/v1/cs2/teams/{teamIdOrSlug}/map-statsPer-map win rate, rounds and pick/ban tendencies.
- GET
/api/v1/cs2/teams/{teamIdOrSlug}/vs/{opponentIdOrSlug}Record and past matches between two teams.
- GET
/api/v1/cs2/roster-changes/recentPlayers joining and leaving teams, newest first.
- GET
/api/v1/cs2/transfers/recentPlayer moves between teams, newest first.
Rankings
The world team ranking and the Valve Regional Standings, with points and movement, plus one table per region.
- GET
/api/v1/cs2/rankings/worldThe latest world team ranking.
- GET
/api/v1/cs2/rankings/vrsThe latest Valve Regional Standings (VRS).
- GET
/api/v1/cs2/rankings/region/{region}One region, with each team's world and regional rank.
- GET
/api/v1/cs2/rankingsOne row per team, with points and change since the previous ranking.
Skin prices
The current price of a skin on each marketplace, its daily price history, sell listings across markets, and the week's biggest movers.
- GET
/api/v1/cs2/skins?q={name}Search skins by name, weapon, rarity or price range.
- GET
/api/v1/cs2/skins/{skinIdOrSlug}/pricesThe current price on each marketplace, cheapest first, with offer counts.
- GET
/api/v1/cs2/skins/{skinIdOrSlug}/historyDaily cheapest price across marketplaces, years back on long-listed skins.
- GET
/api/v1/cs2/skins/{skinIdOrSlug}/order-bookSell listings per marketplace with counts, and the lowest ask.
- GET
/api/v1/cs2/skins/trendingBiggest gainers and losers over 1 day to 1 year, with sparklines.
- GET
/api/v1/cs2/skins/{skinIdOrSlug}One skin: weapon, rarity, collection, image and price summary.
Tournaments
Tournaments with dates, prize pool, tier and format, plus every match, the standings and the bracket.
- GET
/api/v1/cs2/events/liveTournaments being played now.
- GET
/api/v1/cs2/events/{eventIdOrSlug}Dates, location, prize pool, tier, teams and format.
- GET
/api/v1/cs2/events/{eventIdOrSlug}/matchesEvery match of the tournament.
- GET
/api/v1/cs2/events/{eventIdOrSlug}/standingsTeams with seed, final placement and prize.
- GET
/api/v1/cs2/events/{eventIdOrSlug}/bracketBracket, groups, play-in and Swiss, each slot with teams, scores and winner.
FAQs
HLTV and CS2 data in JavaScript
Can't find what you're looking for? Contact our customer support team
Next steps
- HLTV APIIs there an HLTV API? No official one. What to use instead.
- HLTV API in PythonResults, player stats, rankings and live scores in Python.
- CS2 live APISeries, map, and round score while the match is on.
- CS2 results APISeries and map scores for every finished match.
- HLTV stats APIHLTV-style ratings, ADR, KAST and K/D as JSON.
- Free CS2 APIWhat a free key includes, with every limit stated.
- HLTV scraper alternativeWhy HLTV answers scrapers with a 403 challenge, and the fix.
- CS2 API docsEvery endpoint, its parameters, and a real response.
Build in JavaScript:Â Discord bots
Create a free key and run the examples above.