rCTF Docs
Overview

Leaderboard

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

Leaderboard routes read from cached standings produced by the leaderboard worker. The worker keeps the public scoreboard data up to date, including challenge scores, team scores, global ranks, division ranks, first bloods, and score graph samples.

Optional auth is accepted on these routes. When a token has the relevant bypass permission, the request can read leaderboard data before the CTF start time gate opens. Standings and graph routes use leaderboardRead for that bypass, while challenge metadata uses challsRead.

For new clients, prefer the V2 routes. V1 routes remain available for older clients and return a smaller set of fields, without search support.

Query behavior#

/now, /with-graph, and /graph use query string pagination. The API expects limit to be at least 1 and offset to be at least 0. Deployment config sets the maximum allowed 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.

This gives the worker a stable ordering when both scores and regular last-solve times match.

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

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 requirednumber
Maximum number of records to return.
offset requirednumber
Number of records to skip before returning results.
divisionstring
Division name or filter.
searchstring
Search string.

GET /api/v1/leaderboard/now supports limit, offset, and optional division. Team name search is available through the V2 route.

Query parameters

limit requirednumber
Maximum number of records to return.
offset requirednumber
Number of records to skip before returning results.
divisionstring
Division name or filter.

Response fields

totalnumber
Total number of matching records.
leaderboard[].idstring
Unique identifier.
leaderboard[].namestring
Display name.
leaderboard[].scorenumber
Current score.
leaderboard[].avatarUrlstring | null
Avatar image URL.
leaderboard[].countryCodestring | null
Country or region code.
leaderboard[].statusTextstring | null
Team status text.
leaderboard[].solves[].idstring
Unique identifier.
leaderboard[].solves[].solveTimenumber
Solve timestamp.
leaderboard[].dynamicScores[].idstring
Unique identifier.
leaderboard[].dynamicScores[].pointsnumber
Point value.
leaderboard[].dynamicScores[].pointDeltanumber
Returned leaderboard dynamic scores point delta value.
leaderboard[].divisionstring
Division name or filter.
leaderboard[].divisionPlacenumber
Division leaderboard rank.
leaderboard[].globalPlacenumber | null
Global leaderboard rank.

Response fields

totalnumber
Total number of matching records.
leaderboard[].idstring
Unique identifier.
leaderboard[].namestring
Display name.
leaderboard[].scorenumber
Current score.

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

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 requirednumber
Maximum number of records to return.
offset requirednumber
Number of records to skip before returning results.
divisionstring
Division name or filter.
searchstring
Search string.

Response fields

totalnumber
Total number of matching records.
leaderboard[].idstring
Unique identifier.
leaderboard[].namestring
Display name.
leaderboard[].scorenumber
Current score.
leaderboard[].avatarUrlstring | null
Avatar image URL.
leaderboard[].countryCodestring | null
Country or region code.
leaderboard[].statusTextstring | null
Team status text.
leaderboard[].solves[].idstring
Unique identifier.
leaderboard[].solves[].solveTimenumber
Solve timestamp.
leaderboard[].dynamicScores[].idstring
Unique identifier.
leaderboard[].dynamicScores[].pointsnumber
Point value.
leaderboard[].dynamicScores[].pointDeltanumber
Returned leaderboard dynamic scores point delta value.
leaderboard[].divisionstring
Division name or filter.
leaderboard[].divisionPlacenumber
Division leaderboard rank.
leaderboard[].globalPlacenumber | null
Global leaderboard rank.
graph[].points[].timenumber
Timestamp for the sample.
graph[].points[].scorenumber
Current score.
graph[].dynamicPoints[].timenumber
Timestamp for the sample.
graph[].dynamicPoints[].scorenumber
Current score.
graph[].idstring
Unique identifier.
graph[].namestring
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

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 the worker’s cached sample set. leaderboard.graphMaxTeams controls how many teams are kept for graphing, and leaderboard.graphSampleTime controls sample cadence.

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

Query parameters

limit requirednumber
Maximum number of records to return.
offset requirednumber
Number of records to skip before returning results.
divisionstring
Division name or filter.

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

Query parameters

limit requirednumber
Maximum number of records to return.
divisionstring
Division name or filter.

Response fields

graph[].points[].timenumber
Timestamp for the sample.
graph[].points[].scorenumber
Current score.
graph[].dynamicPoints[].timenumber
Timestamp for the sample.
graph[].dynamicPoints[].scorenumber
Current score.
graph[].idstring
Unique identifier.
graph[].namestring
Display name.

Response fields

graph[].points[].timenumber
Timestamp for the sample.
graph[].points[].scorenumber
Current score.
graph[].dynamicPoints[].timenumber
Timestamp for the sample.
graph[].dynamicPoints[].scorenumber
Current score.
graph[].idstring
Unique identifier.
graph[].namestring
Display name.

GET Leaderboard challenges

GET /api/v2/leaderboard/challs

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[] }>
Returned challenges value.

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

Esc

Start typing to search the docs.