Console
Guide

Custom Leaderboards

Create multiple independent leaderboards per app — each with its own score name and sort direction. A soccer game can have "Goals" and "Assists" as separate boards; a racing game can rank by fastest lap time.

Concepts

FieldTypeDescription
slugstringUnique identifier within your app. Only [a-z0-9_-]. E.g. goals, fastest-lap
namestringDisplay name shown to users. E.g. "Top Scorers"
points_namestringLabel for the score unit. E.g. "Goals", "Kills", "ms"
sort_orderstringdesc — higher is better (default). asc — lower is better (e.g. lap times)

Authentication

Management calls (create, update, delete, score) require Authorization: AppID:AppKey. Read-only calls (list, ranking, user) also accept a user session token in the Authorization header — the response will include my_position automatically.

Endpoints overview

EndpointAction
POST /api/leaderboards/create.phpCreate a leaderboard
POST /api/leaderboards/update.phpUpdate name, points_name or sort_order
POST /api/leaderboards/delete.phpDelete a leaderboard and all its scores
POST /api/leaderboards/score.phpSet, add, reset or remove a user's score
POST /api/leaderboards/list.phpList all leaderboards for an app
POST /api/leaderboards/ranking.phpRanked entries for a leaderboard (global or friends)
POST /api/leaderboards/user.phpAll scores for a user across an app's leaderboards

1. Create a leaderboard

Do this once — from your backend or the Developer Console — before calling score.php from the game.

// Create leaderboard (one-time setup from server)
await fetch('https://vaneltonmedia.com/api/leaderboards/create.php', {
  method: 'POST',
  headers: { 'Authorization': `${APP_ID}:${APP_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ slug: 'goals', name: 'Top Scorers', points_name: 'Goals' })
});

// Ascending sort: lower score = better rank (e.g. lap time in ms)
await fetch('https://vaneltonmedia.com/api/leaderboards/create.php', {
  method: 'POST',
  headers: { 'Authorization': `${APP_ID}:${APP_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ slug: 'fastest_lap', name: 'Fastest Lap', points_name: 'ms', sort_order: 'asc' })
});
function createLeaderboard(string $slug, string $name, string $pointsName = 'Points', string $order = 'desc'): array {
    $ch = curl_init('https://vaneltonmedia.com/api/leaderboards/create.php');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => ['Authorization: ' . APP_ID . ':' . APP_KEY, 'Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode(['slug' => $slug, 'name' => $name, 'points_name' => $pointsName, 'sort_order' => $order]),
    ]);
    $r = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $r;
}
curl -s -X POST "https://vaneltonmedia.com/api/leaderboards/create.php" \
  -H "Authorization: $APP_ID:$APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug":"goals","name":"Top Scorers","points_name":"Goals"}'

2. Record a score

Call score.php from your game server. You can set an absolute value, add to the current score (use negative values to subtract), reset to zero, or remove the user entirely.

// Add 2 goals after a match
await fetch('https://vaneltonmedia.com/api/leaderboards/score.php', {
  method: 'POST',
  headers: { 'Authorization': `${APP_ID}:${APP_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ action: 'add', slug: 'goals', token: userToken, value: 2 })
});

// Set an absolute best lap time (player beat their record)
await fetch('https://vaneltonmedia.com/api/leaderboards/score.php', {
  method: 'POST',
  headers: { 'Authorization': `${APP_ID}:${APP_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ action: 'set', slug: 'fastest_lap', token: userToken, value: 87432 })
});
type ScoreAction = 'set' | 'add' | 'reset' | 'remove';

async function recordScore(slug: string, action: ScoreAction, userToken: string, value?: number) {
  const res = await fetch('https://vaneltonmedia.com/api/leaderboards/score.php', {
    method: 'POST',
    headers: { 'Authorization': `${APP_ID}:${APP_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ action, slug, token: userToken, ...(value !== undefined && { value }) })
  });
  return res.json() as Promise<{ success: boolean; new_score: number }>;
}
function recordScore(string $slug, string $action, string $userToken, ?int $value = null): array {
    $payload = ['action' => $action, 'slug' => $slug, 'token' => $userToken];
    if ($value !== null) $payload['value'] = $value;

    $ch = curl_init('https://vaneltonmedia.com/api/leaderboards/score.php');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => ['Authorization: ' . APP_ID . ':' . APP_KEY, 'Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode($payload),
    ]);
    $r = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $r; // $r['new_score'] has the updated value
}
import requests

HEADERS = {'Authorization': f'{APP_ID}:{APP_KEY}', 'Content-Type': 'application/json'}

def record_score(slug: str, action: str, user_token: str, value: int | None = None) -> dict:
    body = {'action': action, 'slug': slug, 'token': user_token}
    if value is not None:
        body['value'] = value
    return requests.post('https://vaneltonmedia.com/api/leaderboards/score.php', headers=HEADERS, json=body).json()

record_score('goals', 'add', user_token, value=2)    # add 2 goals
record_score('goals', 'add', user_token, value=-1)   # subtract 1 (own goal penalty)
using System.Net.Http;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"{APP_ID}:{APP_KEY}");

async Task RecordScore(string slug, string action, string userToken, int? value = null)
{
    var body = new System.Collections.Generic.Dictionary
    {
        { "action", action }, { "slug", slug }, { "token", userToken }
    };
    if (value.HasValue) body["value"] = value.Value;

    var payload = JsonSerializer.Serialize(body);
    await client.PostAsync(
        "https://vaneltonmedia.com/api/leaderboards/score.php",
        new StringContent(payload, Encoding.UTF8, "application/json"));
}

await RecordScore("goals", "add", userToken, value: 2);
var _headers = ds_map_create();
ds_map_add(_headers, "Authorization", global.app_id + ":" + global.app_key);
ds_map_add(_headers, "Content-Type",  "application/json");

// Add 2 goals after a match
global.score_req = http_request(
    "https://vaneltonmedia.com/api/leaderboards/score.php",
    "POST", _headers,
    json_stringify({ action: "add", slug: "goals", token: global.user_token, value: 2 })
);
ds_map_destroy(_headers);

// --- In Async HTTP Event ---
if (async_load[? "id"] == global.score_req) {
    var _d = json_parse(async_load[? "result"]);
    show_debug_message("New score: " + string(_d.new_score));
}

3. Display the ranking

Call ranking.php with a user token to get my_position even if the user is outside the top N.

// Global top 10 + my position
const res = await fetch('https://vaneltonmedia.com/api/leaderboards/ranking.php', {
  method: 'POST',
  headers: { 'Authorization': userToken, 'Content-Type': 'application/json' },
  body: JSON.stringify({ slug: 'goals', app_id: 'com.myapp.game', limit: 10 })
});
const { entries, my_position } = await res.json();
// entries[].rank, entries[].username, entries[].score, entries[].updated_at
// my_position.rank — always the global rank, even if using scope: 'friends'

// Friends-only leaderboard
const res2 = await fetch('https://vaneltonmedia.com/api/leaderboards/ranking.php', {
  method: 'POST',
  headers: { 'Authorization': userToken, 'Content-Type': 'application/json' },
  body: JSON.stringify({ slug: 'goals', app_id: 'com.myapp.game', scope: 'friends' })
});
user_headers = {'Authorization': user_token, 'Content-Type': 'application/json'}

r = requests.post('https://vaneltonmedia.com/api/leaderboards/ranking.php',
    headers=user_headers,
    json={'slug': 'goals', 'app_id': 'com.myapp.game', 'limit': 10}
).json()

for e in r['entries']:
    print(f"#{e['rank']} {e['username']} — {e['score']} {r['leaderboard']['points_name']}")

if r['my_position']:
    print(f"Your rank: #{r['my_position']['rank']}")
// Authenticate as user for my_position
using var userClient = new HttpClient();
userClient.DefaultRequestHeaders.Add("Authorization", userToken);

var payload = JsonSerializer.Serialize(new { slug = "goals", app_id = "com.myapp.game", limit = 10 });
var res = await userClient.PostAsync(
    "https://vaneltonmedia.com/api/leaderboards/ranking.php",
    new StringContent(payload, Encoding.UTF8, "application/json"));

using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var entries = doc.RootElement.GetProperty("entries");