Leaderboard Service API Reference
The Leaderboard Service manages user statistics aggregation and leaderboard generation across all platform integrations. It provides rankings by various metrics (LTZ earned, activity score, streak, claims) and time periods (week, month, all-time).
Base URL
https://services.loyalteez.app
Endpoints
Get Leaderboard
Retrieves a ranked leaderboard for a specific metric and period.
Endpoint: GET /leaderboard/:brandId?metric=ltz_earned&period=all&platform=all&limit=10
URL Parameters:
brandId(path, required): The brand's wallet addressmetric(query, optional): Metric to rank by. Default:"ltz_earned"ltz_earned: Total LTZ tokens earnedactivity: Activity score (calculated from LTZ, claims, streak)streak: Current streak lengthclaims: Total number of claims
period(query, optional): Time period. Default:"all"week: Weekly rankingsmonth: Monthly rankingsall: All-time rankings
platform(query, optional): Platform filter. Default:"all"discord,telegram,twitter,farcaster,shopify,wordpress,all
limit(query, optional): Number of results. Default:10
Response:
{
"success": true,
"leaderboard": [
{
"rank": 1,
"userIdentifier": "[email protected]",
"displayName": "TopUser",
"platform": "discord",
"value": 10000,
"formattedValue": "10,000 LTZ"
},
{
"rank": 2,
"userIdentifier": "[email protected]",
"displayName": "SecondPlace",
"platform": "telegram",
"value": 8500,
"formattedValue": "8,500 LTZ"
}
],
"metric": "ltz_earned",
"period": "all",
"platform": "all",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
Response Fields:
success: Whether the operation succeededleaderboard: Array of ranked entriesrank: Position in leaderboard (1-indexed)userIdentifier: User identifierdisplayName: Display name for the userplatform: User's platformvalue: Raw metric valueformattedValue: Human-readable formatted value
metric: Metric used for rankingperiod: Time period usedplatform: Platform filter usedupdatedAt: Timestamp of last update
Example:
const brandId = encodeURIComponent('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb');
const response = await fetch(
`https://services.loyalteez.app/leaderboard/${brandId}?metric=ltz_earned&period=week&platform=discord&limit=10`
);
const result = await response.json();
if (result.success) {
result.leaderboard.forEach(entry => {
console.log(`${entry.rank}. ${entry.displayName}: ${entry.formattedValue}`);
});
}
Update Stats
Updates user statistics after a reward is distributed. Call this after rewarding a user to keep leaderboard data current.
Endpoint: POST /leaderboard/update-stats
Request Body:
{
"brandId": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"userIdentifier": "[email protected]",
"platform": "discord",
"ltzAmount": 100,
"claimType": "daily_checkin",
"displayName": "Username"
}
Parameters:
brandId(required): The brand's wallet addressuserIdentifier(required): User identifierplatform(required): Platform nameltzAmount(required): LTZ amount earned (used for statistics)claimType(optional): Type of claim/eventdisplayName(optional): User's display name
Response:
{
"success": true,
"stats": {
"brand_id": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"user_identifier": "[email protected]",
"platform": "discord",
"display_name": "Username",
"ltz_earned_total": 10000,
"ltz_earned_week": 500,
"ltz_earned_month": 2000,
"activity_score": 2500,
"claims_count": 50,
"current_streak": 7,
"last_active_at": "2024-01-15T10:30:00.000Z",
"updated_at": "2024-01-15T10:30:00.000Z"
},
"newRank": 5
}
Response Fields:
success: Whether the operation succeededstats: Updated user statisticsltz_earned_total: Total LTZ earned (all-time)ltz_earned_week: LTZ earned this weekltz_earned_month: LTZ earned this monthactivity_score: Calculated activity scoreclaims_count: Total number of claimscurrent_streak: Current streak length
newRank: User's new rank in all-time leaderboard (if in top 1000)
Example:
async function updateUserStats(userIdentifier, platform, brandId, ltzAmount) {
const response = await fetch('https://services.loyalteez.app/leaderboard/update-stats', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
brandId,
userIdentifier,
platform,
ltzAmount,
claimType: 'daily_checkin',
displayName: 'Username'
})
});
const result = await response.json();
if (result.success) {
console.log(`Updated stats. New rank: ${result.newRank}`);
}
}
Metrics Explained
LTZ Earned (ltz_earned)
Total LTZ tokens earned by users. Available for all time periods (week, month, all).
Activity Score (activity)
Calculated score combining multiple factors:
- LTZ earned: 1 point per 10 LTZ
- Claims count: 5 points per claim
- Streak: 10 points per streak day
Formula: (ltz_earned_total / 10) + (claims_count * 5) + (current_streak * 10)
Streak (streak)
Current streak length. Updated when streak service records activity.
Claims (claims)
Total number of reward claims/events. Incremented each time /leaderboard/update-stats is called.
Time Periods
Week (week)
Monday to Sunday (starts on Monday). Resets weekly for weekly rankings.
Month (month)
First day of month to last day. Resets monthly for monthly rankings.
All (all)
All-time statistics since user's first activity. Never resets.
Activity Score Calculation
Activity scores are automatically calculated when stats are updated:
function calculateActivityScore(stats) {
const ltzScore = (stats.ltz_earned_total || 0) / 10; // 1 point per 10 LTZ
const claimsScore = (stats.claims_count || 0) * 5; // 5 points per claim
const streakScore = (stats.current_streak || 0) * 10; // 10 points per streak day
return Math.floor(ltzScore + claimsScore + streakScore);
}
This provides a balanced ranking that rewards:
- High earners (LTZ)
- Active participants (claims)
- Consistent users (streaks)
Integration Examples
Discord Bot - Leaderboard Command
async function handleLeaderboard(brandId, metric = 'ltz_earned', period = 'all') {
const response = await fetch(
`https://services.loyalteez.app/leaderboard/${encodeURIComponent(brandId)}?metric=${metric}&period=${period}&limit=10`
);
const result = await response.json();
if (!result.success) {
return 'Error fetching leaderboard';
}
let message = `🏆 Leaderboard (${metric}, ${period}):\n\n`;
result.leaderboard.forEach(entry => {
const medal = entry.rank === 1 ? '🥇' : entry.rank === 2 ? '🥈' : entry.rank === 3 ? '🥉' : `${entry.rank}.`;
message += `${medal} ${entry.displayName}: ${entry.formattedValue}\n`;
});
return message;
}
Update Stats After Reward
async function rewardUser(userIdentifier, platform, brandId, ltzAmount) {
// First, reward the user (via Event Handler API)
await fetch('https://api.loyalteez.app/loyalteez-api/manual-event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
brandId,
eventType: 'daily_checkin',
userEmail: userIdentifier,
metadata: { platform }
})
});
// Then, update leaderboard stats
await fetch('https://services.loyalteez.app/leaderboard/update-stats', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
brandId,
userIdentifier,
platform,
ltzAmount,
claimType: 'daily_checkin'
})
});
}
Web Application - Weekly Leaderboard
async function getWeeklyLeaderboard(brandId) {
const response = await fetch(
`https://services.loyalteez.app/leaderboard/${encodeURIComponent(brandId)}?metric=activity&period=week&limit=20`
);
const result = await response.json();
if (result.success) {
displayLeaderboard(result.leaderboard);
}
}
Best Practices
- Update Stats After Rewards: Always call
/leaderboard/update-statsafter distributing rewards - Use Appropriate Metrics: Choose the right metric for your use case:
ltz_earned: Best for showing top earnersactivity: Best for overall engagementstreak: Best for consistencyclaims: Best for participation
- Filter by Platform: Use platform filters to show platform-specific leaderboards
- Limit Results: Use appropriate limits (10-50) to avoid overwhelming users
- Cache Responses: Cache leaderboard data and refresh periodically (e.g., every 5 minutes)
- Handle Errors: Always handle errors gracefully and show user-friendly messages
Performance Considerations
- Leaderboard queries are optimized for performance
- Results are sorted by database indexes
- Consider caching responses for frequently accessed leaderboards
- Weekly/monthly periods reset automatically at period boundaries
- Limit results to reasonable numbers (10-50) for best performance