Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/flagForgeCTF/flagForge/llms.txt

Use this file to discover all available pages before exploring further.

The challenges API gives you programmatic access to every CTF problem on the platform. You can browse challenges with pagination and category filtering, retrieve a single challenge by its ID, and — if your account has the Admin role — create new challenges. The flag field is never returned by any read endpoint, keeping challenge answers safe.

GET /api/problems

List challenges. No authentication is required. Results are sorted newest-first and paginated.

Query parameters

page
number
default:"1"
The page number to retrieve. Starts at 1.
limit
number
default:"8"
Number of challenges per page. Maximum value is 1000. Pass a large value (e.g. 1000) to retrieve all challenges in a single request.
category
string
Filter by challenge category. Pass All or omit this parameter to return challenges from all categories. Pass a specific category name (e.g. Web, Crypto) to filter results.

Response fields

data
object[]
Array of challenge objects. The flag field is excluded from all objects in this array.
totalScore
number
The authenticated user’s current total score. Returns 0 when the request is made without a session.
questionDone
object[]
Array of UserQuestion records representing challenges the authenticated user has already solved. Returns an empty array when the request is made without a session.
pagination
object
Pagination metadata for the current result set.

Example

curl "https://flagforgectf.com/api/problems?page=1&limit=8&category=Web"
{
  "data": [
    {
      "_id": "665a1f2e3c4b5d6e7f8a9b0c",
      "title": "SQL Injection 101",
      "description": "Find the flag hidden behind a vulnerable login form.",
      "category": "Web",
      "points": 100,
      "challengeType": "link",
      "link": "https://challenge.flagforgectf.com/sqli-101",
      "hints": 2,
      "isTimeLimited": false,
      "expiryDate": null,
      "expired": false,
      "timeRemaining": null,
      "createdAt": "2024-06-01T10:00:00.000Z",
      "updatedAt": "2024-06-01T10:00:00.000Z"
    }
  ],
  "totalScore": 250,
  "questionDone": [],
  "pagination": {
    "page": 1,
    "limit": 8,
    "total": 42,
    "totalPages": 6,
    "hasNext": true,
    "hasPrev": false
  }
}

GET /api/problems/[id]

Retrieve a single challenge by its MongoDB ID. No authentication is required, but the response includes user-specific fields (isDone, usedHints) when a valid session cookie is present.

Path parameters

id
string
required
The MongoDB _id of the challenge.

Response fields

question
object
The challenge object. The flag and hints fields are stripped from this object — use the hint count in hintCount and the hints endpoint for hint text.
hintCount
number
Number of hints available for this challenge. Only hints with non-empty text are counted.
isDone
boolean
true if the authenticated user has already solved this challenge. Always false when the request is unauthenticated.
expired
boolean
true if the challenge’s expiryDate is in the past.
timeRemaining
number
Milliseconds remaining until expiryDate. null if there is no expiry date.
expiryDate
string
ISO 8601 expiry date, or null if the challenge does not expire.
usedHints
number[]
Array of hint indices the authenticated user has already unlocked. Empty array when unauthenticated.

Error responses

StatusCondition
404 Not FoundNo challenge exists with the given id.
410 GoneThe challenge existed but has expired.

Example

curl "https://flagforgectf.com/api/problems/665a1f2e3c4b5d6e7f8a9b0c"
{
  "question": {
    "_id": "665a1f2e3c4b5d6e7f8a9b0c",
    "title": "SQL Injection 101",
    "description": "Find the flag hidden behind a vulnerable login form.",
    "category": "Web",
    "points": 100,
    "challengeType": "link",
    "link": "https://challenge.flagforgectf.com/sqli-101",
    "isTimeLimited": false,
    "expiryDate": null,
    "createdAt": "2024-06-01T10:00:00.000Z",
    "updatedAt": "2024-06-01T10:00:00.000Z"
  },
  "hintCount": 2,
  "isDone": false,
  "expired": false,
  "timeRemaining": null,
  "expiryDate": null,
  "usedHints": []
}

POST /api/problems

Create a new challenge. Requires the Admin role.
This endpoint requires an active Admin session. Requests from unauthenticated users or non-Admin accounts return 401 Unauthorized.
The endpoint accepts two content types depending on whether the challenge has a file attachment:
  • application/json — for link-based challenges where the resource lives at an external URL.
  • multipart/form-data — for file-based challenges where you upload a challenge file directly.

Request fields (JSON or form-data)

title
string
required
Display title of the challenge.
description
string
required
Full challenge description shown to participants.
category
string
required
Challenge category, for example Web, Crypto, or Pwn.
points
number
required
Point value awarded to the first solver (before any hint penalties).
flag
string
required
The correct flag string. This value is stored securely and never returned by the API.
URL to the external challenge resource. Used for link-type challenges.
Optional supplementary links for participants (e.g. reference materials).
hints
string
JSON-encoded array of hint objects. Each hint object should include text, content, description, and pointsDeduction fields.
isTimeLimited
boolean
Set to true to give the challenge an expiry date.
timeLimit
number
Duration of the time limit in timeLimitUnit units.
timeLimitUnit
string
One of hours, days, or weeks.
expiryDate
string
An explicit expiry date in ISO 8601 format. Overrides the timeLimit / timeLimitUnit calculation when provided.
challengeFile
file
The file to upload. Only accepted when Content-Type is multipart/form-data. Allowed types: ZIP, PDF, TXT, PNG, JPG. Maximum size: 50 MB.

Response fields

success
boolean
true on success.
message
string
Human-readable confirmation message, for example "Your question has been created".

Examples

Create a link-based challenge (JSON):
curl -X POST "https://flagforgectf.com/api/problems" \
  -H "Content-Type: application/json" \
  -H "Cookie: next-auth.session-token=<admin-session>" \
  -d '{
    "title": "XSS Playground",
    "description": "Exploit a reflected XSS vulnerability to steal the admin cookie.",
    "category": "Web",
    "points": 150,
    "flag": "FF{xss_c00ki3_theft}",
    "link": "https://challenge.flagforgectf.com/xss-playground"
  }'
Create a file-based challenge (multipart/form-data):
curl -X POST "https://flagforgectf.com/api/problems" \
  -H "Cookie: next-auth.session-token=<admin-session>" \
  -F "title=Reverse Me" \
  -F "description=Reverse engineer the binary to find the flag." \
  -F "category=Reversing" \
  -F "points=200" \
  -F "flag=FF{r3v3rs3_en9in33r}" \
  -F "challengeFile=@/path/to/challenge.zip"
{
  "success": true,
  "message": "Your question has been created"
}

GET /api/categories

Return all challenge categories currently in use on the platform. No authentication is required.

Response fields

categories
string[]
Sorted list of distinct category names. Always includes "All" as the first entry.

Example

curl "https://flagforgectf.com/api/categories"
{
  "categories": ["All", "Crypto", "Forensics", "Misc", "Pwn", "Reversing", "Web"]
}
Categories are derived dynamically from the challenges in the database. A category only appears after at least one challenge has been created with that category label.

Build docs developers (and LLMs) love