Skip to content

Leaderboard API

Player rankings and statistics leaderboards.

Get Leaderboard

Retrieve player rankings based on various metrics.

Endpoint: GET /api/leaderboard?metric=balance&limit=10

Authentication: Required (JWT)

Query Parameters:

  • metric - Ranking metric (default: balance)
    • balance - Economy balance
    • level - Player level
    • playtime - Total playtime
    • kills - Mob/player kills
    • deaths - Deaths
    • job_level - Specific job level
  • limit - Number of entries (default: 10, max: 100)
  • jobName - Job name (required if metric is job_level)

Success Response (200):

json
{
  "success": true,
  "metric": "balance",
  "leaderboard": [
    {
      "rank": 1,
      "username": "Player123",
      "displayName": "Player123",
      "value": 50000.0,
      "formatted": "💰50,000.00"
    },
    {
      "rank": 2,
      "username": "Player456",
      "displayName": "Player456",
      "value": 45000.0,
      "formatted": "💰45,000.00"
    }
  ],
  "updatedAt": "2024-01-20T15:30:00Z"
}

Example - Balance Leaderboard:

bash
curl "http://localhost:8080/api/leaderboard?metric=balance&limit=10" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Example - Job Level Leaderboard:

bash
curl "http://localhost:8080/api/leaderboard?metric=job_level&jobName=Miner&limit=10" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Example - Playtime Leaderboard:

bash
curl "http://localhost:8080/api/leaderboard?metric=playtime&limit=20" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Available Metrics

Balance

Players ranked by economy balance.

Response Value: Currency amount (e.g., 50000.0)

Level

Players ranked by experience level.

Response Value: Player level (e.g., 35)

Playtime

Players ranked by total time played.

Response Value: Minutes played (e.g., 12000)

Formatted: "200 hours" or "50 days"

Kills

Players ranked by entity kills.

Response Value: Total kills (e.g., 1500)

Deaths

Players ranked by deaths (descending).

Response Value: Total deaths (e.g., 50)

Job Level

Players ranked by level in a specific job.

Response Value: Job level (e.g., 45)

Requires: jobName parameter


Code Examples

TypeScript

typescript
interface LeaderboardEntry {
  rank: number;
  username: string;
  displayName: string;
  value: number;
  formatted: string;
}

type LeaderboardMetric = 'balance' | 'level' | 'playtime' | 'kills' | 'deaths' | 'job_level';

class LeaderboardAPI {
  constructor(private baseUrl: string, private token: string) {}
  
  async getLeaderboard(
    metric: LeaderboardMetric,
    limit = 10,
    jobName?: string
  ): Promise<LeaderboardEntry[]> {
    const params = new URLSearchParams({
      metric,
      limit: limit.toString()
    });
    
    if (jobName && metric === 'job_level') {
      params.append('jobName', jobName);
    }
    
    const response = await fetch(
      `${this.baseUrl}/api/leaderboard?${params}`,
      {
        headers: { 'Authorization': `Bearer ${this.token}` }
      }
    );
    
    const data = await response.json();
    return data.leaderboard;
  }
  
  async getTopPlayers(limit = 10) {
    return this.getLeaderboard('balance', limit);
  }
  
  async getTopJobPlayers(jobName: string, limit = 10) {
    return this.getLeaderboard('job_level', limit, jobName);
  }
  
  async getPlayerRank(username: string, metric: LeaderboardMetric): Promise<number | null> {
    const leaderboard = await this.getLeaderboard(metric, 100);
    const entry = leaderboard.find(e => e.username === username);
    return entry?.rank ?? null;
  }
}

Python

python
class LeaderboardAPI:
    def __init__(self, base_url, token):
        self.base_url = base_url
        self.headers = {'Authorization': f'Bearer {token}'}
    
    def get_leaderboard(self, metric='balance', limit=10, job_name=None):
        params = {'metric': metric, 'limit': limit}
        if job_name and metric == 'job_level':
            params['jobName'] = job_name
        
        response = requests.get(
            f'{self.base_url}/api/leaderboard',
            headers=self.headers,
            params=params
        )
        return response.json()
    
    def get_top_players(self, limit=10):
        """Get top players by balance"""
        return self.get_leaderboard('balance', limit)
    
    def get_top_job_players(self, job_name, limit=10):
        """Get top players for a specific job"""
        return self.get_leaderboard('job_level', limit, job_name)
    
    def get_player_rank(self, username, metric='balance'):
        """Find a player's rank in the leaderboard"""
        data = self.get_leaderboard(metric, limit=100)
        for entry in data.get('leaderboard', []):
            if entry['username'] == username:
                return entry['rank']
        return None

React Component

tsx
import { useState, useEffect } from 'react';

interface LeaderboardEntry {
  rank: number;
  username: string;
  displayName: string;
  value: number;
  formatted: string;
}

export function Leaderboard({ metric = 'balance', limit = 10 }) {
  const [entries, setEntries] = useState<LeaderboardEntry[]>([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function fetchLeaderboard() {
      const token = localStorage.getItem('token');
      const response = await fetch(
        `http://localhost:8080/api/leaderboard?metric=${metric}&limit=${limit}`,
        {
          headers: { 'Authorization': `Bearer ${token}` }
        }
      );
      const data = await response.json();
      setEntries(data.leaderboard);
      setLoading(false);
    }
    
    fetchLeaderboard();
  }, [metric, limit]);
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="leaderboard">
      <h2>Top Players - {metric}</h2>
      <table>
        <thead>
          <tr>
            <th>Rank</th>
            <th>Player</th>
            <th>Value</th>
          </tr>
        </thead>
        <tbody>
          {entries.map(entry => (
            <tr key={entry.rank}>
              <td>{entry.rank}</td>
              <td>{entry.displayName}</td>
              <td>{entry.formatted}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Display Examples

Top 10 Richest Players

┌──────┬──────────────┬──────────────┐
│ Rank │ Player       │ Balance      │
├──────┼──────────────┼──────────────┤
│  1   │ Player123    │ 💰50,000.00  │
│  2   │ Player456    │ 💰45,000.00  │
│  3   │ Player789    │ 💰40,500.00  │
└──────┴──────────────┴──────────────┘

Top Job Performers

┌──────┬──────────────┬────────┐
│ Rank │ Player       │ Level  │
├──────┼──────────────┼────────┤
│  1   │ MinerPro     │   95   │
│  2   │ DigDugger    │   87   │
│  3   │ CaveExplorer │   82   │
└──────┴──────────────┴────────┘

Update Frequency

  • Leaderboards cached and updated every 5 minutes
  • Real-time updates for current player's rank
  • Manual refresh available via cache invalidation (admin)

Customization

Custom Metrics

Server owners can add custom metrics by:

  1. Implementing metric calculation in plugin
  2. Registering metric with leaderboard service
  3. Metric becomes available via API

Display Options

Consider these UI enhancements:

  • Medal icons for top 3 (🥇🥈🥉)
  • Highlight current player's position
  • Show percentile rank
  • Compare to friends
  • Historical rank changes

Pagination

For large leaderboards, use pagination:

typescript
async function getFullLeaderboard(api: LeaderboardAPI, metric: string) {
  const allEntries = [];
  let page = 1;
  const pageSize = 100;
  
  while (true) {
    const entries = await api.getLeaderboard(metric, pageSize);
    allEntries.push(...entries);
    
    if (entries.length < pageSize) break;
    page++;
  }
  
  return allEntries;
}

Notes

  • Leaderboards cached for performance
  • Minimum rank shown is #1
  • Tied values share the same rank
  • Inactive players (90+ days) may be excluded
  • Job leaderboards require Jobs plugin
  • Balance leaderboard requires Vault economy

Released under the MIT License.