Files
csgowtf/docs/API.md
vikingowl 0404188d4d Document CSGOWTFD backend API and update domain modeling plans
Reflect backend audit findings in TODO.md:
- 12 REST endpoints documented for player, match, matches, and sitemap
- Data models aligned with backend schemas (Match, Player, MatchPlayer,
  etc.)
- CS2 compatibility confirmed with Premier rating support (0-30000)

Add comprehensive API documentation covering:
- Endpoint specifications and response structures
- Integration guide with TypeScript examples
- Error handling and caching strategies
- CS2 migration notes for rank system and MR12 changes
2025-11-04 19:32:08 +01:00

23 KiB

CSGOWTFD Backend API Documentation

Version: 1.0 Base URL: http://localhost:8000 (default) Backend Repository: https://somegit.dev/CSGOWTF/csgowtfd Framework: Go + Gin + Ent ORM Database: PostgreSQL


Table of Contents

  1. Overview
  2. API Endpoints
  3. Data Models
  4. Integration Guide
  5. Error Handling

Overview

The CSGOWTFD backend is a REST API service that provides Counter-Strike match statistics, player profiles, and demo parsing capabilities. It uses:

  • Web Framework: Gin (Go)
  • ORM: Ent (Entity Framework for Go)
  • Database: PostgreSQL with pgx driver
  • Cache: Redis
  • Steam Integration: Steam API + demo parsing

Configuration

Default backend configuration:

httpd:
  listen: ":8000"
  cors_allow_domains: ["*"]

database:
  driver: "pgx"
  connection: "postgres://username:password@localhost:5432/database_name"

redis:
  addr: "localhost:6379"

CORS

The backend supports CORS with configurable allowed domains. By default, all origins are allowed in development.


API Endpoints

Player Endpoints

1. Get Player Profile

Retrieves comprehensive player statistics and match history.

Endpoint: GET /player/:id Alternative: GET /player/:id/next/:time

Parameters:

  • id (path, required): Steam ID (uint64)
  • time (path, optional): Unix timestamp for pagination (get matches before this time)

Response (200 OK):

{
  "id": 76561198012345678,
  "name": "PlayerName",
  "avatar": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/...",
  "vanity_url": "custom-url",
  "vanity_url_real": "custom-url",
  "steam_updated": "2024-11-04T10:30:00Z",
  "profile_created": "2015-03-12T00:00:00Z",
  "wins": 1250,
  "looses": 980,
  "ties": 45,
  "vac_count": 0,
  "vac_date": null,
  "game_ban_count": 0,
  "game_ban_date": null,
  "oldest_sharecode_seen": "CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
  "matches": [
    {
      "match_id": 3589487716842078322,
      "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,
      "stats": {
        "team_id": 2,
        "kills": 24,
        "deaths": 18,
        "assists": 6,
        "headshot": 12,
        "mvp": 3,
        "score": 56,
        "kast": 78,
        "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"
      }
    }
  ]
}

Use Case: Display player profile page with career stats and recent matches.


2. Get Player Metadata

Retrieves lightweight player metadata (recent matches summary).

Endpoint: GET /player/:id/meta Alternative: GET /player/:id/meta/:limit

Parameters:

  • id (path, required): Steam ID
  • limit (path, optional): Number of recent matches to include (default: 10)

Response (200 OK):

{
  "id": 76561198012345678,
  "name": "PlayerName",
  "avatar": "...",
  "recent_matches": 25,
  "last_match_date": "2024-11-01T18:45:00Z",
  "avg_kills": 21.3,
  "avg_deaths": 17.8,
  "avg_kast": 75.2,
  "win_rate": 56.5
}

Use Case: Quick player card/preview without full match data.


3. Track Player

Adds a player to the tracking system for automatic match updates.

Endpoint: POST /player/:id/track

Parameters:

  • id (path, required): Steam ID

Request Body:

{
  "auth_code": "XXXX-XXXXX-XXXX"
}

Response (200 OK):

{
  "success": true,
  "message": "Player added to tracking queue"
}

Use Case: Allow users to track their own profile for automatic updates.


4. Untrack Player

Removes a player from the tracking system.

Endpoint: DELETE /player/:id/track

Parameters:

  • id (path, required): Steam ID

Response (200 OK):

{
  "success": true,
  "message": "Player removed from tracking"
}

Match Endpoints

5. Parse Match from Share Code

Triggers parsing of a CS:GO/CS2 match from a share code.

Endpoint: GET /match/parse/:sharecode

Parameters:

  • sharecode (path, required): CS:GO match share code (e.g., CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)

Response (200 OK):

{
  "match_id": 3589487716842078322,
  "status": "parsing",
  "message": "Demo download and parsing initiated"
}

Response (202 Accepted):

{
  "match_id": 3589487716842078322,
  "status": "queued",
  "estimated_time": 120
}

Use Case: User submits match share code for analysis.


6. Get Match Details

Retrieves full match details including all player statistics.

Endpoint: GET /match/:id

Parameters:

  • id (path, required): Match ID (uint64)

Response (200 OK):

{
  "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,
  "players": [
    {
      "id": 76561198012345678,
      "name": "Player1",
      "avatar": "...",
      "team_id": 2,
      "kills": 24,
      "deaths": 18,
      "assists": 6,
      "headshot": 12,
      "mvp": 3,
      "score": 56,
      "kast": 78,
      "rank_old": 18500,
      "rank_new": 18650,
      "dmg_enemy": 2450,
      "dmg_team": 120,
      "flash_assists": 4,
      "flash_duration_enemy": 15.6,
      "flash_duration_team": 2.3,
      "flash_duration_self": 1.2,
      "flash_total_enemy": 8,
      "flash_total_team": 1,
      "flash_total_self": 1,
      "ud_he": 450,
      "ud_flames": 230,
      "ud_flash": 5,
      "ud_smoke": 3,
      "ud_decoy": 0,
      "mk_2": 4,
      "mk_3": 2,
      "mk_4": 1,
      "mk_5": 0,
      "avg_ping": 25.5,
      "color": "yellow",
      "crosshair": "CSGO_XXXXXXXXXXXX"
    }
  ]
}

Use Case: Display match overview page with scoreboard.


7. Get Match Weapons

Retrieves weapon statistics for all players in a match.

Endpoint: GET /match/:id/weapons

Parameters:

  • id (path, required): Match ID

Response (200 OK):

{
  "match_id": 3589487716842078322,
  "weapons": [
    {
      "player_id": 76561198012345678,
      "weapon_stats": [
        {
          "eq_type": 7,
          "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
          }
        }
      ]
    }
  ]
}

Use Case: Display detailed weapon performance page for match.


8. Get Match Rounds

Retrieves round-by-round statistics for a match.

Endpoint: GET /match/:id/rounds

Parameters:

  • id (path, required): Match ID

Response (200 OK):

{
  "match_id": 3589487716842078322,
  "rounds": [
    {
      "round": 1,
      "winner": 2,
      "win_reason": "elimination",
      "players": [
        {
          "player_id": 76561198012345678,
          "bank": 800,
          "equipment": 650,
          "spent": 650,
          "kills_in_round": 2,
          "damage_in_round": 234
        }
      ]
    }
  ]
}

Use Case: Display economy tab with round-by-round breakdown.


9. Get Match Chat

Retrieves in-game chat messages from a match.

Endpoint: GET /match/:id/chat

Parameters:

  • id (path, required): Match ID

Response (200 OK):

{
  "match_id": 3589487716842078322,
  "messages": [
    {
      "player_id": 76561198012345678,
      "player_name": "Player1",
      "message": "nice shot!",
      "tick": 15840,
      "round": 8,
      "all_chat": true,
      "timestamp": "2024-11-01T19:12:34Z"
    }
  ]
}

Use Case: Display chat log page for match.


Matches Listing

10. 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
  • 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):

{
  "matches": [
    {
      "match_id": 3589487716842078322,
      "map": "de_inferno",
      "date": "2024-11-01T18:45:00Z",
      "score_team_a": 13,
      "score_team_b": 10,
      "duration": 2456,
      "demo_parsed": true,
      "player_count": 10
    }
  ],
  "next_page_time": 1698871200,
  "has_more": true
}

Use Case: Display matches listing page with filters.


Sitemap

11. Get Sitemap Index

Returns XML sitemap index for SEO.

Endpoint: GET /sitemap.xml

Response (200 OK):

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://csgow.tf/sitemap/1</loc>
    <lastmod>2024-11-04</lastmod>
  </sitemap>
</sitemapindex>

12. Get Sitemap Pages

Returns XML sitemap for specific page range.

Endpoint: GET /sitemap/:id

Parameters:

  • id (path, required): Sitemap page number

Response (200 OK):

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://csgow.tf/match/3589487716842078322</loc>
    <lastmod>2024-11-01</lastmod>
    <changefreq>never</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

Data Models

Match

interface Match {
  match_id: number;           // uint64, unique identifier
  share_code: string;         // CS:GO share code
  map: string;                // Map name (e.g., "de_inferno")
  date: string;               // ISO 8601 timestamp
  score_team_a: number;       // Final score for team A (T/CT)
  score_team_b: number;       // Final score for team B (CT/T)
  duration: number;           // Match duration in seconds
  match_result: number;       // 0 = tie, 1 = team_a win, 2 = team_b win
  max_rounds: number;         // Max rounds (24 for MR12, 30 for MR15)
  demo_parsed: boolean;       // Demo parsing status
  vac_present: boolean;       // Any VAC bans in match
  gameban_present: boolean;   // Any game bans in match
  tick_rate: number;          // Server tick rate (64 or 128)
  players?: MatchPlayer[];    // Array of player stats
}

Player

interface Player {
  id: number;                     // uint64, Steam ID
  name: string;                   // Steam display name
  avatar: string;                 // Avatar URL
  vanity_url?: string;            // Custom Steam URL
  vanity_url_real?: string;       // Actual vanity URL
  steam_updated: string;          // Last Steam profile update
  profile_created?: string;       // Steam account creation date
  wins?: number;                  // Total competitive wins
  looses?: number;                // Total competitive losses (note: typo in backend)
  ties?: number;                  // Total ties
  vac_count?: number;             // Number of VAC bans
  vac_date?: string;              // Date of last VAC ban
  game_ban_count?: number;        // Number of game bans
  game_ban_date?: string;         // Date of last game ban
  oldest_sharecode_seen?: string; // Oldest match on record
  matches?: Match[];              // Recent matches
}

MatchPlayer

interface MatchPlayer {
  match_stats: number;            // Match ID reference
  player_stats: number;           // Player ID reference
  team_id: number;                // 2 = T, 3 = CT

  // Performance
  kills: number;
  deaths: number;
  assists: number;
  headshot: number;               // Headshot kills
  mvp: number;                    // MVP stars
  score: number;                  // In-game score
  kast: number;                   // KAST percentage (0-100)

  // Rank
  rank_old?: number;              // Rating before match (CS2: 0-30000)
  rank_new?: number;              // Rating after match

  // Damage
  dmg_enemy?: number;             // Damage to enemies
  dmg_team?: number;              // Team damage

  // Multi-kills
  mk_2?: number;                  // Double kills
  mk_3?: number;                  // Triple kills
  mk_4?: number;                  // Quad kills
  mk_5?: number;                  // Ace (5k)

  // Utility damage
  ud_he?: number;                 // HE grenade damage
  ud_flames?: number;             // Molotov/Incendiary damage
  ud_flash?: number;              // Flash grenades used
  ud_smoke?: number;              // Smoke grenades used
  ud_decoy?: number;              // Decoy grenades used

  // Flash statistics
  flash_assists?: number;         // Assists from flashes
  flash_duration_enemy?: number;  // Total enemy blind time
  flash_duration_team?: number;   // Total team blind time
  flash_duration_self?: number;   // Self-flash time
  flash_total_enemy?: number;     // Enemies flashed count
  flash_total_team?: number;      // Teammates flashed count
  flash_total_self?: number;      // Self-flash count

  // Other
  crosshair?: string;             // Crosshair settings
  color?: 'green' | 'yellow' | 'purple' | 'blue' | 'orange' | 'grey';
  avg_ping?: number;              // Average ping
}

RoundStats

interface RoundStats {
  round: number;                  // Round number (1-24 for MR12)
  bank: number;                   // Money at round start
  equipment: number;              // Equipment value purchased
  spent: number;                  // Total money spent
  match_player_id: number;        // Reference to MatchPlayer
}

Weapon

interface Weapon {
  victim: number;                 // Player ID who was hit/killed
  dmg: number;                    // Damage dealt
  eq_type: number;                // Weapon type ID
  hit_group: number;              // Hit location (1=head, 2=chest, etc.)
  match_player_id: number;        // Reference to MatchPlayer
}

Message

interface Message {
  message: string;                // Chat message content
  all_chat: boolean;              // true = all chat, false = team chat
  tick: number;                   // Game tick when sent
  match_player_id: number;        // Reference to MatchPlayer
}

Integration Guide

Frontend Setup

  1. Install HTTP Client
npm install axios
# or use native fetch
  1. Create API Client (src/lib/api/client.ts)
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios';

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
const API_TIMEOUT = import.meta.env.VITE_API_TIMEOUT || 10000;

class APIClient {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: API_BASE_URL,
      timeout: API_TIMEOUT,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    // Response interceptor for error handling
    this.client.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response) {
          // Server responded with error
          console.error('API Error:', error.response.status, error.response.data);
        } else if (error.request) {
          // Request made but no response
          console.error('Network Error:', error.message);
        }
        return Promise.reject(error);
      }
    );
  }

  async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.get<T>(url, config);
    return response.data;
  }

  async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.post<T>(url, data, config);
    return response.data;
  }

  async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.delete<T>(url, config);
    return response.data;
  }
}

export const apiClient = new APIClient();
  1. Create API Endpoints (src/lib/api/players.ts)
import { apiClient } from './client';
import type { Player, Match } from '$lib/types';

export const playersAPI = {
  async getPlayer(steamId: string | number, beforeTime?: number): Promise<Player> {
    const url = beforeTime
      ? `/player/${steamId}/next/${beforeTime}`
      : `/player/${steamId}`;
    return apiClient.get<Player>(url);
  },

  async getPlayerMeta(steamId: string | number, limit: number = 10) {
    return apiClient.get(`/player/${steamId}/meta/${limit}`);
  },

  async trackPlayer(steamId: string | number, authCode: string) {
    return apiClient.post(`/player/${steamId}/track`, { auth_code: authCode });
  },

  async untrackPlayer(steamId: string | number) {
    return apiClient.delete(`/player/${steamId}/track`);
  },
};
  1. Create API Endpoints (src/lib/api/matches.ts)
import { apiClient } from './client';
import type { Match } from '$lib/types';

export const matchesAPI = {
  async getMatch(matchId: string | number): Promise<Match> {
    return apiClient.get<Match>(`/match/${matchId}`);
  },

  async getMatchWeapons(matchId: string | number) {
    return apiClient.get(`/match/${matchId}/weapons`);
  },

  async getMatchRounds(matchId: string | number) {
    return apiClient.get(`/match/${matchId}/rounds`);
  },

  async getMatchChat(matchId: string | number) {
    return apiClient.get(`/match/${matchId}/chat`);
  },

  async parseMatch(shareCode: string) {
    return apiClient.get(`/match/parse/${shareCode}`);
  },

  async getMatches(params?: {
    limit?: number;
    time?: number;
    map?: string;
    player_id?: number;
  }) {
    const queryString = params ? `?${new URLSearchParams(params as any).toString()}` : '';
    const url = params?.time
      ? `/matches/next/${params.time}${queryString}`
      : `/matches${queryString}`;
    return apiClient.get(url);
  },
};
  1. Use in SvelteKit Routes (src/routes/player/[id]/+page.ts)
import { playersAPI } from '$lib/api/players';
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params }) => {
  try {
    const player = await playersAPI.getPlayer(params.id);
    return { player };
  } catch (err) {
    throw error(404, 'Player not found');
  }
};

Error Handling

HTTP Status Codes

Status Meaning Common Causes
200 Success Request completed successfully
202 Accepted Request queued (async operations)
400 Bad Request Invalid parameters or malformed request
404 Not Found Player/match doesn't exist
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Backend issue
503 Service Unavailable Backend down or maintenance

Error Response Format

{
  "error": "Match not found",
  "code": "MATCH_NOT_FOUND",
  "details": {
    "match_id": 3589487716842078322
  }
}

Retry Strategy

For transient errors (500, 503), implement exponential backoff:

async function retryRequest<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      const delay = baseDelay * Math.pow(2, i);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

CS2 Migration Notes

Rank System Changes

CS:GO used 18 ranks (Silver I to Global Elite):

  • Values: 0-18

CS2 uses Premier Rating:

  • Values: 0-30,000
  • No traditional ranks in Premier mode

Backend Compatibility:

  • rank_old and rank_new fields now store Premier rating (0-30000)
  • Frontend must detect game version and display accordingly

MR12 vs MR15

CS:GO: MR15 (max 30 rounds) CS2: MR12 (max 24 rounds)

Check max_rounds field in Match data:

  • 24 = MR12 (CS2)
  • 30 = MR15 (CS:GO)

Game Mode Detection

To determine if a match is CS:GO or CS2:

  1. Check date field (CS2 released Sept 2023)
  2. Check max_rounds (24 = likely CS2)
  3. Backend may add game_version field in future updates

Rate Limiting

Current Limits (may vary by deployment):

  • Steam API: 1 request per second (backend handles this)
  • Demo Parsing: Max 6 concurrent parses
  • Frontend API: No explicit limit, but use reasonable request rates

Best Practices:

  • Implement client-side caching (5-15 minutes for match data)
  • Use debouncing for search inputs (300ms)
  • Batch requests when possible

Caching Strategy

Backend Caching (Redis)

The backend uses Redis for:

  • Steam API responses (7 days)
  • Match data (permanent until invalidated)
  • Player profiles (7 days)

Frontend Caching Recommendations

// In-memory cache with TTL
class DataCache<T> {
  private cache = new Map<string, { data: T; expires: number }>();

  set(key: string, data: T, ttlMs: number) {
    this.cache.set(key, {
      data,
      expires: Date.now() + ttlMs,
    });
  }

  get(key: string): T | null {
    const entry = this.cache.get(key);
    if (!entry) return null;
    if (Date.now() > entry.expires) {
      this.cache.delete(key);
      return null;
    }
    return entry.data;
  }
}

// Usage
const matchCache = new DataCache<Match>();
const cachedMatch = matchCache.get(`match:${matchId}`);
if (!cachedMatch) {
  const match = await matchesAPI.getMatch(matchId);
  matchCache.set(`match:${matchId}`, match, 15 * 60 * 1000); // 15 min
}

Testing

Mock Data

Use MSW (Mock Service Worker) for testing:

// src/mocks/handlers/matches.ts
import { http, HttpResponse } from 'msw';

export const matchHandlers = [
  http.get('/match/:id', ({ params }) => {
    return HttpResponse.json({
      match_id: parseInt(params.id as string),
      map: 'de_inferno',
      date: '2024-11-01T18:45:00Z',
      score_team_a: 13,
      score_team_b: 10,
      // ... rest of mock data
    });
  }),
];

Swagger Documentation

The backend provides interactive API documentation at:

URL: {API_BASE_URL}/api/swagger

This Swagger UI allows you to:

  • Explore all endpoints
  • Test API calls directly
  • View request/response schemas
  • See authentication requirements

Support & Updates


Document Version: 1.0 Last Updated: 2025-11-04 Maintained By: CSGOW.TF Development Team