Leaderboards

The leaderboard system is flexible and allows learning apps to define custom leaderboards based on their needs.

Features

  • Per-App Scoping: Use appId (e.g., trashcat, snake-wars) so each app has its own isolated leaderboard partition. Omit appId to query a cross-app leaderboard (rarely needed).
  • Categories & Subcategories: Use categoryId (e.g., skillId) to create leaderboards per skill, and optionally filter by subcategory (e.g., organizationId)
  • Flexible Units: Track any metric (points, seconds, stars, coins, etc.)
  • Sort Directions: Ascending (lower is better, for times) or descending (higher is better, for scores)
  • Monthly, Weekly & Global Rankings: Each leaderboard keeps a calendar-month board, a weekly board and an all-time board. Each board holds every player's best score for its period. Months start at 00:00 UTC on the 1st, and weeks start at 00:00 UTC on Monday.

List Available Leaderboards

const res = await fetch(`${API_URL}/miscellaneous/v1/leaderboards`, {
  headers: { Authorization: `Bearer ${idToken}` },
});
const { leaderboards } = await res.json();
// Returns: [{ id: 'practice-scores', name: 'Top Scores', unit: 'points', sortDirection: 'desc' }, ...]

Current leaderboards:

  • practice-scores: Top scores (points, higher is better)
  • competition-times: Best times (seconds, lower is better)

Additional leaderboards can be defined as needed. Contact trashcat@trilogy.com to request new leaderboard configurations.

Submit a Score

// Example: Submit competition time
const totalTimeSeconds = activePlayTime + penaltyTime;
await fetch(
  `${API_URL}/miscellaneous/v1/leaderboards/competition-times/scores?categoryId=${skillId}&subcategory=${organizationId}&appId=${appId}`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${idToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      score: Math.round(totalTimeSeconds),
    }),
  },
);

Always pass appId for in-game submissions so scores from different apps stay in separate leaderboards. Use the active app's identifier (e.g. trashcat, snake-wars, survivor-game).

Retrieve Rankings

Monthly Leaderboard

const monthlyRes = await fetch(
  `${API_URL}/miscellaneous/v1/leaderboards/competition-times/monthly/current?categoryId=${skillId}&subcategory=${organizationId}&appId=${appId}&limit=20`,
  {
    headers: { Authorization: `Bearer ${idToken}` },
  },
);
const { entries, current, totalCount, resetsAt } = await monthlyRes.json();

Pass current or a month's first day in UTC (2026-10-01) as the month. Any other value returns 400.

Weekly Leaderboard

const weeklyRes = await fetch(
  `${API_URL}/miscellaneous/v1/leaderboards/competition-times/weekly/current?categoryId=${skillId}&subcategory=${organizationId}&appId=${appId}&limit=20`,
  {
    headers: { Authorization: `Bearer ${idToken}` },
  },
);
const { entries, current, totalCount, resetsAt } = await weeklyRes.json();

Global Leaderboard

const globalRes = await fetch(
  `${API_URL}/miscellaneous/v1/leaderboards/competition-times/global?categoryId=${skillId}&subcategory=${organizationId}&appId=${appId}&limit=20`,
  {
    headers: { Authorization: `Bearer ${idToken}` },
  },
);
const { entries, current, totalCount } = await globalRes.json();

Response Format

All three endpoints return:

{
  "entries": [
    { "rank": 1, "score": 180, "playerName": "Alice", "unit": "seconds" },
    { "rank": 2, "score": 195, "playerName": "Bob", "unit": "seconds" },
    // ... up to limit
  ],
  "current": {
    "rank": 47,
    "score": 240,
    "playerName": "CurrentUser",
    "unit": "seconds"
  },
  "totalCount": 156,
  "resetsAt": "2026-11-01T00:00:00.000Z"
}

Key fields:

  • entries: Top performers (limited by limit parameter)
  • current: Current user's ranking (included even if outside top entries)
  • totalCount: Total participants in this leaderboard
  • resetsAt: Monthly and weekly boards only. The instant the board's period ends, when the next period starts with an empty board.

Leaderboard Scoping

Use appId, categoryId, and subcategory to create focused competitions:

Per-app competition (default for in-game submissions):

// Only Trashcat scores; Snake Wars / Survivor scores live in their own partitions
?categoryId=${skillId}&appId=trashcat

Cross-app competition (rare):

// All apps compete in the same bucket (omit appId)
?categoryId=${skillId}

School-level competition:

// Only students from the same organization compete, scoped to the active app
?categoryId=${skillId}&subcategory=${organizationId}&appId=${appId}

Skill-specific:

// Different leaderboards per skill
?categoryId=multiplication&appId=trashcat  // vs
?categoryId=division&appId=trashcat

Getting Started

Authentication setup required for accessing leaderboards and submitting scores.

Complete Example

Production code showing how to integrate leaderboard submissions into your learning session flow.

API Reference

Complete endpoint documentation for leaderboard operations, including all query parameters and response schemas.