Admin
Challenge management, uploads, teams, verifications, submissions, settings, and admin bot service routes.
These admin API pages cover challenge data, uploads, teams, pending email verifications, submission audit rows, runtime settings, admin bot service work, and external-auth client registration. Most user facing admin routes need a user auth token with the listed permission bits. Admin bot service routes use the shared admin bot bearer token instead. The end-user side of the external-auth flow lives under External auth.
Permissions, captcha actions, and rate limit conventions are documented in the API overview.
Permissions#
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
- 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
idstringnamestringdescriptionstringcategorystringauthorstringfiles[].namestringfiles[].urlstringfiles[].sizenumber | nullpoints.minnumberpoints.maxnumberflagstringtiebreakEligiblebooleansortWeightnumber | null | undefinedinstancerConfigobject | null | undefinedadminBotConfigobject | null | undefinedhiddenbooleanreleaseTimenumber | null | undefinedscoringobject | object | null | undefinedResponse fields
idstringnamestringdescriptionstringcategorystringauthorstringfiles[].namestringfiles[].urlstringpoints.minnumberpoints.maxnumberflagstringtiebreakEligiblebooleansortWeightnumber | null | undefinedV1 challenge entries do not include hidden, releaseTime, instancerConfig, adminBotConfig, or file size.
GET Admin challenge detail
GET /api/[v2,v1]/admin/challs/:id
- 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 requiredstringPath parameters
id requiredstringResponse fields
idstringnamestringdescriptionstringcategorystringauthorstringfiles[].namestringfiles[].urlstringfiles[].sizenumber | nullpoints.minnumberpoints.maxnumberflagstringtiebreakEligiblebooleansortWeightnumber | null | undefinedinstancerConfigobject | null | undefinedadminBotConfigobject | null | undefinedhiddenbooleanreleaseTimenumber | null | undefinedscoringobject | object | null | undefinedResponse fields
idstringnamestringdescriptionstringcategorystringauthorstringfiles[].namestringfiles[].urlstringpoints.minnumberpoints.maxnumberflagstringtiebreakEligiblebooleansortWeightnumber | null | undefinedV1 challenge entries do not include hidden, releaseTime, instancerConfig, adminBotConfig, or file size.
PUT Create or update challenge
PUT /api/[v2,v1]/admin/challs/:id
- 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 requiredstringRequest body
data requiredobjectPath parameters
id requiredstringRequest body
data requiredobjectResponse fields
idstringnamestringdescriptionstringcategorystringauthorstringfiles[].namestringfiles[].urlstringfiles[].sizenumber | nullpoints.minnumberpoints.maxnumberflagstringtiebreakEligiblebooleansortWeightnumber | null | undefinedinstancerConfigobject | null | undefinedadminBotConfigobject | null | undefinedhiddenbooleanreleaseTimenumber | null | undefinedscoringobject | object | null | undefinedResponse fields
idstringnamestringdescriptionstringcategorystringauthorstringfiles[].namestringfiles[].urlstringpoints.minnumberpoints.maxnumberflagstringtiebreakEligiblebooleansortWeightnumber | null | undefinedV1 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
- 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 requiredstringuserId requiredstringResponse#
A successful request returns 200 goodChallengeSolveDelete with no data.
DELETE Delete a challenge
DELETE /api/v1/admin/challs/:id
- 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 requiredstringResponse#
A successful request returns 200 goodChallengeDelete with no data.
POST Upload files
POST /api/[v2,v1]/admin/upload
- 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 requiredtransformRequest body
files requiredobject[]Response fields
namestringurlstringsizenumber | nullResponse fields
namestringurlstringV1 upload responses do not include file size.
POST Query uploads
POST /api/[v2,v1]/admin/upload/query
- 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 requiredobject[]Request body
uploads requiredobject[]Response fields
sha256stringnamestringurlstring | nullsizenumber | nullResponse fields
sha256stringnamestringurlstring | nullGET List teams
GET /api/v2/admin/users
- 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 requirednumberoffset requirednumbersearchstringsortBy"createdAt" | "team" | "email" | "division" | "score" | "solves" | "status"sortOrder"asc" | "desc"Response fields
totalnumberusers[].idstringusers[].namestringusers[].emailstring | nullusers[].divisionstringusers[].permsnumberusers[].bannedbooleanusers[].scorenumberusers[].solveCountnumberusers[].avatarUrlstring | nullusers[].countryCodestring | nullusers[].statusTextstring | nullusers[].createdAtstringEach list item includes public profile fields, admin state, cached score data, and creation time.
POST Filter teams
POST /api/v2/admin/users
- 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 requirednumberoffset requirednumbersearchstringsortBy"createdAt" | "team" | "email" | "division" | "score" | "solves" | "status"sortOrder"asc" | "desc"Request body
statusobjectdivisionobjectResponse fields
totalnumberusers[].idstringusers[].namestringusers[].emailstring | nullusers[].divisionstringusers[].permsnumberusers[].bannedbooleanusers[].scorenumberusers[].solveCountnumberusers[].avatarUrlstring | nullusers[].countryCodestring | nullusers[].statusTextstring | nullusers[].createdAtstringGET Team detail
GET /api/v2/admin/users/:id
- 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 requiredstringResponse fields
idstringnamestringemailstring | nulldivisionstringpermsnumberbannedbooleanscorenumbersolveCountnumberavatarUrlstring | nullcountryCodestring | nullstatusTextstring | nullcreatedAtstringsolves[].challengeIdstringsolves[].challengeNamestringsolves[].challengeCategorystringsolves[].createdAtstringPrivate account fields appear here, so this route belongs in trusted admin views.
PUT Update team
PUT /api/v2/admin/users/:id
- 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 requiredstringRequest body
data requiredobjectResponse#
A successful request returns 200 goodAdminUserUpdate with no data.
DELETE Delete team
DELETE /api/v2/admin/users/:id
- 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 requiredstringResponse#
A successful request returns 200 goodAdminUserDelete with no data.
POST Create team token
POST /api/v2/admin/users/:id/token
- 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 requiredstringResponse fields
tokenstringGET Pending verifications
GET /api/v2/admin/user-verifications
- 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[].idstringverifications[].namestringverifications[].emailstringverifications[].divisionstringverifications[].createdAtnumberverifications[].expiresAtnumberPOST Complete verification
POST /api/v2/admin/user-verifications/:id/complete
- 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 requiredstringResponse fields
userIdstringPOST Resend verification
POST /api/v2/admin/user-verifications/:id/resend
- 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 requiredstringResponse fields
idstringGET List submissions
GET /api/v2/admin/submissions
- 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 requirednumberoffset requirednumbersortBy"createdAt" | "challenge" | "team" | "ip" | "kind" | "result"sortOrder"asc" | "desc"challengeSearchstringteamSearchstringResponse fields
totalnumbersubmissions[].idstringsubmissions[].kind"flag" | "admin_bot"submissions[].challengeIdstringsubmissions[].challengeNamestringsubmissions[].challengeCategorystringsubmissions[].userIdstringsubmissions[].userNamestringsubmissions[].userDivisionstringsubmissions[].userAvatarUrlstring | nullsubmissions[].userCountryCodestring | nullsubmissions[].userStatusTextstring | nullsubmissions[].userBannedbooleansubmissions[].ipstringsubmissions[].result"correct" | "incorrect" | "already_solved" | "queued" | "active_job" | "invalid_input" | "bad_instancer_state"submissions[].detailsRecord<string, any>submissions[].relatedIdstring | nullsubmissions[].createdAtstringEach row includes challenge metadata, team metadata, the source IP address, result details, and creation time.
POST Filter submissions
POST /api/v2/admin/submissions
- 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 requirednumberoffset requirednumbersortBy"createdAt" | "challenge" | "team" | "ip" | "kind" | "result"sortOrder"asc" | "desc"challengeSearchstringteamSearchstringRequest body
challengeobjectteamobjectkindobjectresultobjectteamStatusobjectcategoryobjectdivisionobjectcreatedAfterstringcreatedBeforestringResponse fields
totalnumbersubmissions[].idstringsubmissions[].kind"flag" | "admin_bot"submissions[].challengeIdstringsubmissions[].challengeNamestringsubmissions[].challengeCategorystringsubmissions[].userIdstringsubmissions[].userNamestringsubmissions[].userDivisionstringsubmissions[].userAvatarUrlstring | nullsubmissions[].userCountryCodestring | nullsubmissions[].userStatusTextstring | nullsubmissions[].userBannedbooleansubmissions[].ipstringsubmissions[].result"correct" | "incorrect" | "already_solved" | "queued" | "active_job" | "invalid_input" | "bad_instancer_state"submissions[].detailsRecord<string, any>submissions[].relatedIdstring | nullsubmissions[].createdAtstringGET Read settings
GET /api/v2/admin/settings
- 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
overrides.ctfNamestring | undefinedoverrides.homeContentstring | undefinedoverrides.startTimenumber | undefinedoverrides.endTimenumber | undefinedoverrides.sponsors[].namestringoverrides.sponsors[].iconstringoverrides.sponsors[].descriptionstringoverrides.sponsors[].urlstring | undefinedoverrides.meta.descriptionstring | undefinedoverrides.meta.imageUrlstring | undefinedoverrides.faviconUrlstring | undefinedoverrides.logoLightUrlstring | undefinedoverrides.logoDarkUrlstring | undefineddefaults.ctfNamestring | undefineddefaults.homeContentstring | undefineddefaults.startTimenumber | undefineddefaults.endTimenumber | undefineddefaults.sponsors[].namestringdefaults.sponsors[].iconstringdefaults.sponsors[].descriptionstringdefaults.sponsors[].urlstring | undefineddefaults.meta.descriptionstring | undefineddefaults.meta.imageUrlstring | undefineddefaults.faviconUrlstring | undefineddefaults.logoLightUrlstring | undefineddefaults.logoDarkUrlstring | undefinedPUT Update settings
PUT /api/v2/admin/settings
- 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 requiredobjectResponse fields
overrides.ctfNamestring | undefinedoverrides.homeContentstring | undefinedoverrides.startTimenumber | undefinedoverrides.endTimenumber | undefinedoverrides.sponsors[].namestringoverrides.sponsors[].iconstringoverrides.sponsors[].descriptionstringoverrides.sponsors[].urlstring | undefinedoverrides.meta.descriptionstring | undefinedoverrides.meta.imageUrlstring | undefinedoverrides.faviconUrlstring | undefinedoverrides.logoLightUrlstring | undefinedoverrides.logoDarkUrlstring | undefineddefaults.ctfNamestring | undefineddefaults.homeContentstring | undefineddefaults.startTimenumber | undefineddefaults.endTimenumber | undefineddefaults.sponsors[].namestringdefaults.sponsors[].iconstringdefaults.sponsors[].descriptionstringdefaults.sponsors[].urlstring | undefineddefaults.meta.descriptionstring | undefineddefaults.meta.imageUrlstring | undefineddefaults.faviconUrlstring | undefineddefaults.logoLightUrlstring | undefineddefaults.logoDarkUrlstring | undefinedGET Instancer schema
GET /api/v2/admin/instancer/schema
- 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
schemaRecord<string, unknown>defaultsRecord<string, unknown>GET Admin bot status
GET /api/v2/admin/admin-bot/status
- 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
enabledbooleanconfigLanguagestringPOST Pull admin bot job
POST /api/v2/admin/admin-bot/jobs/pull
- 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.idstringjob.challengeIdstringjob.configRevisionstringjob.userIdstringjob.submittedAtstringjob.flagstringjob.inputsRecord<string, string>job.instancerInstances[].typestringjob.instancerInstances[].hoststringjob.instancerInstances[].portnumberjob.instancerInstances[].titlestring | undefinedGET Admin bot source
GET /api/v2/admin/admin-bot/challenges/:id/source
- 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 requiredstringResponse fields
sourceCodestringconfigRevisionstringPOST Complete admin bot job
POST /api/v2/admin/admin-bot/jobs/:id/complete
- 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 requiredstringRequest body
logsstringResponse fields
okbooleanPOST Fail admin bot job
POST /api/v2/admin/admin-bot/jobs/:id/fail
- 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 requiredstringRequest body
logsstringResponse fields
okbooleanGET Admin bot queue depth
GET /api/v2/admin/admin-bot/queue-depth
- 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
depthnumberGET List external-auth clients
GET /api/v2/admin/external-auth/clients
- 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.
The end-user side of the external-auth flow lives at External auth, and the operator walkthrough lives at External apps.
Response fields
idstringnamestringredirectUristringcreatedAtstringcreatedBystring | nullPOST Create external-auth client
POST /api/v2/admin/external-auth/clients
- 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 requiredstringredirectUri requiredstringResponse fields
idstringnamestringredirectUristringcreatedAtstringcreatedBystring | nullsecretstringDELETE Delete external-auth client
DELETE /api/v2/admin/external-auth/clients/:id
- 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.
Access tokens that were already minted through this client stay valid. There is no per-app token registry, so per-client revocation is not possible. If you need to invalidate outstanding tokens, rotate the global tokenKey (this invalidates every token in the system).
Unknown client IDs return 400 badExternalAuthRequest.
Path parameters
id requiredstringResponse#
A successful request returns 200 goodAdminExternalAuthClientDelete with no data.