Files
jackboxpartypack-gamepicker/docs/api/openapi.yaml

1694 lines
51 KiB
YAML
Raw Normal View History

openapi: 3.1.0
info:
title: Jackbox Game Picker API
description: API for managing Jackbox Party Pack games, sessions, voting, and integrations.
version: "1.0"
servers:
- url: http://localhost:5000
description: Local development (backend direct)
- url: http://localhost:3000/api
description: Docker Compose (via Vite/Nginx proxy)
tags:
- name: Health
description: Server health check
- name: Auth
description: Authentication and token verification
- name: Games
description: Game management, filtering, and pack metadata
- name: Sessions
description: Session lifecycle and game tracking
- name: Picker
description: Weighted random game selection
- name: Stats
description: Aggregate statistics
- name: Votes
description: Real-time popularity voting
- name: Webhooks
description: Webhook management for external integrations
components:
responses:
Unauthorized:
description: No access token provided
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Access token required" }
Forbidden:
description: Invalid or expired token
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Invalid or expired token" }
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: >
JWT token obtained from POST /api/auth/login. Pass as Authorization Bearer <token>.
Tokens expire after 24 hours.
schemas:
Error:
type: object
required:
- error
properties:
error:
type: string
Game:
type: object
properties:
id:
type: integer
pack_name:
type: string
title:
type: string
min_players:
type: integer
max_players:
type: integer
length_minutes:
type:
- integer
- "null"
has_audience:
type: integer
enum: [0, 1]
family_friendly:
type: integer
enum: [0, 1]
game_type:
type:
- string
- "null"
secondary_type:
type:
- string
- "null"
play_count:
type: integer
popularity_score:
type: integer
upvotes:
type: integer
downvotes:
type: integer
enabled:
type: integer
enum: [0, 1]
favor_bias:
type: integer
enum: [-1, 0, 1]
created_at:
type: string
format: date-time
Session:
type: object
properties:
id:
type: integer
created_at:
type: string
format: date-time
closed_at:
type:
- string
- "null"
format: date-time
is_active:
type: integer
enum: [0, 1]
notes:
type:
- string
- "null"
games_played:
type: integer
SessionGame:
type: object
properties:
id:
type: integer
session_id:
type: integer
game_id:
type: integer
played_at:
type: string
format: date-time
manually_added:
type: integer
enum: [0, 1]
status:
type: string
enum: [playing, played, skipped]
room_code:
type:
- string
- "null"
player_count:
type:
- integer
- "null"
player_count_check_status:
type:
- string
- "null"
pack_name:
type: string
title:
type: string
game_type:
type:
- string
- "null"
min_players:
type: integer
max_players:
type: integer
popularity_score:
type: integer
upvotes:
type: integer
downvotes:
type: integer
Pack:
type: object
properties:
id:
type: integer
name:
type: string
favor_bias:
type: integer
enum: [-1, 0, 1]
created_at:
type: string
format: date-time
PackMeta:
type: object
properties:
name:
type: string
total_count:
type: integer
enabled_count:
type: integer
total_plays:
type: integer
Webhook:
type: object
properties:
id:
type: integer
name:
type: string
url:
type: string
format: uri
events:
type: array
items:
type: string
enabled:
type: boolean
created_at:
type: string
format: date-time
WebhookLog:
type: object
properties:
id:
type: integer
webhook_id:
type: integer
event_type:
type: string
payload:
type: object
response_status:
type:
- integer
- "null"
error_message:
type:
- string
- "null"
created_at:
type: string
format: date-time
paths:
/health:
get:
operationId: getHealth
summary: Health check
tags: [Health]
responses:
"200":
description: API is running
content:
application/json:
schema:
type: object
required: [status, message]
properties:
status: { type: string, example: ok }
message: { type: string, example: "Jackbox Game Picker API is running" }
/api/auth/login:
post:
operationId: loginWithAdminKey
summary: Authenticate with admin key
tags: [Auth]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [key]
properties:
key: { type: string }
responses:
"200":
description: Authentication successful
content:
application/json:
schema:
type: object
required: [token, message, expiresIn]
properties:
token: { type: string }
message: { type: string, example: "Authentication successful" }
expiresIn: { type: string, example: "24h" }
"400":
description: Admin key is required
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Admin key is required" }
"401":
description: Invalid admin key
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Invalid admin key" }
/api/auth/verify:
post:
operationId: verifyToken
summary: Verify JWT token
tags: [Auth]
security: [{ bearerAuth: [] }]
responses:
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"200":
description: Token is valid
content:
application/json:
schema:
type: object
required: [valid, user]
properties:
valid: { type: boolean, example: true }
user:
type: object
properties:
role: { type: string, example: admin }
timestamp: { type: number }
/api/games:
get:
operationId: listGames
summary: List games with optional filters
tags: [Games]
parameters:
- name: enabled
in: query
schema: { type: string, enum: ["true", "false"] }
- name: playerCount
in: query
schema: { type: integer }
- name: drawing
in: query
schema: { type: string, enum: ["only", "exclude"] }
- name: length
in: query
schema: { type: string, enum: ["short", "medium", "long"] }
- name: familyFriendly
in: query
schema: { type: string, enum: ["true", "false"] }
- name: pack
in: query
schema: { type: string }
responses:
"200":
description: Array of games ordered by pack_name, title
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Game" }
post:
operationId: createGame
summary: Create a new game
tags: [Games]
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [pack_name, title, min_players, max_players]
properties:
pack_name: { type: string }
title: { type: string }
min_players: { type: integer }
max_players: { type: integer }
length_minutes: { type: integer }
has_audience: { type: boolean }
family_friendly: { type: boolean }
game_type: { type: string }
secondary_type: { type: string }
responses:
"201":
description: Game created
content:
application/json:
schema: { $ref: "#/components/schemas/Game" }
"400":
description: Missing required fields
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Missing required fields" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/games/packs:
get:
operationId: listPacks
summary: List all packs
tags: [Games]
responses:
"200":
description: Array of packs
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Pack" }
/api/games/meta/packs:
get:
operationId: listPackMeta
summary: List pack metadata
tags: [Games]
responses:
"200":
description: Array of pack metadata
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/PackMeta" }
/api/games/export/csv:
get:
operationId: exportGamesCsv
summary: Export games as CSV
tags: [Games]
security: [{ bearerAuth: [] }]
responses:
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"200":
description: CSV file download
headers:
Content-Disposition:
schema:
type: string
example: 'attachment; filename="jackbox-games.csv"'
content:
text/csv:
schema:
type: string
description: CSV with columns Pack Name, Title, Min Players, Max Players, Length (minutes), Audience, Family Friendly, Game Type, Secondary Type
/api/games/import/csv:
post:
operationId: importGamesCsv
summary: Import games from CSV
tags: [Games]
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [csvData]
properties:
csvData: { type: string }
mode: { type: string, enum: ["append", "replace"], description: Optional; defaults to append if absent }
responses:
"200":
description: Import successful
content:
application/json:
schema:
type: object
required: [message, count, mode]
properties:
message: { type: string }
count: { type: integer }
mode: { type: string }
"400":
description: CSV data required
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "CSV data required" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/games/packs/{name}/favor:
patch:
operationId: updatePackFavor
summary: Update pack favor bias
tags: [Games]
security: [{ bearerAuth: [] }]
parameters:
- name: name
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [favor_bias]
properties:
favor_bias: { type: integer, enum: [-1, 0, 1] }
responses:
"200":
description: Pack favor bias updated
content:
application/json:
schema:
type: object
required: [message, favor_bias]
properties:
message: { type: string, example: "Pack favor bias updated successfully" }
favor_bias: { type: integer }
"400":
description: Invalid favor_bias
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "favor_bias must be 1 (favor), -1 (disfavor), or 0 (neutral)" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/games/packs/{name}/toggle:
patch:
operationId: togglePack
summary: Enable or disable a pack
tags: [Games]
security: [{ bearerAuth: [] }]
parameters:
- name: name
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled: { type: boolean }
responses:
"200":
description: Pack enabled/disabled
content:
application/json:
schema:
type: object
required: [message, gamesAffected]
properties:
message: { type: string }
gamesAffected: { type: integer }
"400":
description: enabled status required
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "enabled status required" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/games/{id}:
get:
operationId: getGame
summary: Get a game by ID
tags: [Games]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Game found
content:
application/json:
schema: { $ref: "#/components/schemas/Game" }
"404":
description: Game not found
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Game not found" }
put:
operationId: updateGame
summary: Update a game
tags: [Games]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
content:
application/json:
schema:
type: object
properties:
pack_name: { type: string }
title: { type: string }
min_players: { type: integer }
max_players: { type: integer }
length_minutes: { type: integer }
has_audience: { type: boolean }
family_friendly: { type: boolean }
game_type: { type: string }
secondary_type: { type: string }
enabled: { type: boolean }
responses:
"200":
description: Game updated
content:
application/json:
schema: { $ref: "#/components/schemas/Game" }
"404":
description: Game not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
delete:
operationId: deleteGame
summary: Delete a game
tags: [Games]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Game deleted
content:
application/json:
schema:
type: object
required: [message]
properties:
message: { type: string, example: "Game deleted successfully" }
"404":
description: Game not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/games/{id}/toggle:
patch:
operationId: toggleGame
summary: Toggle game enabled status
tags: [Games]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Game updated
content:
application/json:
schema: { $ref: "#/components/schemas/Game" }
"404":
description: Game not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/games/{id}/favor:
patch:
operationId: updateGameFavor
summary: Update game favor bias
tags: [Games]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [favor_bias]
properties:
favor_bias: { type: integer, enum: [-1, 0, 1] }
responses:
"200":
description: Favor bias updated
content:
application/json:
schema:
type: object
required: [message, favor_bias]
properties:
message: { type: string, example: "Favor bias updated successfully" }
favor_bias: { type: integer }
"400":
description: Invalid favor_bias
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Game not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions:
get:
operationId: listSessions
summary: List all sessions
tags: [Sessions]
responses:
"200":
description: Array of sessions with games_played
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Session" }
post:
operationId: createSession
summary: Create a new session
tags: [Sessions]
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
properties:
notes: { type: string }
responses:
"201":
description: Session created
content:
application/json:
schema: { $ref: "#/components/schemas/Session" }
"400":
description: Active session already exists
content:
application/json:
schema:
type: object
required: [error, activeSessionId]
properties:
error: { type: string }
activeSessionId: { type: integer }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/active:
get:
operationId: getActiveSession
summary: Get the active session
tags: [Sessions]
responses:
"200":
description: Active session (raw object) or null with message when no session
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/Session"
- type: object
required: [session, message]
properties:
session: { type: "null" }
message: { type: string, example: "No active session" }
/api/sessions/{id}:
get:
operationId: getSession
summary: Get a session by ID
tags: [Sessions]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Session found
content:
application/json:
schema: { $ref: "#/components/schemas/Session" }
"404":
description: Session not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
delete:
operationId: deleteSession
summary: Delete a session
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Session deleted
content:
application/json:
schema:
type: object
required: [message, sessionId]
properties:
message: { type: string, example: "Session deleted successfully" }
sessionId: { type: integer }
"404":
description: Session not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"400":
description: Cannot delete active session
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Cannot delete an active session. Please close it first." }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{id}/close:
post:
operationId: closeSession
summary: Close a session
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
content:
application/json:
schema:
type: object
properties:
notes: { type: string }
responses:
"200":
description: Session closed with games_played
content:
application/json:
schema: { $ref: "#/components/schemas/Session" }
"404":
description: Session not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"400":
description: Session already closed
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Session is already closed" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{id}/games:
get:
operationId: listSessionGames
summary: List games in a session
tags: [Sessions]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Array of session games
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/SessionGame" }
"404":
description: Session not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
post:
operationId: addGameToSession
summary: Add a game to a session
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [game_id]
properties:
game_id: { type: integer }
manually_added: { type: boolean }
room_code: { type: string }
responses:
"201":
description: Game added to session
content:
application/json:
schema: { $ref: "#/components/schemas/SessionGame" }
"400":
description: Closed session or missing game_id
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Session or game not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{id}/chat-import:
post:
operationId: importSessionChat
summary: Import chat log for vote processing
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [chatData]
properties:
chatData:
type: array
items:
type: object
required: [username, message, timestamp]
properties:
username: { type: string }
message: { type: string }
timestamp: { type: string }
responses:
"200":
description: Chat imported and processed
content:
application/json:
schema:
type: object
required: [message, messagesImported, duplicatesSkipped, votesProcessed, votesByGame, debug]
properties:
message: { type: string }
messagesImported: { type: integer }
duplicatesSkipped: { type: integer }
votesProcessed: { type: integer }
votesByGame: { type: object }
debug: { type: object }
"400":
description: |
Two distinct cases:
- "chatData must be an array" — when chatData is missing or not an array
- "No games played in this session to match votes against" — when session has no games
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Session not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{id}/export:
get:
operationId: exportSession
summary: Export session
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
- name: format
in: query
schema: { type: string, enum: ["json", "txt"], default: "txt" }
responses:
"200":
description: File download
content:
application/json:
schema: { type: object }
text/plain:
schema: { type: string }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{sessionId}/games/{sessionGameId}/status:
patch:
operationId: updateSessionGameStatus
summary: Update session game status
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: sessionId
in: path
required: true
schema: { type: integer }
- name: sessionGameId
in: path
required: true
description: Session-game row ID (session_games.id), not games.id
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [status]
properties:
status: { type: string, enum: [playing, played, skipped] }
responses:
"200":
description: Status updated
content:
application/json:
schema:
type: object
required: [message, status]
properties:
message: { type: string, example: "Status updated successfully" }
status: { type: string }
"400":
description: Invalid status
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{sessionId}/games/{sessionGameId}:
delete:
operationId: removeGameFromSession
summary: Remove game from session
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: sessionId
in: path
required: true
schema: { type: integer }
- name: sessionGameId
in: path
required: true
description: Session-game row ID (session_games.id), not games.id
schema: { type: integer }
responses:
"200":
description: Game removed
content:
application/json:
schema:
type: object
required: [message]
properties:
message: { type: string, example: "Game removed from session successfully" }
"404":
description: Not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{sessionId}/games/{sessionGameId}/room-code:
patch:
operationId: updateSessionGameRoomCode
summary: Update room code for session game
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: sessionId
in: path
required: true
schema: { type: integer }
- name: sessionGameId
in: path
required: true
description: Session-game row ID (session_games.id), not games.id
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [room_code]
properties:
room_code: { type: string, pattern: "^[A-Z0-9]{4}$" }
responses:
"200":
description: Room code updated
content:
application/json:
schema: { $ref: "#/components/schemas/SessionGame" }
"400":
description: Missing or invalid room code format
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check:
post:
operationId: startPlayerCheck
summary: Start room monitor for player count
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: sessionId
in: path
required: true
schema: { type: integer }
- name: sessionGameId
in: path
required: true
description: Session-game row ID (session_games.id), not games.id
schema: { type: integer }
responses:
"200":
description: Room monitor started
content:
application/json:
schema:
type: object
required: [message, status]
properties:
message: { type: string, example: "Room monitor started" }
status: { type: string, example: monitoring }
"400":
description: Game does not have a room code
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string, example: "Game does not have a room code" }
"404":
description: Not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check:
post:
operationId: stopPlayerCheck
summary: Stop room monitor
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: sessionId
in: path
required: true
schema: { type: integer }
- name: sessionGameId
in: path
required: true
description: Session-game row ID (session_games.id), not games.id
schema: { type: integer }
responses:
"200":
description: Monitor stopped
content:
application/json:
schema:
type: object
required: [message, status]
properties:
message: { type: string, example: "Room monitor and player count check stopped" }
status: { type: string, example: stopped }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/sessions/{sessionId}/games/{sessionGameId}/player-count:
patch:
operationId: updateSessionGamePlayerCount
summary: Update player count for session game
tags: [Sessions]
security: [{ bearerAuth: [] }]
parameters:
- name: sessionId
in: path
required: true
schema: { type: integer }
- name: sessionGameId
in: path
required: true
description: Session-game row ID (session_games.id), not games.id
schema: { type: integer }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [player_count]
properties:
player_count: { type: integer, minimum: 0 }
responses:
"200":
description: Player count updated
content:
application/json:
schema:
type: object
required: [message, player_count]
properties:
message: { type: string, example: "Player count updated successfully" }
player_count: { type: integer }
"400":
description: Missing or invalid player count
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/pick:
post:
operationId: pickRandomGame
summary: Pick a random game with optional filters
tags: [Picker]
requestBody:
content:
application/json:
schema:
type: object
properties:
playerCount: { type: integer }
drawing: { type: string }
length: { type: string }
familyFriendly: { type: boolean }
sessionId: { type: integer }
excludePlayed: { type: boolean }
responses:
"200":
description: Random game picked
content:
application/json:
schema:
type: object
required: [game, poolSize, totalEnabled]
properties:
game: { $ref: "#/components/schemas/Game" }
poolSize: { type: integer }
totalEnabled: { type: integer }
"404":
description: No matching games
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string }
suggestion: { type: string }
recentlyPlayed: { type: array, items: { type: integer } }
/api/stats:
get:
operationId: getStats
summary: Get aggregate statistics
tags: [Stats]
responses:
"200":
description: Stats object
content:
application/json:
schema:
type: object
required: [games, gamesEnabled, packs, sessions, activeSessions, totalGamesPlayed, mostPlayedGames, topRatedGames]
properties:
games: { type: object, properties: { count: { type: integer } } }
gamesEnabled: { type: object, properties: { count: { type: integer } } }
packs: { type: object, properties: { count: { type: integer } } }
sessions: { type: object, properties: { count: { type: integer } } }
activeSessions: { type: object, properties: { count: { type: integer } } }
totalGamesPlayed: { type: object, properties: { count: { type: integer } } }
mostPlayedGames:
type: array
items:
type: object
properties:
id: { type: integer }
title: { type: string }
pack_name: { type: string }
play_count: { type: integer }
popularity_score: { type: integer }
upvotes: { type: integer }
downvotes: { type: integer }
topRatedGames:
type: array
items:
type: object
properties:
id: { type: integer }
title: { type: string }
pack_name: { type: string }
play_count: { type: integer }
popularity_score: { type: integer }
upvotes: { type: integer }
downvotes: { type: integer }
/api/votes/live:
post:
operationId: recordLiveVote
summary: Record a live vote (up/down)
tags: [Votes]
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [username, vote, timestamp]
properties:
username: { type: string }
vote: { type: string, enum: [up, down] }
timestamp: { type: string, format: date-time, description: ISO 8601 }
responses:
"200":
description: Vote recorded
content:
application/json:
schema:
type: object
required: [success, message, session, game, vote]
properties:
success: { type: boolean, example: true }
message: { type: string, example: "Vote recorded successfully" }
session:
type: object
properties:
id: { type: integer }
games_played: { type: integer }
game:
type: object
properties:
id: { type: integer }
title: { type: string }
upvotes: { type: integer }
downvotes: { type: integer }
popularity_score: { type: integer }
vote:
type: object
properties:
username: { type: string }
type: { type: string }
timestamp: { type: string }
"400":
description: Missing fields, invalid vote, or invalid timestamp
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: No active session, no games, or no matching game (vote timestamp doesn't match)
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string }
debug:
type: object
description: Present when vote timestamp doesn't match any game
properties:
voteTimestamp: { type: string }
sessionGames:
type: array
items:
type: object
properties:
title: { type: string }
played_at: { type: string }
"409":
description: Duplicate vote within 1 second
content:
application/json:
schema:
type: object
required: [error]
properties:
error: { type: string }
message: { type: string }
timeSinceLastVote: { type: number }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/webhooks:
get:
operationId: listWebhooks
summary: List all webhooks
tags: [Webhooks]
security: [{ bearerAuth: [] }]
responses:
"200":
description: Array of webhooks
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Webhook" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
operationId: createWebhook
summary: Create a webhook
tags: [Webhooks]
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, url, secret, events]
properties:
name: { type: string }
url: { type: string, format: uri }
secret: { type: string }
events:
type: array
items: { type: string }
responses:
"201":
description: Webhook created
content:
application/json:
schema:
allOf:
- { $ref: "#/components/schemas/Webhook" }
- type: object
properties:
message: { type: string, example: "Webhook created successfully" }
"400":
description: Invalid input
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/webhooks/{id}:
get:
operationId: getWebhook
summary: Get a webhook by ID
tags: [Webhooks]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Webhook found
content:
application/json:
schema: { $ref: "#/components/schemas/Webhook" }
"404":
description: Webhook not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
patch:
operationId: updateWebhook
summary: Update a webhook
tags: [Webhooks]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
requestBody:
content:
application/json:
schema:
type: object
properties:
name: { type: string }
url: { type: string, format: uri }
secret: { type: string }
events:
type: array
items: { type: string }
enabled: { type: boolean }
responses:
"200":
description: Webhook updated
content:
application/json:
schema:
allOf:
- { $ref: "#/components/schemas/Webhook" }
- type: object
properties:
message: { type: string, example: "Webhook updated successfully" }
"400":
description: No fields, invalid URL, or events not array
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"404":
description: Webhook not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
delete:
operationId: deleteWebhook
summary: Delete a webhook
tags: [Webhooks]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Webhook deleted
content:
application/json:
schema:
type: object
required: [message, webhookId]
properties:
message: { type: string, example: "Webhook deleted successfully" }
webhookId: { type: integer }
"404":
description: Webhook not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
/api/webhooks/test/{id}:
post:
operationId: testWebhook
summary: Send test webhook
tags: [Webhooks]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
"200":
description: Test webhook sent
content:
application/json:
schema:
type: object
required: [message, note]
properties:
message: { type: string, example: "Test webhook sent" }
note: { type: string, example: "Check webhook_logs table for delivery status" }
"404":
description: Webhook not found
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/webhooks/{id}/logs:
get:
operationId: listWebhookLogs
summary: List webhook delivery logs
tags: [Webhooks]
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: integer }
- name: limit
in: query
schema: { type: integer, default: 50 }
responses:
"200":
description: Array of webhook logs
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/WebhookLog" }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }