API reference
Reference for rCTF API versions, authentication, responses, permissions, rate limits, and typed route definitions.
rCTF exposes a JSON REST API under /api. The @rctf/types package exports the route definitions and TypeScript types used by both the server and API clients.
Versions
rCTF serves V1 and V2 together:
| Version | Prefix | Status | Use |
|---|---|---|---|
| V1 | /api/v1 |
Supported | Legacy clients and compatibility with the original rCTF API. |
| V2 | /api/v2 |
Recommended | New integrations, richer response data, runtime settings, avatars, instancers, admin bot jobs, and admin search APIs. |
Use V2 when it provides the operation you need. Some operations still exist only in V1. Routes shared by both versions usually return the same response kind, with V2 adding fields such as avatars, country codes, and blood indices.
Typed route contract
The @rctf/types package exports the public route definitions, responses, enums, and helper types:
import { GetChallengesRouteV2, type RouteBodyInput, type RouteQueryInput, type RouteSuccessResponse,} from '@rctf/types'
type Query = RouteQueryInput<typeof GetChallengesRouteV2>type Response = RouteSuccessResponse<typeof GetChallengesRouteV2>RouteBodyInput<T>, RouteQueryInput<T>, and RouteParamsInput<T> provide the input types for the body, query string, and path parameters. They describe values before the server applies Zod conversions.
Authentication
User-authenticated routes require an auth token in the Authorization header:
Authorization: Bearer <auth-token>Admin bot service routes use the same header with the shared secret from adminBot.provider.options.secretKey instead of a user token.
| Auth mode | Behavior |
|---|---|
| Public | No token is required. Some public routes still accept optional auth so admins can bypass start-time gates. |
| Optional | The route works without a token, but an auth token may affect the response. |
| Required | A missing or invalid token returns 401 badToken. |
| Permissioned | The user token must be valid and the user must have every required permission bit. Missing bits return 403 badPerms. |
| Service | The shared service bearer token must match the configured provider secret. |
Token types
| Token | Lifetime | Used by |
|---|---|---|
| Auth | No expiry | Authorization: Bearer <auth-token> on user routes. |
| Team | No expiry | Account recovery and v1 login. V2 registration returns it directly when registration completes without email verification. |
| Verify | loginTimeout |
Email update verification. |
| CTFtime auth | loginTimeout |
CTFtime registration and login handoff. |
Tokens are encrypted with AES-GCM using the configured tokenKey. Changing tokenKey invalidates every token that was issued before the rotation.
Response format
Most API responses wrap their fields in the following object:
{ "kind": "goodChallenges", "message": "The retrieval of challenges was successful.", "data": []}Responses without additional data omit the data field:
{ "kind": "goodFlag", "message": "The flag is correct."}One typed route does not use this wrapper. GET /api/v1/integrations/ctftime/leaderboard returns { "standings": [...] } directly.
Request validation
Routes with a JSON body parse JSON, while upload routes parse multipart/form-data. Invalid input returns 400 badBody with a machine-readable reason:
{ "kind": "badBody", "message": "The request body does not meet requirements.", "data": { "reason": "query:limit: Too small: expected number to be >=1" }}The reason begins with body, query, or params to identify the source. Malformed JSON returns 400 badJson. Malformed form data returns 400 badBody with body:formData:malformed.
Permissions
Admin routes use bitmask permissions from Permissions:
| Permission | Bit | Routes |
|---|---|---|
challsRead |
1 |
Admin challenge reads, instancer schema, admin bot status, challenge start-time bypass. |
challsWrite |
2 |
Challenge updates, challenge deletion in v1, file upload. |
leaderboardRead |
4 |
CTFtime leaderboard and leaderboard start-time bypass. |
challsSolveWrite |
8 |
Solve deletion. |
usersWrite |
16 |
Admin user lists, user mutation, pending verification management, submission audit logs. |
settingsWrite |
32 |
Runtime settings reads and updates. |
When a route lists multiple permission bits, the user needs all of them.
Timing gates
Routes marked as start-gated return 401 badNotStarted before startTime. Admin users can bypass that gate when the route defines a bypass permission and their token has the required bit.
POST /api/v1/challs/:id/submit is also end-gated, returning 401 badEnded after endTime.
Captcha
Captcha is checked only for actions listed in the provider’s protected actions. V1 request bodies use recaptchaCode. V2 uses captchaCode.
| Action | Routes |
|---|---|
register |
POST /api/v1/auth/register, POST /api/v2/auth/register. |
recover |
POST /api/v1/auth/recover, POST /api/v2/auth/recover. |
setEmail |
PUT /api/v1/users/me/auth/email, PUT /api/v2/users/me/auth/email. |
avatarUpload |
PATCH /api/v2/users/me/avatar. |
instancerStart |
PUT /api/v2/integrations/challs/:id/instance. |
instancerExtend |
PATCH /api/v2/integrations/challs/:id/instance. |
adminBotSubmit |
POST /api/v2/integrations/challs/:id/admin-bot. |
Rate limits
Rate-limited routes return 429 badRateLimit with data.timeLeft, measured in milliseconds:
| Action | Scope | Bucket |
|---|---|---|
| Registration email | IP address | Burst 20, refill window 600000 ms. |
| Registration email | Email address | Burst 2, refill window 3600000 ms. |
| Account recovery | IP address | Burst 5, refill window 1500000 ms. |
| Account recovery | Email address | Burst 2, refill window 3600000 ms. |
| Flag submission | User and challenge | Burst 5, refill window 25000 ms. |
| Profile name update | User | Burst 3, refill window 180000 ms. |
| Email change | User | Burst 3, refill window 900000 ms. |
| Avatar upload | User | Burst 2, refill window 120000 ms. |
| Admin bot submission | User and challenge | Burst 1, refill window 10000 ms. |
| Leaderboard search | IP address | Burst 3, refill window 3000 ms. |
Registration uses its buckets only when rCTF would send a verification email. Recovery checks its buckets before looking up the account, so its rate-limit behavior does not reveal whether an email is registered. Both limits apply even when captcha is enabled.
Route sections
| Section | Scope |
|---|---|
| Authentication | Registration, verification, recovery, login, and token validation across V2 and V1 route contracts. |
| Challenges | Public challenge listing, solves, v1 flag submission, and the dynamic-scoring webhook. |
| Leaderboard | Current standings, graph data, challenge leaderboard metadata, search, and pagination. |
| Users | Public profiles, self profile, updates, avatar upload, email/CTFtime auth, and team members. |
| Admin | Challenge management, users, verification queue, submissions, uploads, settings, admin bot service routes, and external-auth clients. |
| Integrations | Client config, analytics script proxy, CTFtime, instancers, and participant admin bot routes. |
| External auth | “Sign in with rCTF” flow for external services, including client lookup, consent, and token exchange. |
| Responses | Shared response kinds, error payloads, and common object models. |