API Reference — World of Mythos
Registers a new player name linked to an email, or updates the email if the name was previously unclaimed.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | 3–12 characters, no spaces or special characters |
email | string | Yes | Valid email address (must contain @) |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"success": true} | Name claimed or email updated |
| 400 | {"error": "Name must be 3–12 characters long"} | Invalid name format |
| 400 | {"error": "Invalid email."} | Invalid email format |
| 409 | {"error": "Name already claimed."} | Name belongs to another email |
| 500 | {"error": "Server error."} | Database error |
Verifies that a name + email pair matches what is stored in the database.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Registered name |
email | string | Yes | Associated email |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"success": true} | Credentials verified |
| 403 | {"error": "Email does not match."} | Wrong email for that name |
| 404 | {"error": "Name not found."} | Name does not exist |
| 500 | {"error": "Server error."} | Database error |
Creates a new game lobby. The creator becomes the admin.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Player name (validated against DB) |
email | string | Yes | Player email (must match DB record) |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"lobby_id": "<3-char-id>"} | Lobby created |
| 400 | {"error": "..."} | Invalid name format |
| 403 | {"error": "This name is already claimed..."} | Email mismatch |
"Verden" is reserved and always joins as a spectator.Joins an existing lobby as a player. Joining after round 1 has started results in spectator status.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Player name |
email | string | Yes | Player email |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "joined"} | Successfully joined |
| 400 | {"error": "Name taken"} | Name already in lobby |
| 403 | {"error": "This name is claimed..."} | Email mismatch |
| 404 | {"error": "Lobby not found"} | Lobby ID does not exist |
Admin removes a player from the lobby before the game starts.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
admin | string | Yes | Name of the admin |
target | string | Yes | Name of player to kick |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "Player kicked"} | Player removed |
| 400 | {"error": "Game already started"} | Cannot kick mid-game |
| 400 | {"error": "You can't kick yourself"} | Self-kick attempt |
| 403 | {"error": "You are not the admin"} | Not the admin |
| 404 | {"error": "Lobby not found"} | Invalid lobby ID |
| 404 | {"error": "Player not found"} | Target not in lobby |
Admin starts the game, advancing the lobby to round 1.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
admin | string | Yes | Name of the admin |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "Game started"} | Game started |
| 400 | {"error": "Game already started"} | Already in progress |
| 403 | {"error": "Only admin can start the game"} | Not admin |
| 404 | {"error": "Lobby not found"} | Invalid lobby ID |
The primary polling endpoint. Returns the complete current state of a lobby and drives automatic game logic — making it the heartbeat of every active game session.
What happens on each call
The endpoint executes five stages before returning data:
Round resolution — what resolve_round() does
Triggered either by timeout (stage 5) or when all alive players have submitted (from submit_choice).
- Sets
round_locked = Trueto prevent concurrent calls. - Applies resource bonuses —
gain_hprestores HP,gain_coinadds coins,gain_attackraises attack. - Executes the attack phase — attackers deal damage; defenders reduce incoming damage; raiders skip this phase.
- Executes the raid phase — raiders deal damage to the boss; boss defeat awards relics and sets
raidwinner. - Eliminates players with HP ≤ 0.
- Checks game-end conditions: one PvP survivor → set
winner; boss HP ≤ 0 → setraidwinner. - Advances
round += 1, sets newround_end_time = now + 40s. - Clears all per-round player state (
submittedAction,submittedResource,messages). - Resets
round_locked = FalseandsubmittedBossMoves = False. - If
gameover, persists final stats to Supabase (games,game_player_stats,playerstables).
Response schema
{
"round": 2,
"players": [ ...player objects... ],
"winner": null,
"raidwinner": null,
"pending_deny": null,
"deny_target": null,
"readyPlayers": ["Alice", "Bob"],
"history": ["Alice created a lobby.", "Bob joined.", "..."],
"round_end_time": "2024-01-01T12:00:40+00:00",
"boss_fight": false,
"gremlin_fight": false,
"start_time": "2024-01-01T12:00:00+00:00",
"gameover": false,
"chat": [ ...chat message objects... ]
}
Response field reference
| Field | Type | Description |
|---|---|---|
round | integer | 0 = waiting in lobby; 1+ = game in progress. |
players | array | All player objects in the lobby (see Player Object below). |
winner | string | null | Name of the PvP winner once one alive non-spectator player remains. null while game is ongoing. |
raidwinner | string | null | Name of the player who dealt the killing blow to the raid boss. null otherwise. |
pending_deny | string | null | Name of the player who currently holds the deny ability and must pick a target this round. |
deny_target | string | null | Name of the player who was most recently denied their choices. Cleared each round. |
readyPlayers | string[] | Names of players who have submitted both action and resource this round. |
history | string[] | Ordered event log for the entire session. Human-readable strings, never cleared. |
round_end_time | ISO 8601 | UTC timestamp when the timer expires and auto-resolution fires. |
boss_fight | boolean | true for cooperative raid lobbies. |
gremlin_fight | boolean | true for solo gremlin encounters. |
start_time | ISO 8601 | UTC timestamp when a scheduled boss fight begins. Not meaningful for PvP lobbies. |
gameover | boolean | true once the game has ended. |
chat | array | Lobby chat messages. Max 100 entries kept. |
Player Object fields
| Field | Type | Description |
|---|---|---|
name | string | Display name. |
hp | integer | Current hit points. |
coins | integer | Current coin count. |
attack | integer | Current attack stat. |
alive | boolean | false once eliminated. |
admin | boolean | true for the lobby creator. |
bot | boolean | true for AI dummy players. |
boss | boolean | true for the raid boss entity. |
spectator | boolean | true for observers with no actions. |
submittedAction | string | Current round action ("" if not yet submitted). Cleared after round resolves. |
submittedResource | string | Current round resource ("" if not yet submitted). Cleared after round resolves. |
target | string | null | Chosen attack target. |
messages | string[] | Private messages for this player this round. Cleared each round. |
personal_history | array | Round-by-round summary of this player's actions and outcomes. |
idle_rounds | integer | Consecutive rounds without a submission. Players with idle_rounds > 1 are skipped in resolution. |
config.lobbies). It is not persisted to Supabase until the game ends. A server restart will lose all active lobbies.
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"history": ["...", "..."]} | Event log returned |
| 404 | {"error": "..."} | Lobby not found |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"player": "<name>", "messages": [...]} | Messages returned |
| 404 | {"error": "..."} | Lobby or player not found |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"player": "<name>", "player_history": [...]} | History returned |
| 404 | {"error": "..."} | Lobby or player not found |
Player submits their action and resource choice for the current round. If all alive players have now submitted, the round resolves immediately without waiting for the timer.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
player | string | Yes | Player name |
action | string | Yes | attack, defend, or raid |
resource | string | Yes | gain_hp, gain_coin, or gain_attack |
target | string | No | Target player name (required when action is attack) |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "choice received"} | Choice accepted |
| 400 | {"error": "Invalid action"} | Unknown action value |
| 400 | {"error": "Invalid resource"} | Unknown resource value |
| 404 | {"error": "Lobby not found"} | Invalid lobby ID |
| 409 | {"error": "Invalid action: the game has ended"} | Game is over |
A player who holds the "deny" ability picks which opponent to block this round.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
player | string | Yes | Must match the name in pending_deny |
target | string | Yes | Player to deny |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"success": true} | Deny applied |
| 400 | {"error": "Invalid"} | Player is not the pending denier |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Admin's name |
lobby_id | string | Yes | Target lobby ID |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "<bot_name> added"} | Bot added |
| 400 | {"error": "Dummy already exists"} | Bot already in lobby |
| 403 | {"error": "Only the admin can add a dummy."} | Not admin |
| 404 | {"error": "Lobby not found"} | Invalid lobby ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Sender's name (must be in the lobby) |
message | string | Yes | Message content (max 200 characters) |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "sent"} | Message delivered |
| 400 | {"error": "Message cannot be empty"} | Empty body |
| 400 | {"error": "Message too long..."} | Exceeds 200 chars |
| 403 | {"error": "You are not in this lobby"} | Not a lobby member |
| 404 | {"error": "Lobby not found"} | Invalid lobby ID |
chat array from get_state. Capped at 100 messages.Joins an existing open raid lobby, or creates a new one if none is available.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Player name (must exist in DB) |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"lobby_id": "...", "start_time": "..."} | Raid lobby joined/created |
| 500 | {"error": "..."} | Server/database error |
{ "start_time": "<ISO 8601 datetime>" }
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Player name |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"lobby_id": "..."} | Gremlin lobby created |
| 400 | {"error": "Name is required"} | Missing name |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Player name |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"relics": [...]} | Relics returned |
| 500 | {"error": "Failed to fetch relics"} | Database error |
Relic fields: id, boss_id, name, power-cathegory, flavour_text, created_at, count
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
code | string or int | Yes | Numeric passkey or the original finder's name |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"first": true} | First person to find this |
| 200 | {"first": false, "seen_before": N, "og_keyfinder": "..."} | Already found before |
| 401 | {"success": false} | Invalid code |
times_found counter. Numeric codes match passkey; string codes match og_keyfinder.Request Body
| Field | Type | Required | Description |
|---|---|---|---|
code | string or int | Yes | Artifact passkey |
name | string | Yes | Name to register |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"success": true} | Name registered |
| 401 | {"success": false} | Artifact not found |
| 403 | {"success": false} | Already claimed by someone else |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
code | string or int | Yes | Artifact passkey |
email | string | Yes | Email to register |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"success": true} | Email registered |
| 401 | {"success": false} | Artifact not found |
| 403 | {"success": false} | Email already registered |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
type | string | No | "monthly" for current month; omit for all-time |
Response (200)
{
"top_wins": [{ "name": "...", "wins": 5 }, ...],
"top_kills": [{ "name": "...", "kills": 12 }, ...],
"top_played": [{ "name": "...", "played_games": 20 }, ...],
"top_raid_wins": [{ "name": "...", "raid_wins": 8 }, ...]
}
game_player_stats. All-time reads totals from the players table.Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Sender's player name |
city_id | int/string | Yes | ID of the city channel |
message | string | Yes | Content (max 200 characters) |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"status": "sent"} | Message sent |
| 400 | {"error": "name, city_id, and message are required"} | Missing fields |
| 400 | {"error": "Message too long..."} | Exceeds 200 chars |
{
"city_id": 1,
"chat": [
{ "sender": "Alice", "message": "Hello!", "timestamp": "2024-01-01T12:00:00+00:00" }
]
}
name + email pair matches the record stored in the players database table. Unregistered names are created on first use via /claim_name. CORS is enabled globally for all origins.