Skip to content
axsddlrPublic

About

An Unofficial REST API for vlr.gg, a site for Valorant Pro Esports match results and news.

Topics

Resources

Stars

224 stars

Watchers

4 watching

Forks

Repository files navigation

vlrggapi

Important Notice

https://vlrggapi.vercel.app/ is currently down because it exceeded free-tier limits. Please self-host the API for now, or wait while I look into another solution.

An Unofficial REST API for vlr.gg, a site for Valorant Esports match and news coverage.

Built by Andre Saddler

Support

If this API helps your projects, you can support ongoing maintenance and development on Ko-fi.

Buy Me a Coffee at ko-fi.com

Quick Start

  • Public base URL: https://vlrggapi.vercel.app — currently down, see the notice above
  • Local base URL: http://127.0.0.1:3001 (see Installation)
  • Interactive docs: /
  • Version info: /version
curl "http://127.0.0.1:3001/v2/news"
curl "http://127.0.0.1:3001/v2/match?q=live_score"
curl "http://127.0.0.1:3001/v2/player?id=9&timespan=all"

Highlights

  • V2-first API - consistent response envelopes, validation, and per-endpoint caching
  • Backwards compatibility - original unversioned endpoints are still available
  • Deep match coverage - detailed match, player, team, and event match endpoints
  • Operational guardrails - async HTTP, rate limiting, and bounded expensive scrapes

What's New

Match Details, Player Profiles & Team Profiles

New endpoints provide deep coverage of individual matches, players, and teams:

  • Match details — per-map player stats (K/D/A, ACS, rating), round-by-round data, kill matrix, economy breakdown, head-to-head history, map veto data
  • Player profiles — agent stats (17 metrics), current/past teams, event placements, total winnings, match history
  • Team profiles — roster with roles/captain status, VLR rating/rank, event placements, total winnings, match history, roster transactions
  • Event matches — full match list for any event with scores and VOD links
  • Event detail — event info with prize pool breakdown, participating team rosters, and group/stage standings tables
  • Search — cross-entity search for teams, players, and events by name or keyword

V2 API

  • Standardized responses — all V2 endpoints return {"status": "success", "data": {...}}
  • Input validation — invalid parameters return HTTP 400 with clear error messages
  • Per-endpoint caching — in-memory TTL cache reduces load on vlr.gg
  • Async HTTP — scrapers use httpx for non-blocking I/O

API Versions

Both the original and V2 endpoints coexist. The original endpoints (/news, /match, etc.) are preserved unchanged for backwards compatibility. V2 endpoints (/v2/news, /v2/match, etc.) are the recommended API going forward.

Feature Original V2
Response shape Varies per endpoint Consistent {"status": "success", "data": {...}}
Input validation HTTP 400 on most routes (/match and /events are looser) HTTP 400 on invalid params
Caching Shared in-memory TTL cache Per-endpoint TTL cache

Interactive Swagger docs are available at /.

Operational Notes

  • Recommended base path - use /v2 for new integrations
  • Current version endpoint - GET /version returns the current API version and default API
  • Rate limit - tiered sliding-window limits, applied per client IP. 200/min (/health, /version), 60/min (/news, /events, /event/{id}, /events/matches, /rankings, /search, and /match with q=upcoming or q=live_score), 20/min (/match/details, /stats, /player, /team, and /match with q=results or q=upcoming_extended). The /v2 prefix shares each tier with its unversioned route. Exceeding a tier returns HTTP 429 with Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers. Limits are per worker process, so a multi-worker deployment enforces them independently in each worker.
  • Error handling - V2 returns HTTP 400 for invalid input and propagates upstream failures with HTTP error codes
  • Deployment targets - Vercel for the hosted API, Docker for containerized self-hosting

V2 Endpoint Overview

Route Query Params Cache
GET /v2/news — 10 min
GET /v2/match q (upcoming/upcoming_extended/live_score/results); pagination params num_pages, from_page, to_page apply to upcoming_extended/results only; also max_retries, request_delay, timeout 30s–5 min
GET /v2/match/details match_id 5 min
GET /v2/rankings region 1 hr
GET /v2/stats region, timespan, event_id 30 min
GET /v2/events q (upcoming/completed/live), page 30 min
GET /v2/event/{id} event_id (path), stage 30 min
GET /v2/events/matches event_id 10 min
GET /v2/search q 5 min
GET /v2/player id, q (profile/matches), timespan, page 30 min / 10 min
GET /v2/team id, q (profile/matches/transactions/stats/roster/schedule), page 30 min / 10 min / 1 hr / 10 min / 30 min / 5 min
GET /v2/health — none

See section below for full descriptions and response examples.

V2 Endpoints

All examples are collapsed — click to expand.

GET /v2/news

Params: none | Cache: 10 min

GET /v2/news
Response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [
      {
        "title": "Article title",
        "description": "Article summary",
        "date": "April 23, 2024",
        "author": "author_name",
        "url_path": "https://vlr.gg/..."
      }
    ]
  }
}

GET /v2/match

Params: q (required: upcoming/upcoming_extended/live_score/results), num_pages, from_page, to_page, max_retries, request_delay, timeout Cache: 30s (live_score), 5min (upcoming, upcoming_extended), 60s (results)

The pagination params (num_pages, from_page, to_page) apply to upcoming_extended and results only. Passing any of them with q=upcoming or q=live_score returns HTTP 400, since those read a single fixed page. For the paginated queries the page window is capped at 20 pages, max_retries at 3, and timeout at 45 seconds.

GET /v2/match?q=upcoming
Response (upcoming)
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [
      {
        "team1": "G2 Esports", "team2": "Leviatán",
        "flag1": "flag_us", "flag2": "flag_cl",
        "time_until_match": "51m from now",
        "match_series": "Regular Season: Week 3",
        "match_event": "Champions Tour 2024: Americas Stage 1",
        "unix_timestamp": "2024-04-24 21:00:00",
        "match_page": "https://www.vlr.gg/...",
        "match_id": "312784"
      }
    ]
  }
}

GET /v2/rankings

Params: region (required — see Region Codes) | Cache: 1 hr

GET /v2/rankings?region=na
Response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [
      {
        "rank": "1", "team": "Sentinels", "country": "United States",
        "last_played": "22h ago", "record": "7-3", "earnings": "$295,500",
        "logo": "//owcdn.net/img/..."
      }
    ]
  }
}

GET /v2/stats

Params: region (required), timespan (required: 30/60/90/all), event_id (optional — numeric event ID, filters the stats to a single event) | Cache: 30 min

Regions (the /stats page taxonomy, distinct from /rankings): all, americas, emea, pacific, china, intl. Deprecated aliases are still accepted and normalized: na/br → americas, eu → emea, ap/kr/jp/oce → pacific, cn → china.

GET /v2/stats?region=americas&timespan=30
Response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [
      {
        "player": "player_name", "org": "ORG",
        "rating": "1.18", "average_combat_score": "235.2",
        "kill_deaths": "1.19", "kill_assists_survived_traded": "72%",
        "average_damage_per_round": "158.4", "kills_per_round": "0.81",
        "assists_per_round": "0.29", "first_kills_per_round": "0.19",
        "first_deaths_per_round": "0.13", "headshot_percentage": "26%",
        "clutch_success_percentage": "28%", "clutch_attempts": "9/57"
      }
    ]
  }
}

GET /v2/events

Browse events — use to discover event IDs for detail/matches lookups. Params: q (optional: upcoming/completed/live), page | Cache: 30 min

GET /v2/events?q=upcoming
GET /v2/events?q=live
Response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [
      {
        "title": "VCT 2026: Pacific Stage 2", "event_id": "2776", "status": "completed",
        "prize": "$250,000", "dates": "Jul 15—Sep 6", "region": "kr",
        "thumb": "https://owcdn.net/img/...",
        "url_path": "https://www.vlr.gg/event/2776/vct-2026-pacific-stage-2"
      }
    ]
  }
}

GET /v2/match/details

Params: match_id (required) | Cache: 5 min (30s for live)

GET /v2/match/details?match_id=595657
Response
{
  "status": "success",
  "data": {
    "match_id": "595657",
    "event": { "name": "Champions Tour 2024: Americas Stage 1", "series": "Regular Season: Week 3" },
    "map_vetos": "SEN ban Breeze; C9 ban Lotus; SEN pick Ascent; C9 pick Bind; Haven remains",
    "date": "April 23, 2024", "status": "completed",
    "teams": [
      { "id": "2", "name": "Sentinels", "score": 2, "logo": "//owcdn.net/img/..." },
      { "id": "188", "name": "Cloud9", "score": 1, "logo": "//owcdn.net/img/..." }
    ],
    "maps": [{
      "map_name": "Ascent", "picked_by": "Sentinels", "duration": "28:41",
      "score": { "team1": { "total": 13, "ct": 8, "t": 5 }, "team2": { "total": 9, "ct": 4, "t": 5 } },
      "players": { "team1": [{ "name": "TenZ", "agent": "Jett", "rating": "1.32", "acs": "267", "kills": "24", "deaths": "15", "assists": "3", "kd_diff": "+9", "kast": "77%", "adr": "172.3", "hs_pct": "32%", "fk": "4", "fd": "2", "fk_diff": "+2" }], "team2": [] },
      "rounds": [{ "round_num": 1, "winner": "team1", "side": "t" }]
    }],
    "head_to_head": [{ "event": "VCT Americas", "date": "2024/03/15", "score": "2-1", "url": "https://www.vlr.gg/..." }],
    "performance": {
      "kill_matrix": [{ "player": "TenZ", "kills_vs": { "opponent1": "5" } }],
      "advanced_stats": [{ "player": "TenZ", "2K": "3", "3K": "1" }]
    },
    "economy": [{
      "Team": "Sentinels", "Pistol": "1/2", "Conv.": "1/1", "Bonus": "0/1",
      "On Eco <10k: vs. Full": "0/2", "On Half buy 10–18k: vs. Full": "0/3",
      "On Full buy 18k+: vs. Eco": "2/2", "On Full buy 18k+: vs. Half": "1/1", "On Full buy 18k+: vs. Full": "4/8"
    }]
  }
}

GET /v2/event/{event_id}

Event detail: stages, prizes, team rosters, and standings tables. Params: event_id (path, required — from /v2/events), stage (optional — a slug from stages) | Cache: 30 min

A multi-stage event page on vlr.gg shows one stage at a time, and its prize rows and standings are that stage's (a group-stage view lists only the placements decided there). Without stage you get vlr.gg's default view, usually the current stage; stages lists every stage with its slug, and stage=<slug> returns that stage's view. An unknown slug returns 404.

GET /v2/event/2124
GET /v2/event/2124?stage=playoffs
Response
{
  "status": "success",
  "data": {
    "segments": {
      "event": {
        "name": "VCT 2026: Americas Stage 1", "series": "Valorant Champions Tour 2026",
        "dates": "Apr 15 - May 10, 2026", "prize": "$250,000 USD",
        "location": "Los Angeles, USA", "logo": "https://owcdn.net/img/..."
      },
      "stages": [
        { "name": "Playoffs", "slug": "playoffs", "dates": "May 2–10", "active": false },
        { "name": "Group Stage", "slug": "group-stage", "dates": "Apr 15–27", "active": true }
      ],
      "prizes": [
        { "placement": "1st", "amount": "$100,000", "team": { "id": "120", "name": "100 Thieves", "logo": "...", "region": "United States" } },
        { "placement": "2nd", "amount": "$60,000", "team": { "id": "2355", "name": "KRÜ Esports", "logo": "...", "region": "Chile" } }
      ],
      "teams": [{
        "id": "120", "name": "100 Thieves",
        "players": [{ "id": "9", "name": "Asuna", "flag": "mod-us" }],
        "qualification": "NA Circuit Points"
      }],
      "standings": [{
        "stage": "Group Stage", "group": "Group A", "columns": ["Team", "W", "L", "T", "MAP", "RND", "Δ"],
        "rows": [{ "Team": "100 Thieves", "W": "4", "L": "1", "T": "0", "MAP": "8/3", "RND": "135/102", "Δ": "+33" }]
      }]
    }
  }
}

GET /v2/search

Cross-entity search for players, teams, and events. Params: q (required) | Cache: 5 min

GET /v2/search?q=tenz
Response
{
  "status": "success",
  "data": {
    "segments": {
      "query": "tenz",
      "results": {
        "players": [{ "id": "9", "name": "TenZ", "img": "https://owcdn.net/img/...", "description": "", "tag": "" }],
        "teams": [{ "id": "16647", "name": "TenZ and Friends", "img": "https://...", "description": "", "tag": "(inactive)" }],
        "events": []
      }
    }
  }
}

GET /v2/player

Params: id (required), q (profile/matches, default: profile), timespan (30d/60d/90d/all, default: 90d), page (1-based, default: 1) | Cache: varies

GET /v2/player?id=9&q=profile&timespan=all
GET /v2/player?id=9&q=matches&page=1
Profile response
{
  "status": "success",
  "data": {
    "info": { "name": "TenZ", "real_name": "Tyson Ngo", "avatar": "https://owcdn.net/img/...", "country": "Canada", "socials": [{ "platform": "twitter", "url": "..." }] },
    "current_teams": [{ "name": "Sentinels", "tag": "SEN", "status": "Active" }],
    "past_teams": [{ "name": "Cloud9", "tag": "C9", "dates": "2020–2021" }],
    "agent_stats": [{ "agent": "Jett", "use_count": 150, "use_pct": "42%", "rounds": 3200, "rating": "1.15", "acs": "245.3", "kd": "1.18", "adr": "162.1", "kast": "71%", "kpr": "0.82", "apr": "0.28", "fkpr": "0.20", "fdpr": "0.14", "kills": 2624, "deaths": 2224, "assists": 896, "fk": 640, "fd": 448 }],
    "event_placements": [{ "event": "Champions 2024", "placement": "1st", "prize": "$100,000", "team": "Sentinels" }],
    "total_winnings": "$177,650"
  }
}
Matches response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [{
      "match_id": "719455",
      "url": "https://www.vlr.gg/719455/...",
      "event": "Twitch Rivals 2026",
      "date": "2026/07/24 8:50 pm",
      "team1": { "name": "Team TenZ", "tag": "Team TenZ", "logo": "" },
      "team2": { "name": "Team s0m", "tag": "Team s0m", "logo": "" },
      "score": "2:0",
      "result": "win"
    }],
    "meta": { "page": 1 }
  }
}

GET /v2/team

Params: id (required), q (profile/matches/transactions/stats/roster/schedule, default: profile), page (1-based, default: 1) | Cache: 30 min (profile, roster), 10 min (matches, stats), 1 hr (transactions), 5 min (schedule)

GET /v2/team?id=2&q=profile
GET /v2/team?id=2&q=matches&page=1
GET /v2/team?id=2&q=transactions
GET /v2/team?id=2&q=stats
GET /v2/team?id=2&q=roster
GET /v2/team?id=2&q=schedule
Profile response
{
  "status": "success",
  "data": {
    "info": { "name": "Sentinels", "tag": "SEN", "logo": "https://owcdn.net/img/...", "country": "United States", "socials": [{ "platform": "twitter", "url": "..." }] },
    "rating": { "vlr_rating": "1850", "rank": "1", "region": "na" },
    "roster": [{ "alias": "TenZ", "real_name": "Tyson Ngo", "role": "Duelist", "is_captain": false, "avatar": "..." }],
    "event_placements": [{ "event": "Champions 2024", "placement": "1st", "prize": "$100,000" }],
    "total_winnings": "$1,194,000"
  }
}
Matches response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [{
      "match_id": "729757",
      "url": "https://www.vlr.gg/729757/sentinels-vs-kr-esports-...",
      "event": "LR2",
      "date": "2026/08/22",
      "time": "7:10 pm",
      "team1": { "name": "Sentinels", "tag": "SEN", "logo": "https://owcdn.net/img/..." },
      "team2": { "name": "KRÜ Esports", "tag": "KRÜ", "logo": "https://owcdn.net/img/..." },
      "score": "0:2",
      "result": "loss"
    }],
    "meta": { "page": 1 }
  }
}

Unlike the player equivalent, each segment also carries a separate time field; the player date folds both together.

Transactions response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [{
      "date": "Gurjiwan Gill",
      "action": "inactive",
      "player": { "name": "Jerrwin", "id": "34057", "url": "https://www.vlr.gg/player/34057/gurjiwan-gill", "avatar": "", "country": "in" },
      "role": ""
    }]
  }
}

As with the roster's transactions list, player is a nested object and date carries a person's full name rather than a date — see the roster note above.

Stats response
{
  "status": "success",
  "data": {
    "segments": [
      {
        "map": "Bind",
        "games": 86,
        "win_pct": "71%",
        "wins": 61,
        "losses": 25,
        "atk_first": 54,
        "def_first": 32,
        "atk_rwin_pct": "60%",
        "atk_rw": 575,
        "atk_rl": 383,
        "def_rwin_pct": "53%",
        "def_rw": 458,
        "def_rl": 408
      }
    ]
  }
}
Roster response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [{
      "active": [{
        "id": "1265", "url": "https://www.vlr.gg/player/1265/johnqt",
        "alias": "johnqt", "real_name": "Mohamed Amine Ouarid",
        "avatar": "https://owcdn.net/img/...", "country": "ma",
        "is_captain": true, "role": "", "is_staff": false
      }],
      "staff": [],
      "former": [],
      "benched": [],
      "transactions": [{
        "date": "Gurjiwan Gill", "action": "inactive",
        "player": { "name": "Jerrwin", "id": "34057", "url": "https://www.vlr.gg/player/34057/gurjiwan-gill", "avatar": "", "country": "in" },
        "role": ""
      }]
    }]
  }
}

segments holds a single object with the roster split into active, staff, former, and benched. transactions mirrors q=transactions, but note vlr.gg renders that table's columns as name and source link rather than a date, so date carries a person's full name and role carries a URL on join/leave entries.

Schedule response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [{
      "match_id": "729757",
      "url": "https://www.vlr.gg/729757/sentinels-vs-kr-esports-...",
      "event": "LR2",
      "date": "2026/08/22",
      "time": "7:10 pm",
      "eta": "",
      "team1": { "name": "Sentinels", "tag": "SEN", "logo": "https://owcdn.net/img/..." },
      "team2": { "name": "KRÜ Esports", "tag": "KRÜ", "logo": "https://owcdn.net/img/..." }
    }]
  }
}

segments is a flat list of upcoming matches. eta is a countdown string when vlr.gg renders one and "" otherwise.

GET /v2/events/matches

Params: event_id (required) | Cache: 10 min

GET /v2/events/matches?event_id=2095
Response
{
  "status": "success",
  "data": {
    "status": 200,
    "segments": [{
      "match_id": "595657",
      "url": "https://www.vlr.gg/595657/...",
      "date": "Sun, April 24, 2024",
      "time": "3:00 PM",
      "unix_timestamp": "2024-04-24 19:00:00",
      "status": "Completed",
      "note": "",
      "stage": "Playoffs",
      "event_series": "Grand Final",
      "team1": { "name": "Sentinels", "score": "2", "is_winner": true },
      "team2": { "name": "Cloud9", "score": "1", "is_winner": false }
    }]
  }
}

date and time are as vlr.gg rendered them, in the timezone vlr.gg picked for the requesting server. unix_timestamp is UTC, derived from a minute-precision countdown on the same page; it is "" when the page has none (e.g. an event whose every match is more than a day away or a day old).

GET /v2/health

Params: none | Cache: none

Reports local service readiness — the app itself and its shared HTTP client. It does not call vlr.gg, so it is safe to use as a container liveness probe.

GET /v2/health
Response
{
  "status": "success",
  "data": {
    "service": { "status": "Healthy" },
    "http_client": { "status": "Healthy", "status_code": null }
  },
  "meta": null,
  "message": null
}

Original Endpoints

Preserved for backwards compatibility. Most return {"data": {"status": int, "segments": [...]}}, mirroring their V2 counterparts — see V2 Endpoints for examples. Three differ:

  • /rankings returns {"status": int, "data": [...]} — the segment list is unwrapped.
  • /health returns the bare {"service": {...}, "http_client": {...}} object with no envelope.
  • /match/details omits the id field from every entry in teams.
Route Query Params
GET /news —
GET /match q (upcoming/upcoming_extended/live_score/results); pagination params for upcoming_extended/results only
GET /match/details match_id
GET /stats region, timespan, event_id
GET /rankings region
GET /events q (upcoming/completed/live), page
GET /events/matches event_id
GET /player id, timespan
GET /player/matches id, page
GET /team id
GET /team/matches id, page
GET /team/transactions id
GET /team/roster id
GET /team/schedule id
GET /health —

/search and /event/{id} exist only under /v2 — there is no unversioned equivalent.

GET /match?q=upcoming — response example
{
  "data": {
    "status": 200,
    "segments": [
      {
        "team1": "G2 Esports",
        "team2": "Leviatán",
        "flag1": "flag_us",
        "flag2": "flag_cl",
        "time_until_match": "51m from now",
        "match_series": "Regular Season: Week 3",
        "match_event": "Champions Tour 2024: Americas Stage 1",
        "unix_timestamp": "2024-04-24 21:00:00",
        "match_page": "https://www.vlr.gg/..."
      }
    ]
  }
}
GET /match?q=live_score — response example
{
  "data": {
    "status": 200,
    "segments": [
      {
        "team1": "Team 1",
        "team2": "Team 2",
        "flag1": "flag_xx",
        "flag2": "flag_xx",
        "team1_logo": "https://...",
        "team2_logo": "https://...",
        "score1": "1",
        "score2": "0",
        "team1_round_ct": "7",
        "team1_round_t": "6",
        "team2_round_ct": "5",
        "team2_round_t": "4",
        "map_number": "1",
        "current_map": "Ascent",
        "time_until_match": "LIVE",
        "match_event": "Event name",
        "match_series": "Series name",
        "unix_timestamp": "1713996000",
        "match_page": "https://www.vlr.gg/..."
      }
    ]
  }
}
GET /match?q=results — response example
{
  "data": {
    "status": 200,
    "segments": [
      {
        "team1": "Team Vitality",
        "team2": "Gentle Mates",
        "score1": "0",
        "score2": "2",
        "flag1": "flag_eu",
        "flag2": "flag_fr",
        "time_completed": "2h 44m ago",
        "round_info": "Regular Season-Week 4",
        "tournament_name": "Champions Tour 2024: EMEA Stage 1",
        "match_page": "/318931/team-vitality-vs-gentle-mates-...",
        "tournament_icon": "https://owcdn.net/img/..."
      }
    ]
  }
}
GET /stats?region=americas×pan=30 — response example
{
  "data": {
    "status": 200,
    "segments": [
      {
        "player": "corey",
        "org": "TTR",
        "rating": "1.18",
        "average_combat_score": "235.2",
        "kill_deaths": "1.19",
        "kill_assists_survived_traded": "72%",
        "average_damage_per_round": "158.4",
        "kills_per_round": "0.81",
        "assists_per_round": "0.29",
        "first_kills_per_round": "0.19",
        "first_deaths_per_round": "0.13",
        "headshot_percentage": "26%",
        "clutch_success_percentage": "28%",
        "clutch_attempts": "9/57"
      }
    ]
  }
}
GET /rankings?region=na — response example

Note: /rankings uses a different response shape than other endpoints.

{
  "status": 200,
  "data": [
    {
      "rank": "1",
      "team": "Sentinels",
      "country": "United States",
      "last_played": "22h ago",
      "last_played_team": "vs. Evil Geniuses",
      "last_played_team_logo": "//owcdn.net/img/...",
      "record": "7-3",
      "earnings": "$295,500",
      "logo": "//owcdn.net/img/..."
    }
  ]
}
GET /events?q=upcoming — response example
{
  "data": {
    "status": 200,
    "segments": [
      {
        "title": "VCT 2025: Pacific Stage 2",
        "status": "ongoing",
        "prize": "$250,000",
        "dates": "Jul 15—Aug 31",
        "region": "kr",
        "thumb": "https://owcdn.net/img/...",
        "url_path": "https://www.vlr.gg/event/..."
      }
    ]
  }
}
GET /health — response example
{
  "service": {
    "status": "Healthy"
  },
  "http_client": {
    "status": "Healthy",
    "status_code": null
  }
}

Region Codes

Code Region
na North America
eu Europe
ap Asia Pacific
la Latin America
la-s Latin America South
la-n Latin America North
oce Oceania
kr Korea
mn MENA
gc Game Changers
br Brazil
cn China
jp Japan
col Collegiate

Validation & Error Handling

V2 endpoints validate input and return HTTP 400 with a detail message:

  • Invalid region — /rankings accepts the 14 codes listed above. /stats uses its own taxonomy (all, americas, emea, pacific, china, intl) and normalizes the deprecated aliases instead of rejecting them
  • Invalid timespan — must be 30, 60, 90, or all
  • Invalid player timespan — must be 30d, 60d, 90d, or all
  • Invalid match query — must be upcoming, upcoming_extended, live_score, or results
  • Invalid event query — must be upcoming, completed, or live
  • Invalid player query — must be profile or matches
  • Invalid team query — must be profile, matches, transactions, stats, roster, or schedule
  • Invalid stage — must be a vlr.gg slug such as group-stage (lowercase letters, digits, and hyphens)
  • Invalid ID — match_id, event_id, player id, and team id must be numeric
  • Unsupported pagination — num_pages, from_page, and to_page are rejected for q=upcoming and q=live_score
  • Excessive workload — for the paginated match queries the page window is capped at 20 pages, max_retries at 3, and timeout at 45 seconds
{
  "detail": "Invalid region 'xyz'. Valid regions: ap, br, cn, col, eu, gc, jp, kr, la, la-n, la-s, mn, na, oce"
}

/stats reports its own vocabulary in the same shape:

{
  "detail": "Invalid region 'xyz'. Valid regions: all, americas, china, emea, intl, pacific"
}

The unversioned endpoints are not uniformly unvalidated — most (/rankings, /stats, /player, /player/matches, /team, /team/matches, /team/transactions, /team/roster, /team/schedule, /match/details, /events/matches) reuse these same validators and return the same HTTP 400. Two are deliberately looser for backwards compatibility: /match answers an unknown q with HTTP 200 and {"error": "Invalid query parameter"}, and /events ignores an unknown q and returns the unfiltered event list.

Caching

V2 endpoints use an in-memory TTL cache to reduce load on vlr.gg. Cache durations per endpoint:

| Data | TTL | |---|---|---| | Live scores | 30 seconds | | Match detail (live) | 30 seconds | | Results | 60 seconds | | Upcoming matches | 5 minutes | | Match detail | 5 minutes | | Search | 5 minutes | | Team schedule | 5 minutes | | News | 10 minutes | | Player matches | 10 minutes | | Team matches | 10 minutes | | Team stats | 10 minutes | | Event matches | 10 minutes | | Stats | 30 minutes | | Events | 30 minutes | | Event detail | 30 minutes | | Player profile | 30 minutes | | Team profile | 30 minutes | | Team roster | 30 minutes | | Rankings | 1 hour | | Team transactions | 1 hour |

Original endpoints share the same in-memory TTL cache as their V2 counterparts, so the same scrape is never repeated across the two versions. /health is the exception: it is never cached, since it only reports in-process state.

Installation

Requirements

  • Python 3.11 locally (from .python-version). CI tests 3.11–3.13, and the Docker image runs Python 3.14
  • uv
git clone https://github.com/axsddlr/vlrggapi/
cd vlrggapi
uv sync

uv sync creates .venv and installs the exact versions pinned in uv.lock, including the dev dependency group. Prefix commands with uv run to run them inside that environment — there is nothing to activate by hand.

Run locally

uv run python main.py

The API will be available at http://127.0.0.1:3001 and the interactive docs will be at http://127.0.0.1:3001/.

Smoke check

curl http://127.0.0.1:3001/version
curl "http://127.0.0.1:3001/v2/health"

Docker

docker compose up --build

Testing

uv run pytest tests/ -v

Built With

Rate limiting is implemented in-house (api/utils/rate_limiter.py) with no additional dependency.

Contributing

Issues and pull requests are welcome.

Recommended workflow:

  1. Branch from dev, the active integration branch (master is the default branch).
  2. Run uv sync and verify the app starts locally.
  3. Run uv run pytest tests/ -v.
  4. Open a pull request against dev.

Open a pull request or file an issue.

License

The MIT License (MIT)

About

An Unofficial REST API for vlr.gg, a site for Valorant Pro Esports match results and news.

Topics

Resources

Stars

224 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages