Open standard

CTFProfile Event Results Standard

A single, platform-neutral JSON format for importing completed-event results into CTFProfile. Conform your scoring data to this shape and it imports cleanly — every section is optional, and anything missing is flagged to the event's organizers to fill in later. Current version: ctfprofile-results/v1.

Download blank template Download filled sample

Top-level shape

One JSON object with these arrays. Include only what you have.

{
  "format": "ctfprofile-results/v1",
  "event":      { "name": "Example CTF 2025" },
  "challenges": [ ... ],
  "teams":      [ ... ],   // each may contain members[]
  "players":    [ ... ],   // solo competitors
  "solves":     [ ... ],
  "hints":      [ ... ],   // optional
  "opens":      [ ... ]    // optional
}

challenges[]

Every challenge in the event.

FieldRequirementMeaning
id recommended Stable id referenced by solves/hints/opens.
name required Challenge name.
category recommended e.g. web, pwn, crypto — drives skill mapping.
points recommended Point value (integer).
description optional Markdown allowed.
author optional Challenge author / attribution.

teams[]

Team entries. Omit for solo-only events.

FieldRequirementMeaning
id required Stable id referenced by solves.
name required Team name (used to match a registered team).
ctfprofile_token optional Pre-event link token — most reliable match.
score optional Final score.
rank optional Final placement.
members optional Array of player objects (same shape as players[]).

players[] (and team members[])

Individual competitors. Solo players go in players[]; team members go in their team's members[].

FieldRequirementMeaning
id recommended Stable id referenced by solves.
name required Username (used to match a site account).
ctfprofile_token optional Pre-event link token — most reliable match.
score optional Final score (solo players).
rank optional Final placement (solo players).

solves[]

One row per correct solve.

FieldRequirementMeaning
challenge_id required Refers to a challenge id.
team_id / user_id required Who solved it (one or both).
timestamp recommended ISO-8601; powers solve-time & first-blood stats.
id optional Submission id; de-duplicates re-imports.

hints[] · opens[]

Optional. Power hint-usage and time-to-solve-from-open stats.

FieldRequirementMeaning
challenge_id required Refers to a challenge id.
user_id recommended Who unlocked/opened it.
timestamp recommended ISO-8601.

Matching & timestamps

Linking results per platform

The most reliable match is the per-event link token. Each registrant gets one on the event page after registering (the event must be tagged with a scoring platform). The player pastes it into a field on the scoring platform; on export, map that value to ctfprofile_token. Recipes per platform:

CTFd

Paste into: CTFProfile Token

  1. Open your CTFd profile settings (or your team's settings, if a captain).
  2. Find the “CTFProfile Token” custom field (the organizer adds it under Admin → Config → Fields).
  3. Paste your token below into that field and save.

If the organizer installed the CTFProfile Sync plugin, your solves link automatically once the token is set — no manual claim needed.

rCTF

Paste into: CTFProfile Token

  1. Open your rCTF team settings page.
  2. Paste your token into the “CTFProfile Token” / bio field the organizer designated.
  3. Save. At export time the organizer maps that column to the import.

rCTF is team-based — the team captain's token covers the whole team.

MetaCTF

Paste into: CTFProfile Token

  1. Open your MetaCTF profile / team settings.
  2. Paste your token into the CTFProfile token field provided by the organizer.
  3. Save before the event ends so it's included in the results export.

Any other platform

Paste into: CTFProfile token field

  1. Find any custom field, bio, or profile note your platform lets you edit.
  2. Paste your token there, exactly as shown.
  3. Tell the organizer which field you used so they can map it on export.

Any platform that can attach a value to your account and include it in an export can use this — the token just needs to reach the import as “ctfprofile_token”.

Complete example

{
  "format": "ctfprofile-results/v1",
  "event": {
    "name": "Example CTF 2025"
  },
  "challenges": [
    {
      "id": "c1",
      "name": "Cookie Monster",
      "category": "web",
      "points": 100,
      "description": "Auth bypass via an unsigned session cookie.",
      "author": "webwyrm"
    },
    {
      "id": "c2",
      "name": "Heap Lantern",
      "category": "pwn",
      "points": 450,
      "description": "Tcache poisoning into code execution.",
      "author": "cipherlynx"
    }
  ],
  "teams": [
    {
      "id": "t1",
      "name": "Null Pointers",
      "score": 550,
      "rank": 1,
      "members": [
        {
          "id": "u1",
          "name": "cipherlynx"
        },
        {
          "id": "u2",
          "name": "webwyrm"
        }
      ]
    }
  ],
  "players": [
    {
      "id": "u9",
      "name": "soloist",
      "score": 100,
      "rank": 2
    }
  ],
  "solves": [
    {
      "id": "s1",
      "challenge_id": "c1",
      "team_id": "t1",
      "user_id": "u2",
      "timestamp": "2025-01-01T10:15:00Z"
    },
    {
      "id": "s2",
      "challenge_id": "c2",
      "team_id": "t1",
      "user_id": "u1",
      "timestamp": "2025-01-01T12:40:00Z"
    },
    {
      "id": "s3",
      "challenge_id": "c1",
      "user_id": "u9",
      "timestamp": "2025-01-01T11:05:00Z"
    }
  ],
  "hints": [
    {
      "challenge_id": "c2",
      "user_id": "u1",
      "timestamp": "2025-01-01T12:10:00Z"
    }
  ],
  "opens": [
    {
      "challenge_id": "c1",
      "user_id": "u2",
      "timestamp": "2025-01-01T10:00:00Z"
    }
  ]
}