Leaderboard

API reference for leaderboard standings, graph data, challenge metadata, search, and pagination.

Edit this page View Markdown

The leaderboard worker calculates challenge values, team scores, global and division ranks, first bloods, and graph samples. These routes return its latest cached result.

Authentication is optional. Before the event starts, leaderboardRead can access standings and graphs, while challsRead can access challenge metadata.

V2 adds fields and search support. V1 remains available for existing clients.

Query behavior

/now, /with-graph, and /graph use limit and offset for pagination. limit must be at least 1, offset must be at least 0, and deployment settings define their maximum values.

division filters standings to a configured division. search is available on V2 /now and V2 /with-graph for fuzzy team name search. Search values are expected to be 2 to 100 characters.

Search requests are rate limited per IP address with burst 3 and refill window 3000 ms. If the bucket is exhausted, the route returns 429 badRateLimit with data.timeLeft.

V1 /api/v1/leaderboard/graph supports limit and optional division. Offset based graph pagination is available through the V2 graph route.

Ranking rules

The leaderboard worker sorts teams by score descending. Ties are broken in this order:

  1. Most recent solve of a challenge marked tiebreakEligible (last_tiebreak_solve_at). Earlier is better.
  2. Absolute last solve time. Earlier is better.

Banned teams

Banned teams are left out of leaderboard responses. Their cached rank fields are cleared until they are unbanned and the leaderboard is recalculated. If a banned team appears in a response, globalPlace is null.

GET Current standings

GET /api/[v2,v1]/leaderboard/now

Edit this page View Markdown
Auth
Public
Gate
Started (bypass leaderboardRead)
Permissions
No extra permissions
Captcha
No captcha
Rate limit
Search bucket per IP (burst 3, refill window 3000 ms) when search is provided.

Returns a page of the current standings from the leaderboard cache.

For new clients, prefer V2. It includes avatar, country, status, solve, dynamic score, division rank, and global rank fields. V1 remains available for older clients and returns the original id, name, and score fields.

V2 separates flag solves from external score-feed data. solves contains actual challenge solves. dynamicScores contains per-team points for dynamic challenges, plus the team’s point delta from the latest scoring tick for that challenge.

GET /api/v2/leaderboard/now supports limit, offset, optional division, and optional search. Deployment config sets the maximum pagination values, and search values are expected to be 2 to 100 characters.

Query parameters

limit*number
Maximum number of entries to return. Capped by deployment config.
offset*number
Number of leading entries to skip for pagination.
divisionstring
Restrict results to a single division.
searchstring
Filter teams by name substring (2-100 characters).

Response fields

totalnumber
Total number of ranked teams in the division.
leaderboard[].idstring
Team ID.
leaderboard[].namestring
Team display name.
leaderboard[].scorenumber
Total team score.
leaderboard[].avatarUrlstring | null
Team avatar URL, or null when unset.
leaderboard[].countryCodestring | null
ISO 3166-1 alpha-2 country code, or null when unset.
leaderboard[].statusTextstring | null
Free-form team status badge, or null when unset.
leaderboard[].solves[].idstring
Solved challenge ID.
leaderboard[].solves[].solveTimenumber
Solve time as a Unix timestamp in milliseconds.
leaderboard[].dynamicScores[].idstring
Dynamic challenge ID.
leaderboard[].dynamicScores[].pointsnumber
The team’s current points for this challenge.
leaderboard[].dynamicScores[].pointDeltanumber
The team’s point change from the latest scoring tick.
leaderboard[].divisionstring
Division the team competes in.
leaderboard[].divisionPlacenumber
The team’s rank within its division.
leaderboard[].globalPlacenumber | null
The team’s overall rank, or null when outside the ranked set.

globalPlace is null when a team is not included in global ranking. divisionPlace is the team’s rank within the returned division.

GET Standings with graph

GET /api/v2/leaderboard/with-graph

Edit this page View Markdown
Auth
Public
Gate
Started (bypass leaderboardRead)
Permissions
No extra permissions
Captcha
No captcha
Rate limit
Search bucket per IP (burst 3, refill window 3000 ms) when search is provided.

Returns the same standings data as current standings, along with graph samples. This is useful when a page renders the standings table and score graph together.

Supports limit, offset, optional division, and optional search. Deployment config sets the maximum pagination values, and search values are expected to be 2 to 100 characters.

Query parameters

limit*number
Maximum number of entries to return. Capped by deployment config.
offset*number
Number of leading entries to skip for pagination.
divisionstring
Restrict results to a single division.
searchstring
Filter teams by name substring (2-100 characters).
challengestring
Restrict results to teams that solved or scored in this challenge.

Response fields

totalnumber
Total number of ranked teams in the division.
leaderboard[].idstring
Team ID.
leaderboard[].namestring
Team display name.
leaderboard[].scorenumber
Total team score.
leaderboard[].avatarUrlstring | null
Team avatar URL, or null when unset.
leaderboard[].countryCodestring | null
ISO 3166-1 alpha-2 country code, or null when unset.
leaderboard[].statusTextstring | null
Free-form team status badge, or null when unset.
leaderboard[].solves[].idstring
Solved challenge ID.
leaderboard[].solves[].solveTimenumber
Solve time as a Unix timestamp in milliseconds.
leaderboard[].dynamicScores[].idstring
Dynamic challenge ID.
leaderboard[].dynamicScores[].pointsnumber
The team’s current points for this challenge.
leaderboard[].dynamicScores[].pointDeltanumber
The team’s point change from the latest scoring tick.
leaderboard[].divisionstring
Division the team competes in.
leaderboard[].divisionPlacenumber
The team’s rank within its division.
leaderboard[].globalPlacenumber | null
The team’s overall rank, or null when outside the ranked set.
graph[].points[].timenumber
Sample time as a Unix timestamp in milliseconds.
graph[].points[].scorenumber
Cumulative team score at this time.
graph[].dynamicPoints[].timenumber
Sample time as a Unix timestamp in milliseconds.
graph[].dynamicPoints[].scorenumber
Cumulative dynamic-scoring points at this time.
graph[].idstring
Team ID.
graph[].namestring
Team display name.

leaderboard[] uses the same fields as the V2 standings route. graph[] contains the teams included in the cached graph sample set.

GET Score graph

GET /api/[v2,v1]/leaderboard/graph

Edit this page View Markdown
Auth
Public
Gate
Started (bypass leaderboardRead)
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

Returns score graph data from the leaderboard cache.

The graph uses samples cached by the leaderboard worker. leaderboard.graphMaxTeams limits the number of teams in the graph, and leaderboard.graphSampleTime sets the interval between samples.

GET /api/v2/leaderboard/graph supports limit, offset, and optional division. Team name search is available on the standings routes.

Query parameters

limit*number
Maximum number of entries to return. Capped by deployment config.
offset*number
Number of leading entries to skip for pagination.
divisionstring
Restrict results to a single division.

Response fields

graph[].points[].timenumber
Sample time as a Unix timestamp in milliseconds.
graph[].points[].scorenumber
Cumulative team score at this time.
graph[].dynamicPoints[].timenumber
Sample time as a Unix timestamp in milliseconds.
graph[].dynamicPoints[].scorenumber
Cumulative dynamic-scoring points at this time.
graph[].idstring
Team ID.
graph[].namestring
Team display name.

GET Leaderboard challenges

GET /api/v2/leaderboard/challs

Edit this page View Markdown
Auth
Public
Gate
Started (bypass challsRead)
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

Returns challenge metadata for leaderboard visualizations.

This route uses the same CTF start time gate as the standings routes. Its optional bypass permission is challsRead instead of leaderboardRead.

Response fields

challengesRecord<string, { name: string, category: string, solves: number, points: number, sortWeight: number, scoringKind: "decay" | "dynamic", firstSolvers: object[] }>
Per-challenge solve stats keyed by challenge ID, including current points and first solvers.

data.challenges is keyed by challenge ID. firstSolvers contains up to the first three team IDs for each challenge.

Type to search.