Admin

Challenge management, uploads, teams, verifications, submissions, settings, and admin bot service routes.

Edit this page View Markdown

These admin API pages cover challenges, uploads, teams, email verification, submission logs, runtime settings, admin bot work, and external auth clients. Most user-facing routes require a user auth token with the listed permission bits. Admin bot service routes use the shared admin bot bearer token. See External auth for the user-facing half of the external auth flow.

Permissions, captcha actions, and rate limit conventions are documented in the API overview.

Permissions

Permission Used by
challsRead Reading admin challenge data, upload state, instancer schemas, admin bot status, and admin user solve history.
challsWrite Updating challenges and uploading files.
challsSolveWrite Removing solves.
usersWrite Listing and editing teams, creating team tokens, managing pending verifications, and reading submission audit rows.
settingsWrite Reading and updating runtime settings.

When a route lists more than one permission bit, the token needs every listed bit.

GET Admin challenge list

GET /api/[v2,v1]/admin/challs

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead
Captcha
No captcha
Rate limit
No rate limit

This route shows every challenge with admin fields. Hidden and unreleased challenges are included so the admin panel can display the full challenge set.

For new clients, V2 is usually the best fit. It includes hidden state, release time, instancer config, admin bot config, and file sizes. V1 remains available for older admin tooling.

Response fields

idstring
Challenge ID.
namestring
Challenge name.
descriptionstring
Challenge description in Markdown.
categorystring
Challenge category.
authorstring
Challenge author.
files[].namestring
File name.
files[].urlstring
File download URL.
files[].sizenumber | null
File size in bytes, or null when unknown.
points.minnumber
Minimum (floor) point value.
points.maxnumber
Maximum (initial) point value.
flagstring
The challenge flag.
tiebreakEligibleboolean
Whether solves count toward tiebreak ordering.
sortWeightnumber | null | undefined
Manual ordering weight, or null when unset.
tagsstring[] | null | undefined
Challenge tags, or null when unset.
instancerConfigobject | null | undefined
On-demand instancer (deployment) configuration, or null when the challenge has none.
adminBotConfigobject | null | undefined
Admin-bot configuration, or null when the challenge has no admin bot.
hiddenboolean
Whether the challenge is hidden from players.
releaseTimenumber | null | undefined
Scheduled release time as a Unix timestamp in milliseconds, or null.
scoring{ kind: "decay" } | { kind: "dynamic", source: object } | null | undefined
Scoring algorithm configuration, or null for the CTF default.
solveCountnumber | undefined
Total solves; only present on the single-challenge endpoint.

V1 challenge entries do not include hidden, releaseTime, instancerConfig, adminBotConfig, or file size.

GET Admin challenge detail

GET /api/[v2,v1]/admin/challs/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead
Captcha
No captcha
Rate limit
No rate limit

This route reads one challenge with admin fields. The admin panel uses it when someone opens the challenge editor.

For new clients, V2 is usually the best fit. V1 returns the older admin challenge fields and remains available for existing integrations.

Path parameters

id*string
Challenge ID.

Response fields

idstring
Challenge ID.
namestring
Challenge name.
descriptionstring
Challenge description in Markdown.
categorystring
Challenge category.
authorstring
Challenge author.
files[].namestring
File name.
files[].urlstring
File download URL.
files[].sizenumber | null
File size in bytes, or null when unknown.
points.minnumber
Minimum (floor) point value.
points.maxnumber
Maximum (initial) point value.
flagstring
The challenge flag.
tiebreakEligibleboolean
Whether solves count toward tiebreak ordering.
sortWeightnumber | null | undefined
Manual ordering weight, or null when unset.
tagsstring[] | null | undefined
Challenge tags, or null when unset.
instancerConfigobject | null | undefined
On-demand instancer (deployment) configuration, or null when the challenge has none.
adminBotConfigobject | null | undefined
Admin-bot configuration, or null when the challenge has no admin bot.
hiddenboolean
Whether the challenge is hidden from players.
releaseTimenumber | null | undefined
Scheduled release time as a Unix timestamp in milliseconds, or null.
scoring{ kind: "decay" } | { kind: "dynamic", source: object } | null | undefined
Scoring algorithm configuration, or null for the CTF default.
solveCountnumber | undefined
Total solves; only present on the single-challenge endpoint.

V1 challenge entries do not include hidden, releaseTime, instancerConfig, adminBotConfig, or file size.

PUT Create or update challenge

PUT /api/[v2,v1]/admin/challs/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsWrite
Captcha
No captcha
Rate limit
No rate limit

This route creates a new challenge or updates an existing one. Fields left out of data keep their current values. Setting instancerConfig or adminBotConfig to null clears that integration config.

After a challenge changes, the leaderboard is recalculated. The active instancer provider validates instancerConfig, and the active admin bot provider validates adminBotConfig before saving a new revision.

Path parameters

id*string
Challenge ID.

Request body

data*object
Challenge fields to update.
data.authorstring
Challenge author.
data.categorystring
Challenge category.
data.descriptionstring
Challenge description in Markdown.
data.flagstring
The challenge flag.
data.namestring
Challenge name.
data.points.min*number
Minimum (floor) point value.
data.points.max*number
Maximum (initial) point value.
data.tiebreakEligibleboolean
Whether solves count toward tiebreak ordering.
data.files[].name*string
File name.
data.files[].url*string
File download URL.
data.files[].size*number
File size in bytes, or null when unknown.
data.sortWeightnumber
Manual ordering weight.
data.tagsstring[]
Free-form tags.
data.instancerConfigobject
On-demand instancer configuration, or null to remove it.
data.instancerConfig.challengeIntegrationIdstring
Challenge integration ID this config belongs to.
data.instancerConfig.instancerstring
Instancer to use, or the default when omitted.
data.instancerConfig.configrecord
Provider-specific instancer configuration.
data.instancerConfig.expose[].kind*"tcp" | "tcp-ssl" | "http" | "https" | "raw"
Exposure protocol.
data.instancerConfig.expose[].hostPrefix*string
Subdomain/host prefix for the exposed service.
data.instancerConfig.expose[].containerName*string
Container to expose.
data.instancerConfig.expose[].containerPort*number
Container port to expose.
data.instancerConfig.expose[].shouldDisplayboolean
Whether to show the endpoint to players.
data.instancerConfig.expose[].titlestring
Short label for the endpoint, when present.
data.instancerConfig.timeoutMillisecondsnumber
Instance lifetime in milliseconds.
data.instancerConfig.extendableboolean
Whether the instance lifetime can be extended.
data.adminBotConfigobject
Admin-bot configuration, or null to remove it.
data.adminBotConfig.code*string
Admin-bot config code to attach.
data.hiddenboolean
Whether the challenge is hidden from players.
data.releaseTimenumber
Scheduled release time as a Unix ms timestamp, or null.
data.scoring{ kind: "decay" } | { kind: "dynamic", source: object }
Scoring algorithm configuration.

Response fields

idstring
Challenge ID.
namestring
Challenge name.
descriptionstring
Challenge description in Markdown.
categorystring
Challenge category.
authorstring
Challenge author.
files[].namestring
File name.
files[].urlstring
File download URL.
files[].sizenumber | null
File size in bytes, or null when unknown.
points.minnumber
Minimum (floor) point value.
points.maxnumber
Maximum (initial) point value.
flagstring
The challenge flag.
tiebreakEligibleboolean
Whether solves count toward tiebreak ordering.
sortWeightnumber | null | undefined
Manual ordering weight, or null when unset.
tagsstring[] | null | undefined
Challenge tags, or null when unset.
instancerConfigobject | null | undefined
On-demand instancer (deployment) configuration, or null when the challenge has none.
adminBotConfigobject | null | undefined
Admin-bot configuration, or null when the challenge has no admin bot.
hiddenboolean
Whether the challenge is hidden from players.
releaseTimenumber | null | undefined
Scheduled release time as a Unix timestamp in milliseconds, or null.
scoring{ kind: "decay" } | { kind: "dynamic", source: object } | null | undefined
Scoring algorithm configuration, or null for the CTF default.
solveCountnumber | undefined
Total solves; only present on the single-challenge endpoint.

V1 does not include hidden state, scheduled release time, instancer config, admin bot config, or file sizes.

DELETE Delete a solve

DELETE /api/v2/admin/challs/:challengeId/solves/:userId

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsSolveWrite
Captcha
No captcha
Rate limit
No rate limit

This route removes one solve for a specific team and challenge. The challenge and team remain in place, and the leaderboard is recalculated after the solve is removed.

Path parameters

challengeId*string
Challenge ID.
userId*string
Team ID whose solve to delete.

Response

A successful request returns 200 goodChallengeSolveDeleteV2 with no data.

DELETE Delete a challenge

DELETE /api/v1/admin/challs/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsWrite
Captcha
No captcha
Rate limit
No rate limit

This legacy V1 route removes a challenge and its solves. There is no V2 delete route. V2 admin clients remove solves directly and update challenge data through create or update challenge.

The leaderboard is recalculated after the challenge is removed.

Path parameters

id*string
Challenge ID.

Response

A successful request returns 200 goodChallengeDelete with no data.

POST Upload files

POST /api/[v2,v1]/admin/upload

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsWrite
Captcha
No captcha
Rate limit
No rate limit

This route uploads challenge files. Files are deduplicated by SHA 256, so uploading the same bytes again returns the existing URL.

V2 sends multipart/form-data with one or more files fields. V1 accepts JSON with base64 data URIs and remains available for older admin clients.

Request body

files*custom[]
One or more files to upload (multipart form-data).

Response fields

namestring
File name.
urlstring
File download URL.
sizenumber | null
File size in bytes, or null when unknown.

V1 upload responses do not include file size.

POST Query uploads

POST /api/[v2,v1]/admin/upload/query

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead
Captcha
No captcha
Rate limit
No rate limit

This route lets the admin panel check whether files already exist by SHA 256 before uploading them. A null URL means the file has not been uploaded yet.

For new clients, V2 is usually the best fit because it also returns nullable file size. V1 only returns the nullable URL.

Request body

uploads[].sha256*string
SHA-256 hex digest of the file contents.
uploads[].name*string
Upload file name (1-255 chars, no path separators).

Response fields

sha256string
SHA-256 hash of the file contents.
namestring
File name.
urlstring | null
Download URL, or null when not uploaded.
sizenumber | null
File size in bytes, or null when unknown.

GET List teams

GET /api/v2/admin/users

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route shows teams for the admin panel. Query parameters control pagination, sorting, and name or email search.

Filter teams is available when a UI also needs status or division filters.

Query parameters

limit*number
Integer 1-100. Page size.
offset*number
Integer >= 0. Page offset.
searchstring
Free-text search over team name and email.
sortBy"createdAt" | "team" | "email" | "division" | "score" | "solves" | "status"
Column to sort by.
sortOrder"asc" | "desc"
Sort direction.

Response fields

totalnumber
Total number of teams matching the query.
users[].idstring
Team ID.
users[].namestring
Team name.
users[].emailstring | null
Team email, or null when unset.
users[].divisionstring
Team’s division.
users[].permsnumber
Permission bitmask.
users[].bannedboolean
Whether the team is banned.
users[].scorenumber
Team’s current score.
users[].solveCountnumber
Number of challenges solved.
users[].avatarUrlstring | null
Avatar URL, or null when unset.
users[].countryCodestring | null
ISO 3166-1 alpha-2 country code, or null.
users[].statusTextstring | null
Free-form status badge, or null.
users[].createdAtstring
Account creation time as an ISO 8601 string.

Each list item includes public profile fields, admin state, cached score data, and creation time.

POST Filter teams

POST /api/v2/admin/users

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route lists teams with richer filters than the GET route can carry comfortably. The query string still controls pagination, sorting, and text search.

Filter objects can include nullable include and exclude arrays. A field can be omitted when that filter does not apply.

Query parameters

limit*number
Integer 1-100. Page size.
offset*number
Integer >= 0. Page offset.
searchstring
Free-text search over team name and email.
sortBy"createdAt" | "team" | "email" | "division" | "score" | "solves" | "status"
Column to sort by.
sortOrder"asc" | "desc"
Sort direction.

Request body

statusobject
Include/exclude filter on team status.
status.include("active" | "banned" | "admin")[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
status.exclude("active" | "banned" | "admin")[]
Drop rows matching any of these values; omitted or null applies no denylist.
divisionobject
Include/exclude filter on division.
division.includestring[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
division.excludestring[]
Drop rows matching any of these values; omitted or null applies no denylist.

Response fields

totalnumber
Total number of teams matching the query.
users[].idstring
Team ID.
users[].namestring
Team name.
users[].emailstring | null
Team email, or null when unset.
users[].divisionstring
Team’s division.
users[].permsnumber
Permission bitmask.
users[].bannedboolean
Whether the team is banned.
users[].scorenumber
Team’s current score.
users[].solveCountnumber
Number of challenges solved.
users[].avatarUrlstring | null
Avatar URL, or null when unset.
users[].countryCodestring | null
ISO 3166-1 alpha-2 country code, or null.
users[].statusTextstring | null
Free-form status badge, or null.
users[].createdAtstring
Account creation time as an ISO 8601 string.

GET Team detail

GET /api/v2/admin/users/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead, usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route shows full team details for the admin panel, including solve history. The token needs both usersWrite and challsRead because the response includes challenge solve data.

Path parameters

id*string
Team ID.

Response fields

idstring
Team ID.
namestring
Team name.
emailstring | null
Team email, or null when unset.
divisionstring
Team’s division.
permsnumber
Permission bitmask.
bannedboolean
Whether the team is banned.
scorenumber
Team’s current score.
solveCountnumber
Number of challenges solved.
avatarUrlstring | null
Avatar URL, or null when unset.
countryCodestring | null
ISO 3166-1 alpha-2 country code, or null.
statusTextstring | null
Free-form status badge, or null.
createdAtstring
Account creation time as an ISO 8601 string.
solves[].challengeIdstring
Challenge ID.
solves[].challengeNamestring
Challenge name.
solves[].challengeCategorystring
Challenge category.
solves[].createdAtstring
Solve time as an ISO 8601 string.

Private account fields appear here, so this route belongs in trusted admin views.

PUT Update team

PUT /api/v2/admin/users/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route changes admin controlled team state. The current route supports banning and unbanning teams.

Banning removes the team from leaderboard output after recalculation without deleting solves. Unbanning restores its rank after the leaderboard is recalculated. Admin users are protected from changes through this route.

Path parameters

id*string
Team ID.

Request body

data*object
Team fields to update.
data.bannedboolean
Whether the team is banned.
data.namestring
2-64 printable ASCII characters.
data.divisionstring
Team’s division.
data.countryCode"AF" | "AX" | "AL" | "DZ" | "AS" | "AD" | "AO" | "AI" | "AQ" | "AG" | "AR" | "AM" | "AW" | "AC" | "AU" | "AT" | "AZ" | "BS" | "BH" | "BD" | "BB" | "BY" | "BE" | "BZ" | "BJ" | "BM" | "BT" | "BO" | "BA" | "BW" | "BV" | "BR" | "IO" | "VG" | "BN" | "BG" | "BF" | "BI" | "KH" | "CM" | "CA" | "IC" | "CV" | "BQ" | "KY" | "CF" | "EA" | "TD" | "CL" | "CN" | "CX" | "CP" | "CC" | "CO" | "KM" | "CK" | "CR" | "HR" | "CU" | "CW" | "CY" | "CZ" | "DK" | "DG" | "DJ" | "DM" | "DO" | "CD" | "EC" | "EG" | "SV" | "GQ" | "ER" | "EE" | "SZ" | "ET" | "EU" | "FK" | "FO" | "FJ" | "FI" | "FR" | "GF" | "PF" | "TF" | "GA" | "GM" | "GE" | "DE" | "GH" | "GI" | "GR" | "GL" | "GD" | "GP" | "GU" | "GT" | "GG" | "GN" | "GW" | "GY" | "HT" | "HM" | "HN" | "HK" | "HU" | "IS" | "IN" | "ID" | "IR" | "IQ" | "IE" | "CQ" | "IM" | "IL" | "IT" | "CI" | "JM" | "JP" | "JE" | "JO" | "KZ" | "KE" | "KI" | "XK" | "KW" | "KG" | "LA" | "LV" | "LB" | "LS" | "LR" | "LY" | "LI" | "LT" | "LU" | "MO" | "MG" | "MW" | "MY" | "MV" | "ML" | "MT" | "MH" | "MQ" | "MR" | "MU" | "YT" | "MX" | "FM" | "MD" | "MC" | "MN" | "ME" | "MS" | "MA" | "MZ" | "MM" | "NA" | "NR" | "NP" | "NL" | "NC" | "NZ" | "NI" | "NE" | "NG" | "NU" | "NF" | "KP" | "MK" | "MP" | "NO" | "OM" | "PK" | "PW" | "PS" | "PA" | "PG" | "PY" | "PE" | "PH" | "PN" | "PL" | "PT" | "PR" | "QA" | "CG" | "RE" | "RO" | "RU" | "RW" | "BL" | "SH" | "KN" | "LC" | "MF" | "PM" | "VC" | "WS" | "SM" | "ST" | "SA" | "SN" | "RS" | "SC" | "SL" | "SG" | "SX" | "SK" | "SI" | "SB" | "SO" | "ZA" | "GS" | "KR" | "SS" | "ES" | "LK" | "SD" | "SR" | "SJ" | "SE" | "CH" | "SY" | "TW" | "TJ" | "TZ" | "TH" | "TL" | "TG" | "TK" | "TO" | "TT" | "TA" | "TN" | "TR" | "TM" | "TC" | "TV" | "UG" | "UA" | "AE" | "GB" | "UN" | "US" | "UM" | "VI" | "UY" | "UZ" | "VU" | "VA" | "VE" | "VN" | "WF" | "EH" | "YE" | "ZM" | "ZW"
ISO 3166-1 alpha-2 country code, or null.
data.statusTextstring
Free-form status badge, or null.

Response

A successful request returns 200 goodAdminUserUpdateV2 with no data.

DELETE Delete team

DELETE /api/v2/admin/users/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route deletes a team account. Admin users are protected from deletion here.

This operation removes the team rather than hiding it from standings. Update team is the better fit when the intent is to ban a team while keeping its record.

Path parameters

id*string
Team ID.

Response

A successful request returns 200 goodAdminUserDeleteV2 with no data.

POST Create team token

POST /api/v2/admin/users/:id/token

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route creates a fresh team token. It helps organizers recover access for a team that has lost its original credential.

The returned token is a credential. This route does not issue tokens for admin users.

Path parameters

id*string
Team ID.

Response fields

tokenstring
Newly created user API token.

GET Pending verifications

GET /api/v2/admin/user-verifications

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route shows teams that registered with email verification but have not completed the verification step yet.

Pending verification rows include the team name, email, division, creation time, and expiration time. Admins can complete or resend these rows through the related endpoints.

Response fields

verifications[].idstring
Pending verification ID.
verifications[].namestring
Team name.
verifications[].emailstring
Team email.
verifications[].divisionstring
Team’s division.
verifications[].createdAtnumber
Creation time as a Unix timestamp in milliseconds.
verifications[].expiresAtnumber
Expiry time as a Unix timestamp in milliseconds.

POST Complete verification

POST /api/v2/admin/user-verifications/:id/complete

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route lets an organizer complete a pending email verification manually and create the team. It is useful when the registration has already been verified outside the normal email flow.

Duplicate email and duplicate name checks still apply.

Path parameters

id*string
Pending verification ID.

Response fields

userIdstring
ID of the verified team.

POST Resend verification

POST /api/v2/admin/user-verifications/:id/resend

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route sends another verification email for an outstanding pending verification row.

If email delivery is not configured, the route returns 404 badEndpoint because there is no provider available to send the message.

Path parameters

id*string
Pending verification ID.

Response fields

idstring
ID of the pending verification.

GET List submissions

GET /api/v2/admin/submissions

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead, usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route shows submission audit rows. The table records flag submissions and admin bot job submissions for review and abuse investigation.

Query parameters control pagination, sorting, and lightweight challenge or team search. Filter submissions adds category, division, kind, result, team status, and time filters.

Query parameters

limit*number
Integer 1-100. Page size.
offset*number
Integer >= 0. Page offset.
sortBy"createdAt" | "challenge" | "team" | "ip" | "kind" | "result"
Column to sort by.
sortOrder"asc" | "desc"
Sort direction.
challengeSearchstring
Free-text search over challenge name.
teamSearchstring
Free-text search over team name.

Response fields

totalnumber
Total number of submissions matching the query.
submissions[].idstring
Submission ID.
submissions[].kind"flag" | "admin_bot"
Kind of submission.
submissions[].challengeIdstring
Challenge ID.
submissions[].challengeNamestring
Challenge name.
submissions[].challengeCategorystring
Challenge category.
submissions[].userIdstring
Submitting team ID.
submissions[].userNamestring
Submitting team name.
submissions[].userDivisionstring
Submitting team division.
submissions[].userAvatarUrlstring | null
Team avatar URL, or null when unset.
submissions[].userCountryCodestring | null
ISO 3166-1 alpha-2 country code, or null.
submissions[].userStatusTextstring | null
Free-form status badge, or null.
submissions[].userBannedboolean
Whether the team is banned.
submissions[].ipstring
Source IP of the submission.
submissions[].result"correct" | "incorrect" | "already_solved" | "queued" | "active_job" | "invalid_input" | "bad_instancer_state"
Outcome of the submission.
submissions[].detailsRecord<string, any>
Result-specific details.
submissions[].relatedIdstring | null
Related entity ID (e.g. admin-bot job), or null.
submissions[].createdAtstring
Submission time as an ISO 8601 string.

Each row includes challenge metadata, team metadata, the source IP address, result details, and creation time.

POST Filter submissions

POST /api/v2/admin/submissions

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead, usersWrite
Captcha
No captcha
Rate limit
No rate limit

This route lists submission audit rows with richer filters. The query string still controls pagination, sorting, and lightweight text search.

Filter objects can include nullable include and exclude arrays. Date filters are parsed as ISO timestamps, and createdAfter needs to be earlier than createdBefore when both are provided.

Query parameters

limit*number
Integer 1-100. Page size.
offset*number
Integer >= 0. Page offset.
sortBy"createdAt" | "challenge" | "team" | "ip" | "kind" | "result"
Column to sort by.
sortOrder"asc" | "desc"
Sort direction.
challengeSearchstring
Free-text search over challenge name.
teamSearchstring
Free-text search over team name.

Request body

challengeobject
Include/exclude filter on challenge ID.
challenge.includestring[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
challenge.excludestring[]
Drop rows matching any of these values; omitted or null applies no denylist.
teamobject
Include/exclude filter on team ID.
team.includestring[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
team.excludestring[]
Drop rows matching any of these values; omitted or null applies no denylist.
kindobject
Include/exclude filter on submission kind.
kind.include("flag" | "admin_bot")[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
kind.exclude("flag" | "admin_bot")[]
Drop rows matching any of these values; omitted or null applies no denylist.
resultobject
Include/exclude filter on result.
result.include("correct" | "incorrect" | "already_solved" | "queued" | "active_job" | "invalid_input" | "bad_instancer_state")[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
result.exclude("correct" | "incorrect" | "already_solved" | "queued" | "active_job" | "invalid_input" | "bad_instancer_state")[]
Drop rows matching any of these values; omitted or null applies no denylist.
teamStatusobject
Include/exclude filter on team ban status.
teamStatus.include("banned" | "not_banned")[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
teamStatus.exclude("banned" | "not_banned")[]
Drop rows matching any of these values; omitted or null applies no denylist.
categoryobject
Include/exclude filter on category.
category.includestring[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
category.excludestring[]
Drop rows matching any of these values; omitted or null applies no denylist.
divisionobject
Include/exclude filter on division.
division.includestring[]
Keep only rows matching one of these values; omitted or null applies no allowlist.
division.excludestring[]
Drop rows matching any of these values; omitted or null applies no denylist.
createdAfterstring
Only include submissions at or after this date.
createdBeforestring
Only include submissions at or before this date.

Response fields

totalnumber
Total number of submissions matching the query.
submissions[].idstring
Submission ID.
submissions[].kind"flag" | "admin_bot"
Kind of submission.
submissions[].challengeIdstring
Challenge ID.
submissions[].challengeNamestring
Challenge name.
submissions[].challengeCategorystring
Challenge category.
submissions[].userIdstring
Submitting team ID.
submissions[].userNamestring
Submitting team name.
submissions[].userDivisionstring
Submitting team division.
submissions[].userAvatarUrlstring | null
Team avatar URL, or null when unset.
submissions[].userCountryCodestring | null
ISO 3166-1 alpha-2 country code, or null.
submissions[].userStatusTextstring | null
Free-form status badge, or null.
submissions[].userBannedboolean
Whether the team is banned.
submissions[].ipstring
Source IP of the submission.
submissions[].result"correct" | "incorrect" | "already_solved" | "queued" | "active_job" | "invalid_input" | "bad_instancer_state"
Outcome of the submission.
submissions[].detailsRecord<string, any>
Result-specific details.
submissions[].relatedIdstring | null
Related entity ID (e.g. admin-bot job), or null.
submissions[].createdAtstring
Submission time as an ISO 8601 string.

GET Read settings

GET /api/v2/admin/settings

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
settingsWrite
Captcha
No captcha
Rate limit
No rate limit

This route shows runtime setting overrides and config defaults side by side. Runtime overrides come from the database. Defaults come from config files and environment variables.

Update settings can change or clear runtime overrides without restarting the server.

Response fields

overridesobject
Operator-set values overriding the defaults.
overrides.ctfNamestring | undefined
Name of the CTF.
overrides.homeContentstring | undefined
Markdown content shown on the home page.
overrides.startTimenumber | undefined
CTF start time as a Unix timestamp in milliseconds.
overrides.endTimenumber | undefined
CTF end time as a Unix timestamp in milliseconds.
overrides.sponsors[].namestring
Sponsor name.
overrides.sponsors[].iconstring | undefined
Deprecated. Mirrors iconLight (or iconDark when only it is set) for old clients.
overrides.sponsors[].iconLightstring | undefined
Sponsor icon URL for light mode; may be absent on data from older versions.
overrides.sponsors[].iconDarkstring | undefined
Sponsor icon URL for dark mode; may be absent on data from older versions.
overrides.sponsors[].descriptionstring
Sponsor description.
overrides.sponsors[].urlstring | undefined
Sponsor link, when present.
overrides.meta.descriptionstring | undefined
Meta description used for link previews.
overrides.meta.imageUrlstring | undefined
Preview image URL.
overrides.faviconUrlstring | undefined
Favicon URL.
overrides.logoLightUrlstring | undefined
Light-theme logo URL.
overrides.logoDarkUrlstring | undefined
Dark-theme logo URL.
defaultsobject
Built-in default values.
defaults.ctfNamestring | undefined
Name of the CTF.
defaults.homeContentstring | undefined
Markdown content shown on the home page.
defaults.startTimenumber | undefined
CTF start time as a Unix timestamp in milliseconds.
defaults.endTimenumber | undefined
CTF end time as a Unix timestamp in milliseconds.
defaults.sponsors[].namestring
Sponsor name.
defaults.sponsors[].iconstring | undefined
Deprecated. Mirrors iconLight (or iconDark when only it is set) for old clients.
defaults.sponsors[].iconLightstring | undefined
Sponsor icon URL for light mode; may be absent on data from older versions.
defaults.sponsors[].iconDarkstring | undefined
Sponsor icon URL for dark mode; may be absent on data from older versions.
defaults.sponsors[].descriptionstring
Sponsor description.
defaults.sponsors[].urlstring | undefined
Sponsor link, when present.
defaults.meta.descriptionstring | undefined
Meta description used for link previews.
defaults.meta.imageUrlstring | undefined
Preview image URL.
defaults.faviconUrlstring | undefined
Favicon URL.
defaults.logoLightUrlstring | undefined
Light-theme logo URL.
defaults.logoDarkUrlstring | undefined
Dark-theme logo URL.

PUT Update settings

PUT /api/v2/admin/settings

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
settingsWrite
Captcha
No captcha
Rate limit
No rate limit

This route updates runtime setting overrides. A nullable field set to null clears that override and lets the configured default take effect again.

Changing startTime or endTime also queues a leaderboard recalculation so standings match the updated event window.

Request body

data*object
Settings fields to update.
data.ctfNamestring
Public CTF name.
data.homeContentstring
Home page content in Markdown.
data.startTimenumber
CTF start time as a Unix ms timestamp.
data.endTimenumber
CTF end time as a Unix ms timestamp.
data.sponsorsobject[]
data.meta.descriptionstring
Site meta description.
data.meta.imageUrlstring
Social preview image URL.
data.faviconUrlstring
Favicon URL.
data.logoLightUrlstring
Light-theme logo URL.
data.logoDarkUrlstring
Dark-theme logo URL.

Response fields

overridesobject
Operator-set values overriding the defaults.
overrides.ctfNamestring | undefined
Name of the CTF.
overrides.homeContentstring | undefined
Markdown content shown on the home page.
overrides.startTimenumber | undefined
CTF start time as a Unix timestamp in milliseconds.
overrides.endTimenumber | undefined
CTF end time as a Unix timestamp in milliseconds.
overrides.sponsors[].namestring
Sponsor name.
overrides.sponsors[].iconstring | undefined
Deprecated. Mirrors iconLight (or iconDark when only it is set) for old clients.
overrides.sponsors[].iconLightstring | undefined
Sponsor icon URL for light mode; may be absent on data from older versions.
overrides.sponsors[].iconDarkstring | undefined
Sponsor icon URL for dark mode; may be absent on data from older versions.
overrides.sponsors[].descriptionstring
Sponsor description.
overrides.sponsors[].urlstring | undefined
Sponsor link, when present.
overrides.meta.descriptionstring | undefined
Meta description used for link previews.
overrides.meta.imageUrlstring | undefined
Preview image URL.
overrides.faviconUrlstring | undefined
Favicon URL.
overrides.logoLightUrlstring | undefined
Light-theme logo URL.
overrides.logoDarkUrlstring | undefined
Dark-theme logo URL.
defaultsobject
Built-in default values.
defaults.ctfNamestring | undefined
Name of the CTF.
defaults.homeContentstring | undefined
Markdown content shown on the home page.
defaults.startTimenumber | undefined
CTF start time as a Unix timestamp in milliseconds.
defaults.endTimenumber | undefined
CTF end time as a Unix timestamp in milliseconds.
defaults.sponsors[].namestring
Sponsor name.
defaults.sponsors[].iconstring | undefined
Deprecated. Mirrors iconLight (or iconDark when only it is set) for old clients.
defaults.sponsors[].iconLightstring | undefined
Sponsor icon URL for light mode; may be absent on data from older versions.
defaults.sponsors[].iconDarkstring | undefined
Sponsor icon URL for dark mode; may be absent on data from older versions.
defaults.sponsors[].descriptionstring
Sponsor description.
defaults.sponsors[].urlstring | undefined
Sponsor link, when present.
defaults.meta.descriptionstring | undefined
Meta description used for link previews.
defaults.meta.imageUrlstring | undefined
Preview image URL.
defaults.faviconUrlstring | undefined
Favicon URL.
defaults.logoLightUrlstring | undefined
Light-theme logo URL.
defaults.logoDarkUrlstring | undefined
Dark-theme logo URL.

GET Instancer schema

GET /api/v2/admin/instancer/schema

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead
Captcha
No captcha
Rate limit
No rate limit

This route provides the active instancer provider JSON schema and defaults. The challenge editor uses this response to validate instancerConfig before saving a challenge.

When no instancer provider is configured, the route returns 404 badEndpoint.

Response fields

defaultInstancerstring
Name of the instancer used by default.
instancers[].namestring
Instancer name.
instancers[].schemaRecord<string, unknown>
JSON Schema describing the instancer config fields.
instancers[].defaultsRecord<string, unknown>
Default config values for the instancer.
instancers[].canStopboolean
Whether instances can be stopped.
instancers[].canExtendboolean
Whether instance lifetimes can be extended.

GET Admin bot status

GET /api/v2/admin/admin-bot/status

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
challsRead
Captcha
No captcha
Rate limit
No rate limit

This route shows whether the admin bot provider is enabled and which source language the configured provider expects.

When no admin bot provider is configured, the route returns 404 badEndpoint.

Response fields

enabledboolean
Whether the admin bot is enabled.
configLanguagestring
Scripting language used for admin-bot configs.

POST Pull admin bot job

POST /api/v2/admin/admin-bot/jobs/pull

Edit this page View Markdown
Auth
Service
Gate
None
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

The admin bot worker calls this service route when it is ready for more work. It receives the oldest queued job. When nothing is waiting, the response has job set to null.

The request is authenticated with the shared admin bot bearer token from adminBot.provider.options.secretKey in rctf.d/.

Response fields

job.idstring
Admin-bot job ID.
job.challengeIdstring
Challenge ID the job targets.
job.configRevisionstring
Admin-bot config revision used.
job.userIdstring
Team ID that submitted the job.
job.submittedAtstring
Submission time as an ISO 8601 string.
job.flagstring
Flag the bot should submit, when applicable.
job.inputsRecord<string, string>
Submitted input values keyed by field name.
job.instancerInstances[].typestring
Instance exposure type.
job.instancerInstances[].hoststring
Instance host.
job.instancerInstances[].portnumber
Instance port.
job.instancerInstances[].titlestring | undefined
Short label for the instance, when present.

GET Admin bot source

GET /api/v2/admin/admin-bot/challenges/:id/source

Edit this page View Markdown
Auth
Service
Gate
None
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

The admin bot worker calls this service route to read the source code saved for a challenge config revision.

The request is authenticated with the shared admin bot bearer token from adminBot.provider.options.secretKey in rctf.d/.

Path parameters

id*string
Challenge ID.

Response fields

sourceCodestring
Admin-bot script source code.
configRevisionstring
Config revision the source belongs to.

POST Complete admin bot job

POST /api/v2/admin/admin-bot/jobs/:id/complete

Edit this page View Markdown
Auth
Service
Gate
None
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

The admin bot worker calls this service route after a job finishes successfully. The request can include logs so organizers can review what happened later.

Logs are optional. When included, the server accepts up to 1048576 characters.

Path parameters

id*string
Admin-bot job ID.

Request body

logsstring
Captured job logs.

Response fields

okboolean
Whether the update was applied.

POST Fail admin bot job

POST /api/v2/admin/admin-bot/jobs/:id/fail

Edit this page View Markdown
Auth
Service
Gate
None
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

The admin bot worker calls this service route when a job cannot complete successfully. The request can include logs so organizers can inspect the failure.

Logs are optional. When included, the server accepts up to 1048576 characters.

Path parameters

id*string
Admin-bot job ID.

Request body

logsstring
Captured job logs.

Response fields

okboolean
Whether the update was applied.

GET Admin bot queue depth

GET /api/v2/admin/admin-bot/queue-depth

Edit this page View Markdown
Auth
Service
Gate
None
Permissions
No extra permissions
Captcha
No captcha
Rate limit
No rate limit

The admin bot worker can call this service route to check how many jobs are waiting in the queue.

The request is authenticated with the shared admin bot bearer token from adminBot.provider.options.secretKey in rctf.d/.

Response fields

depthnumber
Number of jobs currently queued.

GET List external-auth clients

GET /api/v2/admin/external-auth/clients

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

Returns every registered external-auth client. Client secrets are never returned by this route - they are shown exactly once at creation time by Create external-auth client and cannot be retrieved afterward.

See External auth for the user-facing routes and External apps for the operator setup.

Response fields

idstring
OAuth client ID.
namestring
Client display name.
redirectUristring
Registered redirect URI.
createdAtstring
Creation time as an ISO 8601 timestamp.
createdBystring | null
ID of the admin who created the client, or null.

POST Create external-auth client

POST /api/v2/admin/external-auth/clients

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

Registers a new external-auth client. rCTF generates a UUID id and a 32-byte base64url secret. The redirect URI is stored exactly as supplied and is matched byte-for-byte at authorize time - no wildcards, no path normalization.

Warning (The secret is shown exactly once)

The response is the only place where the client secret is ever returned. Store it immediately somewhere the integrator can retrieve it. If it gets lost the client must be deleted and re-created.

Request body

name*string
Display name for the OAuth client.
redirectUri*string
Allowed OAuth redirect URI.

Response fields

idstring
OAuth client ID.
namestring
Client display name.
redirectUristring
Registered redirect URI.
createdAtstring
Creation time as an ISO 8601 timestamp.
createdBystring | null
ID of the admin who created the client, or null.
secretstring
Client secret. Shown once - store it now.

DELETE Delete external-auth client

DELETE /api/v2/admin/external-auth/clients/:id

Edit this page View Markdown
Auth
Required
Gate
None
Permissions
usersWrite
Captcha
No captcha
Rate limit
No rate limit

Removes an external-auth client. After this returns, POST /api/v2/external-auth/token exchanges with that clientId always fail with 400 badExternalAuthRequest.

Deleting a client does not revoke access tokens it already issued. Rotating the global tokenKey invalidates them, but also invalidates every other rCTF token.

Unknown client IDs return 400 badExternalAuthRequest.

Path parameters

id*string
OAuth client ID.

Response

A successful request returns 200 goodAdminExternalAuthClientDelete with no data.

Type to search.