# Matches API Endpoint Documentation This document provides detailed information about the matches API endpoints used by CS2.WTF to retrieve match data from the backend CSGOWTFD service. ## Overview The matches API provides access to Counter-Strike 2 match data including match listings, detailed match statistics, and related match information such as weapons, rounds, and chat data. ## Base URL All endpoints are relative to the API base URL: `https://api.csgow.tf` During development, requests are proxied through `/api` to avoid CORS issues. ## Authentication No authentication is required for read operations. All match data is publicly accessible. ## Rate Limiting The API does not currently enforce rate limiting, but clients should implement reasonable request throttling to avoid overwhelming the service. ## Endpoints ### 1. Get Matches List Retrieves a paginated list of matches. **Endpoint**: `GET /matches` **Alternative**: `GET /matches/next/:time` **Parameters**: - `time` (path, optional): Unix timestamp for pagination (use with `/matches/next/:time`) - Query parameters: - `limit` (optional): Number of matches to return (default: 50, max: 100) - `map` (optional): Filter by map name (e.g., `de_inferno`) - `player_id` (optional): Filter by player Steam ID **Response** (200 OK): **IMPORTANT**: This endpoint returns a **plain array**, not an object with properties. ```json [ { "match_id": "3589487716842078322", "map": "de_inferno", "date": 1730487900, "score": [13, 10], "duration": 2456, "match_result": 1, "max_rounds": 24, "parsed": true, "vac": false, "game_ban": false } ] ``` **Field Descriptions**: - `match_id`: Unique match identifier (uint64 as string) - `map`: Map name (can be empty string if not parsed) - `date`: Unix timestamp (seconds since epoch) - `score`: Array with two elements `[team_a_score, team_b_score]` - `duration`: Match duration in seconds - `match_result`: 0 = tie, 1 = team_a win, 2 = team_b win - `max_rounds`: Maximum rounds (24 for MR12, 30 for MR15) - `parsed`: Whether the demo has been parsed - `vac`: Whether any player has a VAC ban - `game_ban`: Whether any player has a game ban **Pagination**: - The API returns a plain array of matches, sorted by date (newest first) - To get the next page, use the `date` field from the **last match** in the array - Request `/matches/next/{timestamp}` where `{timestamp}` is the Unix timestamp - Continue until the response returns fewer matches than your `limit` parameter - Example: If you request `limit=20` and get back 15 matches, you've reached the end ### 2. Get Match Details Retrieves detailed information about a specific match including player statistics. **Endpoint**: `GET /match/{match_id}` **Parameters**: - `match_id` (path): The unique match identifier (uint64 as string) **Response** (200 OK): ```json { "match_id": "3589487716842078322", "share_code": "CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", "map": "de_inferno", "date": "2024-11-01T18:45:00Z", "score_team_a": 13, "score_team_b": 10, "duration": 2456, "match_result": 1, "max_rounds": 24, "demo_parsed": true, "vac_present": false, "gameban_present": false, "tick_rate": 64.0, // Optional: not always provided by API "players": [ { "id": "765611980123456", "name": "Player1", "avatar": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/fe/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg", "team_id": 2, "kills": 24, "deaths": 18, "assists": 6, "headshot": 12, "mvp": 3, "score": 56, "kast": 78, // Optional: not always provided by API "rank_old": 18500, "rank_new": 18650, "dmg_enemy": 2450, "dmg_team": 120, "flash_assists": 4, "flash_duration_enemy": 15.6, "flash_total_enemy": 8, "ud_he": 450, "ud_flames": 230, "ud_flash": 5, "ud_smoke": 3, "avg_ping": 25.5, "color": "yellow" } ] } ``` ### 3. Get Match Weapons Retrieves weapon statistics for all players in a match. **Endpoint**: `GET /match/{match_id}/weapons` **Parameters**: - `match_id` (path): The unique match identifier **Response** (200 OK): ```json { "match_id": 3589487716842078322, "weapons": [ { "player_id": 765611980123456, "weapon_stats": [ { "eq_type": 17, "weapon_name": "AK-47", "kills": 12, "damage": 1450, "hits": 48, "hit_groups": { "head": 8, "chest": 25, "stomach": 8, "left_arm": 3, "right_arm": 2, "left_leg": 1, "right_leg": 1 }, "headshot_pct": 16.7 } ] } ] } ``` ### 4. Get Match Rounds Retrieves round-by-round statistics for a match. **Endpoint**: `GET /match/{match_id}/rounds` **Parameters**: - `match_id` (path): The unique match identifier **Response** (200 OK): ```json { "match_id": 3589487716842078322, "rounds": [ { "round": 1, "winner": 2, "win_reason": "elimination", "players": [ { "round": 1, "player_id": 765611980123456, "bank": 800, "equipment": 650, "spent": 650, "kills_in_round": 2, "damage_in_round": 120 } ] } ] } ``` ### 5. Get Match Chat Retrieves chat messages from a match. **Endpoint**: `GET /match/{match_id}/chat` **Parameters**: - `match_id` (path): The unique match identifier **Response** (200 OK): ```json { "match_id": 3589487716842078322, "messages": [ { "player_id": 765611980123456, "player_name": "Player1", "message": "nice shot!", "tick": 15840, "round": 8, "all_chat": true, "timestamp": "2024-11-01T19:12:34Z" } ] } ``` ### 6. Parse Match from Share Code Initiates parsing of a match from a CS:GO/CS2 share code. **Endpoint**: `GET /match/parse/{sharecode}` **Parameters**: - `sharecode` (path): The CS:GO/CS2 match share code **Response** (200 OK): ```json { "match_id": "3589487716842078322", "status": "parsing", "message": "Demo download and parsing initiated", "estimated_time": 120 } ``` ## Data Models ### Match ```typescript interface Match { match_id: string; // Unique match identifier (uint64 as string) share_code?: string; // CS:GO/CS2 share code (optional) map: string; // Map name (e.g., "de_inferno") date: string; // Match date and time (ISO 8601) score_team_a: number; // Final score for team A score_team_b: number; // Final score for team B duration: number; // Match duration in seconds match_result: number; // Match result: 0 = tie, 1 = team_a win, 2 = team_b win max_rounds: number; // Maximum rounds (24 for MR12, 30 for MR15) demo_parsed: boolean; // Whether the demo has been successfully parsed vac_present: boolean; // Whether any player has a VAC ban gameban_present: boolean; // Whether any player has a game ban tick_rate?: number; // Server tick rate (64 or 128) - optional, not always provided by API players?: MatchPlayer[]; // Array of player statistics (optional) } ``` ### MatchPlayer ```typescript interface MatchPlayer { id: string; // Player Steam ID (uint64 as string) name: string; // Player display name avatar: string; // Steam avatar URL team_id: number; // Team ID: 2 = T side, 3 = CT side kills: number; // Kills deaths: number; // Deaths assists: number; // Assists headshot: number; // Headshot kills mvp: number; // MVP stars earned score: number; // In-game score kast?: number; // KAST percentage (0-100) - optional, not always provided by API rank_old?: number; // Premier rating before match (0-30000) rank_new?: number; // Premier rating after match (0-30000) dmg_enemy?: number; // Damage to enemies dmg_team?: number; // Damage to teammates flash_assists?: number; // Flash assist count flash_duration_enemy?: number; // Total enemy blind time flash_total_enemy?: number; // Enemies flashed count ud_he?: number; // HE grenade damage ud_flames?: number; // Molotov/Incendiary damage ud_flash?: number; // Flash grenades used ud_smoke?: number; // Smoke grenades used avg_ping?: number; // Average ping color?: string; // Player color } ``` ### MatchListItem ```typescript interface MatchListItem { match_id: string; // Unique match identifier (uint64 as string) map: string; // Map name date: string; // Match date and time (ISO 8601) score_team_a: number; // Final score for team A score_team_b: number; // Final score for team B duration: number; // Match duration in seconds demo_parsed: boolean; // Whether the demo has been successfully parsed player_count?: number; // Number of players in the match - optional, not provided by API } ``` ## Error Handling All API errors follow a consistent format: ```json { "error": "Error message", "code": 404, "details": { "match_id": "3589487716842078322" } } ``` ### Common HTTP Status Codes - `200 OK`: Request successful - `400 Bad Request`: Invalid parameters - `404 Not Found`: Resource not found - `500 Internal Server Error`: Server error ## Implementation Notes ### Pagination The matches API implements cursor-based pagination using timestamps: 1. Initial request to `/matches` returns a plain array of matches (sorted newest first) 2. Extract the `date` field from the **last match** in the array 3. Request `/matches/next/{timestamp}` to get older matches 4. Continue until the response returns fewer matches than your `limit` parameter 5. The API does **not** provide `has_more` or `next_page_time` fields - you must calculate these yourself ### Data Transformation The frontend application transforms legacy API responses to a modern schema-validated format: - Unix timestamps are converted to ISO strings - Avatar hashes are converted to full URLs (if provided) - Team IDs are normalized (1/2 → 2/3 if needed) - Score arrays `[team_a, team_b]` are split into separate fields - Field names are mapped: `parsed` → `demo_parsed`, `vac` → `vac_present`, `game_ban` → `gameban_present` - Missing fields are provided with defaults (e.g., `tick_rate: 64`) ### Steam ID Handling All Steam IDs and Match IDs are handled as strings to preserve uint64 precision. Never convert these to numbers as it causes precision loss. ## Examples ### Fetching Matches with Pagination ```javascript // Initial request - API returns a plain array const matches = await fetch('/api/matches?limit=20').then((r) => r.json()); // matches is an array: [{ match_id, map, date, ... }, ...] console.log(`Loaded ${matches.length} matches`); // Get the timestamp of the last match for pagination if (matches.length > 0) { const lastMatch = matches[matches.length - 1]; const lastTimestamp = lastMatch.date; // Unix timestamp // Fetch next page using the timestamp const moreMatches = await fetch(`/api/matches/next/${lastTimestamp}?limit=20`).then((r) => r.json() ); console.log(`Loaded ${moreMatches.length} more matches`); // Check if we've reached the end if (moreMatches.length < 20) { console.log('Reached the end of matches'); } } ``` ### Complete Pagination Loop ```javascript async function loadAllMatches(limit = 50) { let allMatches = []; let hasMore = true; let lastTimestamp = null; while (hasMore) { // Build URL based on whether we have a timestamp const url = lastTimestamp ? `/api/matches/next/${lastTimestamp}?limit=${limit}` : `/api/matches?limit=${limit}`; // Fetch matches const matches = await fetch(url).then((r) => r.json()); // Add to collection allMatches.push(...matches); // Check if there are more if (matches.length < limit) { hasMore = false; } else { // Get timestamp of last match for next iteration lastTimestamp = matches[matches.length - 1].date; } } return allMatches; } ``` ### Filtering Matches by Map ```javascript const response = await fetch('/api/matches?map=de_inferno&limit=20'); const data = await response.json(); ``` ### Filtering Matches by Player ```javascript const response = await fetch('/api/matches?player_id=765611980123456&limit=20'); const data = await response.json(); ```