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-keyheader - 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:
- The score is added to the user's current daily total (cumulative)
- If the daily total exceeds the user's best score, the best score is updated
- Rankings are automatically recalculated
Create Leaderboard
Create a new leaderboard for your app with custom configuration.
Endpoint
POST /leaderboards/:appId
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The 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
| Code | Description |
|---|---|
| 201 | Leaderboard created successfully |
| 400 | Invalid leaderboard data |
| 401 | Unauthorized |
| 404 | App does not exist |
Update Leaderboard
Update leaderboard configuration such as name, description, or reset schedule.
Endpoint
PUT /leaderboards/:appId
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The 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
| Code | Description |
|---|---|
| 200 | Leaderboard updated successfully |
| 400 | Invalid update data |
| 401 | Unauthorized |
| 404 | Leaderboard 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The unique application identifier |
| userId | string | Yes | The 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
| Code | Description |
|---|---|
| 200 | Score submitted successfully |
| 400 | Invalid score data |
| 401 | Unauthorized |
| 404 | Leaderboard 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The unique application identifier |
| userId | string | No | Include to get user's rank and entry |
| page | number | No | Page number (default: 1) |
| limit | number | No | Items 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The unique application identifier |
| userId | string | No | Include to get user's rank and entry |
| page | number | No | Page number (default: 1) |
| limit | number | No | Items 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The unique application identifier |
| userId | string | No | Include to get user's rank and entry |
| page | number | No | Page number (default: 1) |
| limit | number | No | Items 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The unique application identifier |
| userId | string | Yes | The 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The application identifier |
| isActive | boolean | No | Filter 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The 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
| Code | Description |
|---|---|
| 200 | Leaderboard reset successfully |
| 401 | Unauthorized |
| 404 | Leaderboard does not exist |
Delete Leaderboard
Permanently delete a leaderboard and all its entries.
Endpoint
DELETE /leaderboards/:appId
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | string | Yes | The unique application identifier |
Example
await leaderboardsApi.deleteLeaderboard({
appId: "app_123",
});
console.log("Leaderboard deleted successfully");
Response Codes
| Code | Description |
|---|---|
| 200 | Leaderboard deleted successfully |
| 401 | Unauthorized |
| 404 | Leaderboard 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
-
Choose Reset Frequency: Match your game's session length
- Mobile casual games: Daily resets
- Hardcore games: Weekly/monthly resets
- Event-based games: Custom schedules
-
Set Display Limits: Balance performance and user experience
- Display top 10-100 for leaderboards
- Show 3-5 nearby entries for context
-
Score Validation: Set min/max scores to prevent invalid entries
{
minScore: 0,
maxScore: 999999
} -
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
-
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 -
User Context: Always include userId for personalized views
const entries = await getDailyEntries({
appId,
userId: currentUserId, // Include for rank and position
limit: 50,
}); -
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 }); -
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
- Cache Leaderboard Data: Update periodically, not on every score
- Batch Score Submissions: Group multiple scores when possible
- Limit Request Frequency: Don't fetch leaderboard on every frame
- Use Pagination: Fetch only what you need to display
Engagement
- Visual Feedback: Celebrate rank improvements
- Social Features: Show friends' positions
- Rewards: Offer meaningful prizes for top ranks
- Fresh Starts: Regular resets give everyone a chance
- 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:
- Your existing scores will become the "daily" score
- The best score will be initialized from the highest daily score
- 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:
- Documentation: https://docs.getjar.com
- API Reference: https://sdk.getjar.com/api/docs
- Support: support@getjar.com