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
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
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 IDlimit(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
- Install HTTP Client
npm install axios
# or use native fetch
- 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();
- 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`);
},
};
- 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);
},
};
- 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_oldandrank_newfields 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:
- Check
datefield (CS2 released Sept 2023) - Check
max_rounds(24 = likely CS2) - Backend may add
game_versionfield 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
- Backend Issues: https://somegit.dev/CSGOWTF/csgowtfd/issues
- Frontend Issues: https://somegit.dev/CSGOWTF/csgowtf/issues
- API Changes: Watch the backend repository for updates
Document Version: 1.0 Last Updated: 2025-11-04 Maintained By: CSGOW.TF Development Team