Skip to main content

Leaderboards API

The Leaderboards API enables you to create competitive ranking systems for your games. Track player performance with daily scores and all-time bests, implement automatic resets, and drive player engagement through competition.

Authentication

Most endpoints require authentication using either:

  • API Key: Include x-api-key header
  • Cognito JWT: Include Authorization: Bearer <token> header
  • Developer Auth: Special authentication for developer operations

Public endpoints like getDailyEntries, getBestEntries, and getLeaderboardEntries don't require authentication.

Base URL

https://sdk.getjar.com/leaderboards

Core Concepts

Dual Score Tracking

The GetJar leaderboards system tracks two types of scores for each player:

  • Daily Score: Cumulative score that resets based on your schedule (daily, weekly, etc.)
  • Best Score: All-time best score that never resets

This dual tracking allows you to run daily competitions while maintaining historical records.

Score Submission

When you submit a score:

  1. The score is added to the user's current daily total (cumulative)
  2. If the daily total exceeds the user's best score, the best score is updated
  3. Rankings are automatically recalculated

Create Leaderboard

Create a new leaderboard for your app with custom configuration.

Endpoint

POST /leaderboards/:appId

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier

Request Body

{
name: string; // Required: Leaderboard name
description?: string; // Short description
type?: string; // 'global', 'regional', 'friends'
isActive?: boolean; // Default: true
displayTop?: number; // Number of top entries to show (default: 100)
displayNearby?: number; // Number of nearby entries (default: 5)
startAt?: string; // ISO date when leaderboard starts
endAt?: string; // ISO date when leaderboard ends
minScore?: number; // Minimum valid score
maxScore?: number; // Maximum valid score
platform?: string[]; // Platforms: ['web', 'ios', 'android']
resetSchedule?: { // Automatic reset configuration
type: string; // 'DAILY', 'WEEKLY', 'MONTHLY', 'CUSTOM'
resetAt?: string; // Time for reset (e.g., '00:00')
timezone?: string; // Timezone (default: 'UTC')
preserveHistory?: boolean; // Keep historical data
};
stats?: object; // Initial statistics
rewards?: Array<{ // Rank-based rewards
rank: number;
points?: number;
xp?: number;
currency?: number;
achievementIds?: string[];
}>;
}

Response

{
leaderboardId: string;
appId: string;
name: string;
description: string;
type: string;
displayTop: number;
displayNearby: number;
resetSchedule: object;
isActive: boolean;
createdAt: string;
}

Example

import { LeaderboardsApi } from "@getjar-iap/sdk";

const leaderboardsApi = new LeaderboardsApi(configuration);

const leaderboard = await leaderboardsApi.createLeaderboard({
appId: "app_123",
createLeaderboardDto: {
name: "Global High Scores",
description: "Compete for the top spot globally",
isActive: true,
displayTop: 100,
displayNearby: 5,
type: "global",
startAt: new Date().toISOString(),
platform: ["web", "ios", "android"],
maxScore: 999999,
minScore: 0,
resetSchedule: {
type: "DAILY",
resetAt: "00:00",
timezone: "UTC",
preserveHistory: true,
},
rewards: [
{ rank: 1, points: 1000, xp: 500, currency: 100 },
{ rank: 2, points: 750, xp: 350, currency: 75 },
{ rank: 3, points: 500, xp: 250, currency: 50 },
],
},
});

console.log("Leaderboard ID:", leaderboard.data.leaderboardId);

Response Codes

CodeDescription
201Leaderboard created successfully
400Invalid leaderboard data
401Unauthorized
404App does not exist

Update Leaderboard

Update leaderboard configuration such as name, description, or reset schedule.

Endpoint

PUT /leaderboards/:appId

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier

Request Body

{
name?: string;
description?: string;
displayTop?: number;
displayNearby?: number;
isActive?: boolean;
resetSchedule?: object;
rewards?: Array<object>;
}

Example

const updated = await leaderboardsApi.updateLeaderboard({
appId: "app_123",
updateLeaderboardDto: {
name: "Global Rankings - Updated",
description: "Updated leaderboard description",
displayTop: 150,
resetSchedule: {
type: "WEEKLY",
resetAt: "00:00",
timezone: "America/New_York",
},
},
});

console.log("Updated:", updated.data.name);

Response Codes

CodeDescription
200Leaderboard updated successfully
400Invalid update data
401Unauthorized
404Leaderboard does not exist

Submit Score

Submit a new score for a user. Daily scores are cumulative (added together), and the best score is automatically tracked.

Endpoint

POST /leaderboards/:appId/entries/:userId

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier
userIdstringYesThe unique user identifier

Request Body

{
score: number; // Required: Score to add to daily total
displayName?: string; // Display name on leaderboard
avatarUrl?: string; // URL to user avatar
metadata?: Record<string, any>; // Custom metadata
}

Response

{
leaderboardEntryId: string;
userId: string;
score: number; // Current daily score (cumulative)
bestScore: number; // All-time best score
rank: number; // Current daily rank
displayName: string;
avatarUrl: string | null;
lastSubmittedAt: string;
}

Example

const result = await leaderboardsApi.submitScore({
appId: "app_123",
userId: "user_456",
submitScoreRequest: {
score: 1500,
displayName: "ProGamer123",
avatarUrl: "https://cdn.example.com/avatar.png",
metadata: {
level: 42,
powerups: ["shield", "boost"],
},
},
});

console.log("Daily Score:", result.data.score);
console.log("Best Score:", result.data.bestScore);
console.log("Rank:", result.data.rank);

How Scores Work

// First submission
await submitScore({ score: 1000 });
// Result: dailyScore = 1000, bestScore = 1000

// Second submission (same day)
await submitScore({ score: 500 });
// Result: dailyScore = 1500, bestScore = 1500

// Third submission (same day)
await submitScore({ score: 200 });
// Result: dailyScore = 1700, bestScore = 1700

// After daily reset
await submitScore({ score: 800 });
// Result: dailyScore = 800, bestScore = 1700 (unchanged)

Response Codes

CodeDescription
200Score submitted successfully
400Invalid score data
401Unauthorized
404Leaderboard or user not found

Get Daily Entries

Get daily leaderboard entries (scores that reset based on schedule). This endpoint is public.

Endpoint

GET /leaderboards/:appId/entries/daily

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier
userIdstringNoInclude to get user's rank and entry
pagenumberNoPage number (default: 1)
limitnumberNoItems per page (default: 100, max: 100)

Response

{
items: Array<{
leaderboardEntryId: string;
userId: string;
score: number; // Current daily score
rank: number;
displayName: string;
avatarUrl: string | null;
lastSubmittedAt: string;
metadata: object;
}>;
pagination: {
total: number;
page: number;
limit: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
};
userEntry?: object; // If userId provided
userRank?: number; // If userId provided
}

Example

// Get top 50 daily entries
const dailyEntries = await leaderboardsApi.getDailyEntries({
appId: "app_123",
page: 1,
limit: 50,
});

console.log("Top player:", dailyEntries.data.items[0].displayName);
console.log("Total players:", dailyEntries.data.pagination.total);

// Get daily entries with user context
const withUser = await leaderboardsApi.getDailyEntries({
appId: "app_123",
userId: "user_456",
page: 1,
limit: 50,
});

console.log("Your rank:", withUser.data.userRank);
console.log("Your score:", withUser.data.userEntry?.score);

Get Best Entries

Get all-time best score entries (never reset). This endpoint is public.

Endpoint

GET /leaderboards/:appId/entries/best

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier
userIdstringNoInclude to get user's rank and entry
pagenumberNoPage number (default: 1)
limitnumberNoItems per page (default: 100)

Response

{
items: Array<{
leaderboardEntryId: string;
userId: string;
bestScore: number; // All-time best score
rank: number;
displayName: string;
avatarUrl: string | null;
firstSubmittedAt: string;
lastSubmittedAt: string;
}>;
pagination: object;
userEntry?: object;
userRank?: number;
}

Example

const bestEntries = await leaderboardsApi.getBestEntries({
appId: "app_123",
userId: "user_456",
page: 1,
limit: 100,
});

console.log("All-time champion:", bestEntries.data.items[0].displayName);
console.log("Best score:", bestEntries.data.items[0].bestScore);
console.log("Your best rank:", bestEntries.data.userRank);

Get Leaderboard Entries

Unified endpoint for getting leaderboard entries. Defaults to daily scores. This endpoint is public.

Endpoint

GET /leaderboards/:appId/entries

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier
userIdstringNoInclude to get user's rank and entry
pagenumberNoPage number (default: 1)
limitnumberNoItems per page (default: 100)

Example

const entries = await leaderboardsApi.getLeaderboardEntries({
appId: "app_123",
userId: "user_456",
});

console.log("Top players:", entries.data.items.slice(0, 10));
console.log("Your position:", entries.data.userRank);

Get User Entry

Get a specific user's leaderboard entry with both daily and best scores.

Endpoint

GET /leaderboards/:appId/entries/:userId

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier
userIdstringYesThe unique user identifier

Response

{
leaderboardEntryId: string;
userId: string;
score: number; // Current daily score
bestScore: number; // All-time best score
dailyScore: {
score: number;
lastUpdatedAt: string;
};
rank: number; // Current daily rank
displayName: string;
avatarUrl: string | null;
firstSubmittedAt: string;
lastSubmittedAt: string;
achievementsEarned: string[];
metadata: object;
createdAt: string;
updatedAt: string;
}

Example

const userEntry = await leaderboardsApi.getUserEntry({
appId: "app_123",
userId: "user_456",
});

console.log("Daily Score:", userEntry.data.score);
console.log("Best Score:", userEntry.data.bestScore);
console.log("Current Rank:", userEntry.data.rank);
console.log("Display Name:", userEntry.data.displayName);

Get Leaderboard Configuration

Retrieve detailed leaderboard configuration.

Endpoint

GET /leaderboards/:appId

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe application identifier
isActivebooleanNoFilter by active status

Response

{
leaderboardId: string;
appId: string;
name: string;
description: string;
category: string;
tags: string[];
type: string;
displayTop: number;
displayNearby: number;
minScore: number;
maxScore: number;
platform: string[];
resetSchedule: {
type: string;
resetAt: string;
timezone: string;
preserveHistory: boolean;
};
isActive: boolean;
startAt: string;
endAt: string;
stats: {
totalEntries: number;
uniquePlayers: number;
averageScore: number;
medianScore: number;
topScore: number;
lastUpdatedAt: string;
};
rewards: Array<object>;
createdAt: string;
updatedAt: string;
}

Example

const leaderboard = await leaderboardsApi.getLeaderboard({
appId: "app_123",
isActive: true,
});

console.log("Name:", leaderboard.data.name);
console.log("Total Players:", leaderboard.data.stats.uniquePlayers);
console.log("Top Score:", leaderboard.data.stats.topScore);

Reset Leaderboard

Manually trigger a leaderboard reset, clearing all daily entries.

Endpoint

POST /leaderboards/:appId/reset

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier

Response

{
success: boolean;
message: string;
resetAt: string;
}

Example

const result = await leaderboardsApi.resetLeaderboard({
appId: "app_123",
});

console.log(result.data.message); // "Leaderboard reset successfully"

Response Codes

CodeDescription
200Leaderboard reset successfully
401Unauthorized
404Leaderboard does not exist

Delete Leaderboard

Permanently delete a leaderboard and all its entries.

Endpoint

DELETE /leaderboards/:appId

Parameters

ParameterTypeRequiredDescription
appIdstringYesThe unique application identifier

Example

await leaderboardsApi.deleteLeaderboard({
appId: "app_123",
});

console.log("Leaderboard deleted successfully");

Response Codes

CodeDescription
200Leaderboard deleted successfully
401Unauthorized
404Leaderboard does not exist

Reset Schedules

Configure automatic leaderboard resets to maintain fresh competition.

Daily Reset

{
resetSchedule: {
type: "DAILY",
resetAt: "00:00",
timezone: "UTC",
preserveHistory: true
}
}

Resets at midnight every day in the specified timezone.

Weekly Reset

{
resetSchedule: {
type: "WEEKLY",
resetAt: "Monday 00:00",
timezone: "America/New_York",
preserveHistory: true
}
}

Resets at the start of each week.

Monthly Reset

{
resetSchedule: {
type: "MONTHLY",
resetAt: "1st 00:00",
timezone: "Europe/London",
preserveHistory: true
}
}

Resets on the first day of each month.

Custom Reset

{
resetSchedule: {
type: "CUSTOM",
resetAt: "2025-12-31 23:59:59",
timezone: "Asia/Tokyo",
preserveHistory: false
}
}

Resets at a specific date and time.


Complete Example

Here's a complete workflow for implementing leaderboards:

import { Configuration, LeaderboardsApi } from "@getjar-iap/sdk";

const configuration = new Configuration({
basePath: "https://sdk.getjar.com",
apiKey: "your-api-key",
});

const leaderboardsApi = new LeaderboardsApi(configuration);
const appId = "app_123";

async function setupLeaderboard() {
// 1. Create leaderboard
const leaderboard = await leaderboardsApi.createLeaderboard({
appId,
createLeaderboardDto: {
name: "Daily High Scores",
description: "Compete for daily supremacy",
isActive: true,
displayTop: 100,
displayNearby: 5,
type: "global",
platform: ["web"],
maxScore: 999999,
resetSchedule: {
type: "DAILY",
resetAt: "00:00",
timezone: "UTC",
preserveHistory: true,
},
rewards: [
{ rank: 1, points: 1000, xp: 500, currency: 100 },
{ rank: 2, points: 750, xp: 350, currency: 75 },
{ rank: 3, points: 500, xp: 250, currency: 50 },
],
},
});

console.log("✓ Leaderboard created:", leaderboard.data.leaderboardId);

// 2. Submit test scores
const testUsers = [
{ id: "user_1", name: "Alice", score: 5000 },
{ id: "user_2", name: "Bob", score: 4500 },
{ id: "user_3", name: "Charlie", score: 6000 },
];

for (const user of testUsers) {
await leaderboardsApi.submitScore({
appId,
userId: user.id,
submitScoreRequest: {
score: user.score,
displayName: user.name,
},
});
console.log(`✓ Score submitted for ${user.name}`);
}

// 3. Get daily leaderboard
const dailyEntries = await leaderboardsApi.getDailyEntries({
appId,
page: 1,
limit: 10,
});

console.log("\n📊 Daily Leaderboard:");
dailyEntries.data.items.forEach((entry, index) => {
console.log(
`${index + 1}. ${entry.displayName}: ${entry.score} points (Rank #${
entry.rank
})`
);
});

// 4. Get user-specific entry
const userEntry = await leaderboardsApi.getUserEntry({
appId,
userId: "user_1",
});

console.log("\n👤 User Stats:");
console.log("Daily Score:", userEntry.data.score);
console.log("Best Score:", userEntry.data.bestScore);
console.log("Current Rank:", userEntry.data.rank);

// 5. Get all-time best entries
const bestEntries = await leaderboardsApi.getBestEntries({
appId,
page: 1,
limit: 10,
});

console.log("\n🏆 All-Time Best:");
bestEntries.data.items.forEach((entry, index) => {
console.log(
`${index + 1}. ${entry.displayName}: ${entry.bestScore} points`
);
});

// 6. Get leaderboard configuration
const config = await leaderboardsApi.getLeaderboard({ appId });

console.log("\n⚙️ Leaderboard Config:");
console.log("Name:", config.data.name);
console.log("Total Players:", config.data.stats?.uniquePlayers || 0);
console.log("Total Entries:", config.data.stats?.totalEntries || 0);

return leaderboard;
}

// Run the setup
setupLeaderboard()
.then(() => console.log("\n✅ Setup complete!"))
.catch((error) => console.error("❌ Error:", error));

Real-Time Leaderboard Example

Here's how to implement a live leaderboard that updates as players compete:

async function liveLeaderboard(appId: string, userId: string) {
// Submit player's score
await leaderboardsApi.submitScore({
appId,
userId,
submitScoreRequest: {
score: 1250,
displayName: "CurrentPlayer",
},
});

// Get updated leaderboard with user context
const entries = await leaderboardsApi.getDailyEntries({
appId,
userId,
page: 1,
limit: 10,
});

// Display top 10
console.log("🏆 Top 10 Players:");
entries.data.items.forEach((entry) => {
const isCurrentUser = entry.userId === userId;
console.log(
`${entry.rank}. ${entry.displayName}: ${entry.score}${
isCurrentUser ? " ⭐ (You)" : ""
}`
);
});

// Show user's position
if (entries.data.userRank) {
console.log(`\nYour Rank: #${entries.data.userRank}`);
console.log(`Your Score: ${entries.data.userEntry?.score}`);
}

// Get nearby players
const nearbyStart = Math.max(1, entries.data.userRank - 2);
const nearby = await leaderboardsApi.getDailyEntries({
appId,
userId,
page: Math.ceil(nearbyStart / 10),
limit: 10,
});

console.log("\n👥 Players Near You:");
nearby.data.items
.filter(
(e) =>
e.rank >= entries.data.userRank - 2 &&
e.rank <= entries.data.userRank + 2
)
.forEach((entry) => {
const isCurrentUser = entry.userId === userId;
console.log(
`${entry.rank}. ${entry.displayName}: ${entry.score}${
isCurrentUser ? " ⭐" : ""
}`
);
});
}

Best Practices

Design

  1. Choose Reset Frequency: Match your game's session length

    • Mobile casual games: Daily resets
    • Hardcore games: Weekly/monthly resets
    • Event-based games: Custom schedules
  2. Set Display Limits: Balance performance and user experience

    • Display top 10-100 for leaderboards
    • Show 3-5 nearby entries for context
  3. Score Validation: Set min/max scores to prevent invalid entries

    {
    minScore: 0,
    maxScore: 999999
    }
  4. Reward Structure: Create compelling incentives

    rewards: [
    { rank: 1, points: 1000, currency: 500 },
    { rank: 2, points: 750, currency: 300 },
    { rank: 3, points: 500, currency: 200 },
    // Rewards for top 10
    { rank: 10, points: 100, currency: 50 },
    ];

Implementation

  1. Cumulative Scoring: Remember that daily scores add up

    // Game session 1: Player scores 1000
    await submitScore({ score: 1000 }); // Daily: 1000

    // Game session 2: Player scores 500
    await submitScore({ score: 500 }); // Daily: 1500

    // Game session 3: Player scores 750
    await submitScore({ score: 750 }); // Daily: 2250, Best: 2250
  2. User Context: Always include userId for personalized views

    const entries = await getDailyEntries({
    appId,
    userId: currentUserId, // Include for rank and position
    limit: 50,
    });
  3. Pagination: Use proper pagination for large leaderboards

    // Page 1
    const page1 = await getDailyEntries({ appId, page: 1, limit: 100 });

    // Page 2
    const page2 = await getDailyEntries({ appId, page: 2, limit: 100 });
  4. Error Handling: Handle edge cases gracefully

    try {
    await submitScore({ appId, userId, score: 1500 });
    } catch (error) {
    if (error.response?.status === 404) {
    console.error("Leaderboard not found");
    } else if (error.response?.status === 400) {
    console.error("Invalid score");
    }
    }

Performance

  1. Cache Leaderboard Data: Update periodically, not on every score
  2. Batch Score Submissions: Group multiple scores when possible
  3. Limit Request Frequency: Don't fetch leaderboard on every frame
  4. Use Pagination: Fetch only what you need to display

Engagement

  1. Visual Feedback: Celebrate rank improvements
  2. Social Features: Show friends' positions
  3. Rewards: Offer meaningful prizes for top ranks
  4. Fresh Starts: Regular resets give everyone a chance
  5. Historical Data: Preserve best scores for long-term goals

Common Patterns

Daily Competition + Hall of Fame

// Setup dual tracking
await createLeaderboard({
resetSchedule: {
type: "DAILY",
resetAt: "00:00",
timezone: "UTC",
},
});

// Show both daily and all-time
const daily = await getDailyEntries({ appId, limit: 10 });
const allTime = await getBestEntries({ appId, limit: 10 });

console.log("Today's Leaders:", daily.data.items);
console.log("Hall of Fame:", allTime.data.items);

Nearby Players View

async function showNearbyPlayers(appId: string, userId: string) {
const result = await leaderboardsApi.getDailyEntries({
appId,
userId,
limit: 100,
});

const userRank = result.data.userRank;
const nearby = result.data.items.filter(
(entry) => entry.rank >= userRank - 3 && entry.rank <= userRank + 3
);

return nearby;
}

Tournament Mode

// Create tournament leaderboard
await createLeaderboard({
name: "Weekend Tournament",
startAt: "2025-11-01T00:00:00Z",
endAt: "2025-11-03T23:59:59Z",
resetSchedule: {
type: "CUSTOM",
resetAt: "2025-11-03T23:59:59Z",
},
rewards: [
{ rank: 1, currency: 10000, achievementIds: ["TOURNAMENT_WINNER"] },
{ rank: 2, currency: 5000 },
{ rank: 3, currency: 2500 },
],
});

Error Handling

async function safeSubmitScore(appId: string, userId: string, score: number) {
try {
const result = await leaderboardsApi.submitScore({
appId,
userId,
submitScoreRequest: { score },
});

return {
success: true,
data: result.data,
};
} catch (error) {
if (error.response) {
switch (error.response.status) {
case 400:
console.error("Invalid score:", error.response.data.message);
break;
case 401:
console.error("Authentication required");
break;
case 404:
console.error("Leaderboard not found");
break;
default:
console.error("Unexpected error:", error.response.status);
}
}

return {
success: false,
error: error.message,
};
}
}

Migration Guide

From Single Score to Dual Tracking

If you're migrating from a system with only one score type:

  1. Your existing scores will become the "daily" score
  2. The best score will be initialized from the highest daily score
  3. No data loss occurs during migration

From Custom Leaderboard

// Old system: Manual tracking
const oldLeaderboard = await getOldScores();

// New system: Bulk import
for (const entry of oldLeaderboard) {
await leaderboardsApi.submitScore({
appId,
userId: entry.userId,
submitScoreRequest: {
score: entry.score,
displayName: entry.name,
avatarUrl: entry.avatar,
},
});
}

Support

For additional help or questions: