Game Spec
Generated from code. Regenerate with:
rapua genspecMachine-readable:
GET /api/v7/spec
Authoring constraints
These rules are enforced by the linter (POST /api/v7/lint). Errors block import; warnings should be fixed.
Structure
- Every location must be inside a group. A location placed directly under
structure.childrenis never shown to players. Wrap it in a group. (ROOT_LOCATION_HIDDEN) - Groups must have at least one child. An empty group produces a warning. (
EMPTY_GROUP) - Location slugs must be unique across the entire game, including across groups. (
SLUG_DUPLICATE)
Import modes
- Create-import (
POST /admin/instances/import): omitidon locations and blocks — new UUIDs are generated. - Update-import (
POST /admin/instances/{id}/import): includeidto reconcile with existing records. Matched blocks preserve player state (TeamBlockState). Locations absent from the document are deleted. - Group
idis preserved on update-import to avoid orphaning team progress records (SkippedGroupIDs).
Blocks
- Every block must have a
typefield matching a registered block type. - A block may only appear in contexts listed in its spec. (
INVALID_CONTEXT) - Block
idvalues must be unique across the document. (BLOCK_ID_DUPLICATE) - Block and location
pointsare ignored unlesssettings.enable_pointsis true. (POINTS_DISABLEDwarning)
Start page
- A start page with blocks but no
start_buttonblock will not let players join. (NO_START_BUTTONwarning)
Completion
minimum_requiredis only valid whencompletionis"minimum"; it must be a positive integer. (MINIMUM_REQUIRED_MISMATCH/MINIMUM_REQUIRED_MISSING)
Full spec
{
"version": "v7",
"document": {
"description": "Top-level v7 game document.",
"fields": [
{
"name": "rapua",
"type": "string",
"description": "Format version. Must be \"v7\".",
"required": true
},
{
"name": "id",
"type": "string",
"description": "Instance UUID. Present on export; omit on create-import to generate a new UUID."
},
{
"name": "name",
"type": "string",
"description": "Game name.",
"required": true
},
{
"name": "settings",
"type": "object",
"description": "Game-wide settings.",
"required": true,
"fields": [
{
"name": "must_check_out",
"type": "bool",
"description": "Players must explicitly check out of each location before moving on."
},
{
"name": "show_team_count",
"type": "bool",
"description": "Show how many teams are at each location."
},
{
"name": "enable_points",
"type": "bool",
"description": "Enable the points system."
},
{
"name": "enable_bonus_points",
"type": "bool",
"description": "Enable bonus points on blocks."
},
{
"name": "show_leaderboard",
"type": "bool",
"description": "Show the leaderboard to players."
}
]
},
{
"name": "start",
"type": "list",
"description": "Blocks shown on the start page. Always present, even if empty.",
"required": true
},
{
"name": "finish",
"type": "list",
"description": "Blocks shown on the finish page. Always present, even if empty.",
"required": true
},
{
"name": "structure",
"type": "object",
"description": "Root group defining routing, navigation, and the location tree.",
"required": true,
"fields": [
{
"name": "routing",
"type": "enum",
"description": "How players are routed through locations. See enums.routing.",
"required": true
},
{
"name": "navigation",
"type": "enum",
"description": "How the navigation UI is presented. See enums.navigation.",
"required": true
},
{
"name": "completion",
"type": "enum",
"description": "When the group is considered complete. See enums.completion.",
"required": true
},
{
"name": "minimum_required",
"type": "int",
"description": "Number of locations required when completion is \"minimum\"."
},
{
"name": "children",
"type": "list",
"description": "Ordered list of location or group children.",
"required": true,
"items": {
"name": "",
"type": "object",
"description": "Tagged union: set exactly one of \"location\" or \"group\".",
"fields": [
{
"name": "location",
"type": "object",
"description": "A single game location. See location schema."
},
{
"name": "group",
"type": "object",
"description": "A named sub-group with its own routing/navigation/completion settings."
}
]
}
}
]
},
{
"name": "location",
"type": "object",
"description": "Schema for location objects within structure.children.",
"fields": [
{
"name": "id",
"type": "string",
"description": "Location UUID. Present on export; omit on create-import to generate a new UUID."
},
{
"name": "slug",
"type": "string",
"description": "Short alphanumeric code used in QR/URLs. Must be unique within the game.",
"required": true
},
{
"name": "name",
"type": "string",
"description": "Display name shown to players.",
"required": true
},
{
"name": "points",
"type": "int",
"description": "Points awarded on check-in (requires enable_points in settings)."
},
{
"name": "marker",
"type": "object",
"description": "Geographic map pin. Omit if the location has no map position.",
"fields": [
{
"name": "lat",
"type": "float",
"description": "Latitude in decimal degrees.",
"required": true
},
{
"name": "lng",
"type": "float",
"description": "Longitude in decimal degrees.",
"required": true
}
]
},
{
"name": "content",
"type": "list",
"description": "Blocks shown to players after check-in. Always present, even if empty.",
"required": true
},
{
"name": "clues",
"type": "list",
"description": "Clue blocks shown in navigation to help players find this location."
},
{
"name": "tasks",
"type": "list",
"description": "Task blocks shown in the task checklist (tasks navigation mode)."
},
{
"name": "checkpoint",
"type": "list",
"description": "Validation blocks that must pass before check-in completes."
}
]
}
]
},
"enums": {
"routing": [
{
"value": "randomised",
"label": "Randomised Route",
"description": "The game randomly selects locations for each player/team. Good for large groups."
},
{
"value": "free_roam",
"label": "Open Exploration",
"description": "Players visit locations in any order. All locations are visible."
},
{
"value": "ordered",
"label": "Guided Path",
"description": "Players must visit locations in a fixed order."
},
{
"value": "secret",
"label": "Secret",
"description": "Locations never explicitly shown; players access them out of sequence."
}
],
"navigation": [
{
"value": "map",
"label": "Map Only",
"description": "Players are shown a map with unlabelled pins."
},
{
"value": "labelled_map",
"label": "Labelled Map",
"description": "Players are shown a map with location names."
},
{
"value": "location_list",
"label": "Location List",
"description": "Players are shown a list of location names."
},
{
"value": "custom",
"label": "Custom Clues",
"description": "Players see custom content (e.g. randomised clues) built with the block builder."
},
{
"value": "tasks",
"label": "Tasks",
"description": "Players see a scavenger-hunt-style checklist with completion tracking."
}
],
"completion": [
{
"value": "all",
"label": "All",
"description": "All locations/groups in this group must be completed."
},
{
"value": "minimum",
"label": "Minimum",
"description": "At least minimum_required locations/groups must be completed."
}
]
},
"contexts": [
{
"value": "location_content",
"description": "Blocks shown to players after checking in to a location."
},
{
"value": "location_clues",
"description": "Clue blocks shown in the navigation UI to help players find this location."
},
{
"value": "task",
"description": "Task blocks shown in the tasks navigation checklist."
},
{
"value": "checkpoint",
"description": "Validation blocks that must pass before a check-in is accepted."
},
{
"value": "start",
"description": "Blocks shown on the game start page (introductions, rules, team name, start button)."
},
{
"value": "finish",
"description": "Blocks shown on the game finish/end page."
}
],
"blocks": [
{
"type": "alert",
"name": "Alert",
"description": "Displays a styled alert box with a message.",
"contexts": [
"location_content",
"finish",
"start"
],
"fields": [
{
"name": "content",
"type": "string",
"description": "Alert message text",
"required": true
},
{
"name": "style",
"type": "enum",
"description": "Visual style of the alert",
"enum": [
"info",
"success",
"warning",
"error"
]
}
]
},
{
"type": "broker",
"name": "Information Broker",
"description": "Players spend points to reveal progressively detailed information tiers.",
"contexts": [
"location_content",
"location_clues"
],
"fields": [
{
"name": "prompt",
"type": "string",
"description": "Instructions shown to the player"
},
{
"name": "default_info",
"type": "markdown",
"description": "Information shown for free (0 points)"
},
{
"name": "tiers",
"type": "list",
"description": "Paid information tiers in ascending cost order",
"items": {
"name": "",
"type": "object",
"fields": [
{
"name": "points_required",
"type": "int",
"description": "Points cost for this tier",
"required": true
},
{
"name": "content",
"type": "markdown",
"description": "Information revealed at this tier",
"required": true
}
]
}
}
]
},
{
"type": "button",
"name": "Button",
"description": "A clickable button that opens a URL.",
"contexts": [
"location_content",
"finish",
"start"
],
"fields": [
{
"name": "link",
"type": "string",
"description": "URL the button opens",
"required": true
},
{
"name": "text",
"type": "string",
"description": "Button label text",
"required": true
},
{
"name": "style",
"type": "enum",
"description": "Button visual style",
"enum": [
"primary",
"secondary",
"accent",
"ghost"
]
}
]
},
{
"type": "checklist",
"name": "Checklist",
"description": "An interactive checklist players can tick off.",
"contexts": [
"location_content",
"start"
],
"fields": [
{
"name": "content",
"type": "markdown",
"description": "Optional introductory text above the checklist"
},
{
"name": "items",
"type": "list",
"description": "Checklist items",
"required": true,
"items": {
"name": "",
"type": "object",
"fields": [
{
"name": "id",
"type": "string",
"description": "Unique item identifier"
},
{
"name": "description",
"type": "string",
"description": "Item text",
"required": true
},
{
"name": "checked",
"type": "bool",
"description": "Default checked state"
}
]
}
}
]
},
{
"type": "clue",
"name": "Clue",
"description": "A clue revealed behind a button — players tap to reveal the hint.",
"contexts": [
"location_content",
"location_clues"
],
"fields": [
{
"name": "clue",
"type": "markdown",
"description": "Clue content revealed when the button is tapped",
"required": true
},
{
"name": "description",
"type": "string",
"description": "Description or label shown on the reveal button"
},
{
"name": "button_label",
"type": "string",
"description": "Custom button label (defaults to 'Reveal clue')"
}
]
},
{
"type": "divider",
"name": "Divider",
"description": "A horizontal rule with an optional title label.",
"contexts": [
"location_content",
"finish",
"start"
],
"fields": [
{
"name": "title",
"type": "string",
"description": "Optional label shown on the divider"
}
]
},
{
"type": "game_status",
"name": "Game Status Alert",
"description": "Shows the current game status (scheduled, active, or closed) with optional messages.",
"contexts": [
"start"
],
"fields": [
{
"name": "closed_message",
"type": "string",
"description": "Message shown when the game is closed"
},
{
"name": "scheduled_message",
"type": "string",
"description": "Message shown when the game is scheduled"
},
{
"name": "show_countdown",
"type": "bool",
"description": "Show a countdown timer when game is scheduled"
}
]
},
{
"type": "header",
"name": "Header",
"description": "A page header with an icon and title.",
"contexts": [
"location_content",
"start",
"finish"
],
"fields": [
{
"name": "icon",
"type": "string",
"description": "Icon name (Lucide icon)"
},
{
"name": "title",
"type": "string",
"description": "Header title text",
"required": true
},
{
"name": "size",
"type": "enum",
"description": "Title font size",
"enum": [
"sm",
"md",
"lg",
"xl",
"2xl",
"3xl"
]
}
]
},
{
"type": "image",
"name": "Image",
"description": "Displays an image with an optional caption and link.",
"contexts": [
"location_content",
"location_clues",
"finish",
"start"
],
"fields": [
{
"name": "url",
"type": "string",
"description": "Image URL",
"required": true
},
{
"name": "caption",
"type": "string",
"description": "Caption displayed below the image"
},
{
"name": "link",
"type": "string",
"description": "Optional URL to open when the image is clicked"
},
{
"name": "full_width",
"type": "bool",
"description": "Render image edge-to-edge on mobile screens"
}
]
},
{
"type": "password",
"name": "Password",
"description": "Players must enter the correct text answer to proceed.",
"contexts": [
"location_content",
"checkpoint"
],
"fields": [
{
"name": "prompt",
"type": "string",
"description": "Question or instruction shown to the player"
},
{
"name": "answer",
"type": "string",
"description": "Correct answer (case-insensitive)",
"required": true
},
{
"name": "unlocked_content",
"type": "markdown",
"description": "Content shown after a correct answer"
}
]
},
{
"type": "photo",
"name": "Photo Upload",
"description": "Players upload one or more photos as their response.",
"contexts": [
"location_content",
"finish"
],
"fields": [
{
"name": "prompt",
"type": "string",
"description": "Instructions shown to the player"
},
{
"name": "max_images",
"type": "int",
"description": "Maximum number of photos the player can upload"
}
]
},
{
"type": "pincode",
"name": "Pin Code",
"description": "Players must enter a numeric PIN to proceed.",
"contexts": [
"location_content",
"checkpoint"
],
"fields": [
{
"name": "prompt",
"type": "string",
"description": "Question or instruction shown to the player"
},
{
"name": "pincode",
"type": "string",
"description": "Correct PIN (digits only)",
"required": true
},
{
"name": "unlocked_content",
"type": "markdown",
"description": "Content shown after a correct PIN"
}
]
},
{
"type": "quiz",
"name": "Quiz",
"description": "Multiple choice question with configurable answer options.",
"contexts": [
"location_content",
"checkpoint"
],
"fields": [
{
"name": "question",
"type": "markdown",
"description": "Question text",
"required": true
},
{
"name": "options",
"type": "list",
"description": "Answer choices",
"required": true,
"items": {
"name": "",
"type": "object",
"fields": [
{
"name": "id",
"type": "string",
"description": "Unique option identifier"
},
{
"name": "text",
"type": "string",
"description": "Option text",
"required": true
},
{
"name": "correct",
"type": "bool",
"description": "Whether this option is correct",
"required": true
}
]
}
},
{
"name": "multiple_choice",
"type": "bool",
"description": "Allow selecting multiple options"
},
{
"name": "randomise_order",
"type": "bool",
"description": "Shuffle option order for each player"
},
{
"name": "allow_retry",
"type": "bool",
"description": "Allow players to retry after an incorrect answer"
},
{
"name": "unlocked_content",
"type": "markdown",
"description": "Content shown after a correct answer"
}
]
},
{
"type": "random_clue",
"name": "Random Clue",
"description": "Shows a randomly selected clue from a list each time the player views the location.",
"contexts": [
"location_clues"
],
"fields": [
{
"name": "clues",
"type": "list",
"description": "List of clue strings to randomly select from",
"required": true,
"items": {
"name": "",
"type": "string"
}
}
]
},
{
"type": "rating",
"name": "Rating",
"description": "Players rate something on a star scale.",
"contexts": [
"location_content",
"finish"
],
"fields": [
{
"name": "prompt",
"type": "string",
"description": "What the player is rating"
},
{
"name": "max_rating",
"type": "int",
"description": "Maximum rating value (default 5)"
}
]
},
{
"type": "sorting",
"name": "Sorting",
"description": "Players drag and drop items into the correct order.",
"contexts": [
"location_content",
"checkpoint"
],
"fields": [
{
"name": "content",
"type": "markdown",
"description": "Instructions or question shown above the items"
},
{
"name": "items",
"type": "list",
"description": "Items to sort",
"required": true,
"items": {
"name": "",
"type": "object",
"fields": [
{
"name": "id",
"type": "string",
"description": "Unique item identifier"
},
{
"name": "description",
"type": "string",
"description": "Item label",
"required": true
},
{
"name": "position",
"type": "int",
"description": "Correct 1-based position",
"required": true
}
]
}
},
{
"name": "scoring",
"type": "enum",
"description": "How points are awarded",
"enum": [
"all_or_nothing",
"partial"
]
}
]
},
{
"type": "start_button",
"name": "Start Game Button",
"description": "The button players tap to join and start playing the game.",
"contexts": [
"start"
],
"fields": [
{
"name": "scheduled_text",
"type": "string",
"description": "Button label shown before the game starts"
},
{
"name": "active_text",
"type": "string",
"description": "Button label shown when the game is active"
},
{
"name": "style",
"type": "enum",
"description": "Button visual style",
"enum": [
"primary",
"secondary",
"accent"
]
}
]
},
{
"type": "task",
"name": "Task",
"description": "A single task item in a scavenger hunt task list.",
"contexts": [
"task"
],
"fields": [
{
"name": "task",
"type": "string",
"description": "Task description shown to players",
"required": true
},
{
"name": "icon",
"type": "string",
"description": "Icon name (Lucide icon)"
},
{
"name": "link_through",
"type": "bool",
"description": "If true, tapping takes the player to the task location"
}
]
},
{
"type": "team_name",
"name": "Team Name",
"description": "Displays the team name and optionally allows players to change it.",
"contexts": [
"start"
],
"fields": [
{
"name": "prompt",
"type": "string",
"description": "Label shown above the team name field"
},
{
"name": "allow_changing",
"type": "bool",
"description": "If true, players can edit their team name"
}
]
},
{
"type": "text",
"name": "Markdown",
"description": "Renders Markdown content. Supports headings, lists, links, bold, italic, and images.",
"contexts": [
"location_content",
"location_clues",
"finish",
"start"
],
"fields": [
{
"name": "content",
"type": "markdown",
"description": "Markdown text to display",
"required": true
}
]
},
{
"type": "toggle_text",
"name": "Toggle Text",
"description": "Collapsible text section with a title and hidden content.",
"contexts": [
"location_content",
"location_clues",
"start",
"finish"
],
"fields": [
{
"name": "title",
"type": "string",
"description": "Visible toggle button label",
"required": true
},
{
"name": "content",
"type": "markdown",
"description": "Hidden content revealed when toggled",
"required": true
},
{
"name": "small",
"type": "bool",
"description": "Use a smaller toggle style"
}
]
},
{
"type": "youtube",
"name": "YouTube",
"description": "Embeds a YouTube video.",
"contexts": [
"location_content",
"finish",
"start"
],
"fields": [
{
"name": "url",
"type": "string",
"description": "YouTube video URL or embed URL",
"required": true
}
]
}
]
}