home documents terms contact shop (coming soon!)

Web API Integration

Real-time leaderboards, battle replays, and usage statistics for your website.

Build competitive Pokemon dashboards with CobbleRanked’s HTTP API. Push ranked battle data to your web server in real-time.

Features

  • Live Leaderboards - Top players sync every 30 seconds
  • Battle Replays - Full Showdown protocol logs
  • Match Results - Per-match history with team details
  • Usage Statistics - Pokemon usage with win rates
  • Configurable Intervals - Control sync frequency

Endpoints

EndpointDataDefault Interval
/api/leaderboardTop N players by ELO30 seconds
/api/usage-statsPokemon usage with win rates5 minutes
/api/match-resultIndividual match resultsImmediate
/api/battle-replayFull battle logs (Showdown)Immediate

Configuration

File: config/cobbleranked/api.yaml

enabled: false

endpoint:
  baseUrl: "https://your-server.com"
  paths:
    pushUsageStats: "/api/usage-stats"
    pushLeaderboard: "/api/leaderboard"
    pushBattleReplay: "/api/battle-replay"
    pushMatchResult: "/api/match-result"

auth:
  apiKey: "your-secret-key"

sync:
  intervalMinutes: 60
  leaderboardLimit: 100
  dataTypes:
    usageStats: true
    leaderboard: true
    battleReplays: false
    matchResults: false
  push:
    enabled: true
    onlyIfChanged: true
    batchIntervalSeconds: 30

http:
  timeoutSeconds: 30
  retryAttempts: 3
  retryDelaySeconds: 5
Server Authentication

CobbleRanked sends X-API-Key header with your configured key. Verify this on your server:

const VALID_KEY = process.env.API_KEY;

app.use((req, res, next) => {
  const receivedKey = req.headers['x-api-key'];
  if (receivedKey !== VALID_KEY) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
});

Data Payloads

Leaderboard

{
  "serverId": "your-server-id",
  "season": "Season 1",
  "format": "singles",
  "timestamp": 1745123456789,
  "players": [
    {
      "uuid": "player-uuid",
      "name": "PlayerName",
      "elo": 1850,
      "wins": 45,
      "losses": 12,
      "winRate": 78.95
    }
  ]
}

Match Result

{
  "matchId": "uuid",
  "serverId": "your-server-id",
  "season": "Season 1",
  "format": "singles",
  "timestamp": 1745123456789,
  "endReason": "BATTLE_END",
  "turnCount": 24,
  "players": [
    {
      "uuid": "winner-uuid",
      "name": "Winner",
      "eloBefore": 1800,
      "eloAfter": 1825,
      "eloChange": 25,
      "winner": true,
      "team": [
        {
          "species": "Charizard",
          "level": 100,
          "ability": "Blaze",
          "item": "ChoiceSpecs",
          "moves": ["Flamethrower", "AirSlash", "DragonPulse", "Roost"],
          "nature": "Timid",
          "evSpread": "0/252/4/0/0/252"
        }
      ]
    }
  ]
}

Battle Replay

{
  "replayId": "uuid",
  "serverId": "your-server-id",
  "season": "Season 1",
  "format": "singles",
  "timestamp": 1745123456789,
  "turnCount": 24,
  "endReason": "BATTLE_END",
  "battleLog": [
    "|player|p1|Player1|tate|102",
    "|player|p2|Player2|rusty|102",
    "|poke|p1|Charizard, L100, M|
    ...
  ],
  "players": [
    {
      "uuid": "player-uuid",
      "name": "Player1",
      "winner": true,
      "team": ["Charizard", "Dragonite", "Garchomp"]
    }
  ]
}

Usage Statistics

{
  "serverId": "your-server-id",
  "season": "Season 1",
  "format": "singles",
  "timestamp": 1745123456789,
  "pokemon": [
    {
      "species": "Charizard",
      "usageCount": 156,
      "usagePercentage": 23.45,
      "winRate": 58.97
    }
  ]
}

Server Implementation

Express.js Example:

const express = require('express');
const app = express();

app.use(express.json());

// Leaderboard endpoint
app.post('/api/leaderboard', (req, res) => {
  const { season, format, players } = req.body;
  // Store in database
  console.log(`Received ${players.length} players for ${season} ${format}`);
  res.json({ success: true });
});

// Match result endpoint
app.post('/api/match-result', (req, res) => {
  const { players } = req.body;
  // Store individual match
  console.log(`Match result: ${players[0].name} vs ${players[1].name}`);
  res.json({ success: true });
});

// Battle replay endpoint
app.post('/api/battle-replay', (req, res) => {
  const { battleLog, turnCount } = req.body;
  // Store replay log for playback
  console.log(`Replay: ${turnCount} turns`);
  res.json({ success: true });
});

// Usage stats endpoint
app.post('/api/usage-stats', (req, res) => {
  const { pokemon } = req.body;
  // Update usage statistics
  console.log(`Processed ${pokemon.length} species`);
  res.json({ success: true });
});

app.listen(3000);
NestJS Example (TypeScript)
import { Controller, Post, Body } from '@nestjs/common';

class LeaderboardPayload {
  serverId: string;
  season: string;
  format: string;
  players: Array<{
    uuid: string;
    name: string;
    elo: number;
  }>;
}

@Controller('api')
export class ApiController {
  @Post('leaderboard')
  async leaderboard(@Body() payload: LeaderboardPayload) {
    // Process leaderboard data
    return { success: true };
  }
}

Testing

Curl Commands:

# Test leaderboard
curl -X POST https://your-server.com/api/leaderboard \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-secret-key" \
  -d '{"serverId":"test","season":"Season 1","format":"singles","players":[]}'

# Test match result
curl -X POST https://your-server.com/api/match-result \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-secret-key" \
  -d '{"matchId":"test","players":[]}'

Security

⚠️ Production Requirements:

  • Use HTTPS only
  • Validate X-API-Key header
  • Rate limit requests (100 req/min recommended)
  • Sanitize all input data
  • Use read-only database accounts for queries
-- Create read-only database user
CREATE USER 'cobbleranked_web'@'%' IDENTIFIED BY 'secure_password';
GRANT SELECT ON cobbleranked.* TO 'cobbleranked_web'@'%';

See Also