docs: comprehensive API documentation from source code

Replace existing docs with fresh documentation built entirely from source
code analysis. OpenAPI 3.1 spec as source of truth, plus human-readable
Markdown with curl examples, response samples, and workflow guides.

- OpenAPI 3.1 spec covering all 42 endpoints (validated against source)
- 7 endpoint reference docs (auth, games, sessions, picker, stats, votes, webhooks)
- WebSocket protocol documentation (auth, subscriptions, 4 event types)
- 4 guide documents (getting started, session lifecycle, voting, webhooks)
- API README with overview, auth docs, and quick reference table
- Old docs archived to docs/archive/

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-15 16:44:53 -04:00
parent 505c335d20
commit 8ba32e128c
25 changed files with 6546 additions and 0 deletions

135
docs/api/endpoints/auth.md Normal file
View File

@@ -0,0 +1,135 @@
# Auth Endpoints
Simple admin-key authentication. Single role (admin). No user management. Obtain a JWT from `POST /api/auth/login` and use it as a Bearer token for protected endpoints.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/auth/login` | No | Exchange admin key for JWT |
| POST | `/api/auth/verify` | Bearer | Validate token and return user info |
---
## POST /api/auth/login
Exchange an admin key for a JWT. Use the returned token in the `Authorization: Bearer <token>` header for protected routes. Tokens expire after 24 hours.
### Authentication
None. This endpoint is public.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| key | string | Yes | Admin key (configured via `ADMIN_KEY` env) |
```json
{
"key": "your-admin-key"
}
```
### Response
**200 OK**
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"message": "Authentication successful",
"expiresIn": "24h"
}
```
| Field | Description |
|-------|-------------|
| token | JWT to use in `Authorization: Bearer <token>` |
| message | Success message |
| expiresIn | Token lifetime |
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Admin key is required" }` | `key` field missing |
| 401 | `{ "error": "Invalid admin key" }` | Wrong key |
### Example
```bash
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"key": "your-admin-key"}'
```
**Sample response:**
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJ0aW1lc3RhbXAiOjE3MTAwMDAwMDAwLCJpYXQiOjE3MTAwMDAwMDB9.abc123",
"message": "Authentication successful",
"expiresIn": "24h"
}
```
---
## POST /api/auth/verify
Verify that the provided Bearer token is valid and return the decoded user payload.
### Authentication
Bearer token required. Include in header: `Authorization: Bearer <token>`.
### Parameters
None.
### Response
**200 OK**
```json
{
"valid": true,
"user": {
"role": "admin",
"timestamp": 1710000000000
}
}
```
| Field | Description |
|-------|-------------|
| valid | Always `true` when token is valid |
| user.role | User role (always `"admin"`) |
| user.timestamp | Unix ms when token was issued |
### Error Responses
| Status | Body | When |
|--------|------|------|
| 401 | `{ "error": "Access token required" }` | No `Authorization` header or Bearer token |
| 403 | `{ "error": "Invalid or expired token" }` | Bad or expired token |
### Example
```bash
curl -X POST http://localhost:5000/api/auth/verify \
-H "Authorization: Bearer $TOKEN"
```
**Sample response:**
```json
{
"valid": true,
"user": {
"role": "admin",
"timestamp": 1710000000000
}
}
```

627
docs/api/endpoints/games.md Normal file
View File

@@ -0,0 +1,627 @@
# Games Endpoints
Manage the Jackbox game catalog. Games belong to packs (e.g., "Jackbox Party Pack 7"). Each game has player limits, type, audience support, family-friendliness, and favor bias for weighted selection. Packs can also have favor/disfavor bias to influence the picker.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/games` | No | List games with optional filters |
| GET | `/api/games/packs` | No | List all packs |
| GET | `/api/games/meta/packs` | No | Pack metadata (counts, plays) |
| GET | `/api/games/export/csv` | Bearer | Export games as CSV |
| PATCH | `/api/games/packs/{name}/favor` | Bearer | Set pack favor bias |
| GET | `/api/games/{id}` | No | Get single game |
| POST | `/api/games` | Bearer | Create game |
| PUT | `/api/games/{id}` | Bearer | Update game |
| DELETE | `/api/games/{id}` | Bearer | Delete game |
| PATCH | `/api/games/{id}/toggle` | Bearer | Toggle game enabled status |
| PATCH | `/api/games/packs/{name}/toggle` | Bearer | Enable/disable all games in pack |
| POST | `/api/games/import/csv` | Bearer | Import games from CSV |
| PATCH | `/api/games/{id}/favor` | Bearer | Set game favor bias |
---
## GET /api/games
List all games with optional query filters. Results are ordered by `pack_name`, then `title`.
### Authentication
None.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| enabled | query | string | No | `"true"` or `"false"` to filter by enabled status |
| playerCount | query | integer | No | Filter games where `min_players ≤ count ≤ max_players` |
| drawing | query | string | No | `"only"` = `game_type='Drawing'`, `"exclude"` = exclude Drawing |
| length | query | string | No | `"short"` (≤15 min or NULL), `"medium"` (1625 min), `"long"` (>25 min) |
| familyFriendly | query | string | No | `"true"` or `"false"` |
| pack | query | string | No | Exact `pack_name` match |
### Response
**200 OK**
```json
[
{
"id": 1,
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"min_players": 3,
"max_players": 8,
"length_minutes": 15,
"has_audience": 1,
"family_friendly": 0,
"game_type": "Writing",
"secondary_type": null,
"play_count": 0,
"popularity_score": 0,
"enabled": 1,
"favor_bias": 0,
"created_at": "2024-01-15T12:00:00.000Z"
}
]
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 500 | `{ "error": "..." }` | Server error |
### Example
```bash
curl "http://localhost:5000/api/games?playerCount=6&pack=Jackbox%20Party%20Pack%207"
```
---
## GET /api/games/packs
List all packs with their favor bias.
### Authentication
None.
### Response
**200 OK**
```json
[
{
"id": 1,
"name": "Jackbox Party Pack 7",
"favor_bias": 0,
"created_at": "2024-01-15T12:00:00.000Z"
}
]
```
---
## GET /api/games/meta/packs
Return pack metadata: total game count, enabled count, and total plays per pack.
### Authentication
None.
### Response
**200 OK**
```json
[
{
"name": "Jackbox Party Pack 7",
"total_count": 5,
"enabled_count": 5,
"total_plays": 42
}
]
```
---
## GET /api/games/export/csv
Export the full game catalog as a CSV file download.
### Authentication
Bearer token required.
### Response
**200 OK**
- Content-Type: `text/csv`
- Content-Disposition: `attachment; filename="jackbox-games.csv"`
- Columns: Pack Name, Title, Min Players, Max Players, Length (minutes), Audience, Family Friendly, Game Type, Secondary Type
### Example
```bash
curl -o jackbox-games.csv "http://localhost:5000/api/games/export/csv" \
-H "Authorization: Bearer $TOKEN"
```
---
## PATCH /api/games/packs/{name}/favor
Set favor bias for a pack. Affects weighted random selection in the picker.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| name | string | Pack name (exact match, URL-encode if spaces) |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| favor_bias | integer | Yes | `1` = favor, `-1` = disfavor, `0` = neutral |
```json
{
"favor_bias": 1
}
```
### Response
**200 OK**
```json
{
"message": "Pack favor bias updated successfully",
"favor_bias": 1
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "favor_bias must be 1 (favor), -1 (disfavor), or 0 (neutral)" }` | Invalid value |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/games/packs/Jackbox%20Party%20Pack%207/favor" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"favor_bias": 1}'
```
---
## GET /api/games/{id}
Get a single game by ID.
### Authentication
None.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Game ID |
### Response
**200 OK**
```json
{
"id": 1,
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"min_players": 3,
"max_players": 8,
"length_minutes": 15,
"has_audience": 1,
"family_friendly": 0,
"game_type": "Writing",
"secondary_type": null,
"play_count": 0,
"popularity_score": 0,
"enabled": 1,
"favor_bias": 0,
"created_at": "2024-01-15T12:00:00.000Z"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Game not found" }` | Invalid ID |
### Example
```bash
curl "http://localhost:5000/api/games/1"
```
---
## POST /api/games
Create a new game. Pack is created automatically if it does not exist.
### Authentication
Bearer token required.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| pack_name | string | Yes | Pack name (e.g., "Jackbox Party Pack 7") |
| title | string | Yes | Game title |
| min_players | integer | Yes | Minimum players |
| max_players | integer | Yes | Maximum players |
| length_minutes | integer | No | Approx. play length |
| has_audience | boolean | No | Audience mode supported |
| family_friendly | boolean | No | Family-friendly rating |
| game_type | string | No | Primary type (e.g., "Writing", "Drawing") |
| secondary_type | string | No | Secondary type |
```json
{
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"min_players": 3,
"max_players": 8,
"length_minutes": 15,
"has_audience": true,
"family_friendly": false,
"game_type": "Writing",
"secondary_type": null
}
```
### Response
**201 Created**
Returns the created game object.
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Missing required fields" }` | Missing pack_name, title, min_players, or max_players |
### Example
```bash
curl -X POST "http://localhost:5000/api/games" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"min_players": 3,
"max_players": 8,
"length_minutes": 15,
"has_audience": true,
"family_friendly": false,
"game_type": "Writing",
"secondary_type": null
}'
```
---
## PUT /api/games/{id}
Update a game. All fields are optional; uses COALESCE (only provided fields are updated).
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Game ID |
### Request Body
All fields optional. Include only the fields to update.
```json
{
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"min_players": 3,
"max_players": 8,
"length_minutes": 20,
"has_audience": true,
"family_friendly": false,
"game_type": "Writing",
"secondary_type": null,
"enabled": true
}
```
### Response
**200 OK**
Returns the updated game object.
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Game not found" }` | Invalid ID |
### Example
```bash
curl -X PUT "http://localhost:5000/api/games/1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"length_minutes": 20, "enabled": true}'
```
---
## DELETE /api/games/{id}
Delete a game permanently.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Game ID |
### Response
**200 OK**
```json
{
"message": "Game deleted successfully"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Game not found" }` | Invalid ID |
### Example
```bash
curl -X DELETE "http://localhost:5000/api/games/1" \
-H "Authorization: Bearer $TOKEN"
```
---
## PATCH /api/games/{id}/toggle
Toggle the game's `enabled` field (0↔1). Use to quickly enable/disable a game without a full PUT.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Game ID |
### Response
**200 OK**
Returns the updated game object with the flipped `enabled` value.
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Game not found" }` | Invalid ID |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/games/1/toggle" \
-H "Authorization: Bearer $TOKEN"
```
---
## PATCH /api/games/packs/{name}/toggle
Enable or disable all games in a pack at once.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| name | string | Pack name (URL-encode if spaces) |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| enabled | boolean | Yes | `true` to enable, `false` to disable |
```json
{
"enabled": true
}
```
### Response
**200 OK**
```json
{
"message": "Pack enabled successfully",
"gamesAffected": 12
}
```
Message varies: "Pack enabled successfully" or "Pack disabled successfully" based on the `enabled` value.
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "enabled status required" }` | Missing `enabled` field |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/games/packs/Jackbox%20Party%20Pack%207/toggle" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'
```
---
## POST /api/games/import/csv
Import games from CSV data. Default mode is `append`. Use `"replace"` to delete all existing games before importing.
### Authentication
Bearer token required.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| csvData | string | Yes | Raw CSV content (header + rows) |
| mode | string | No | `"append"` (default) or `"replace"` |
**CSV columns:** Game Pack, Game Title, Min. Players, Max. Players, Length, Audience, Family Friendly?, Game Type, Secondary Type
```json
{
"csvData": "Game Pack,Game Title,Min. Players,Max. Players,Length,Audience,Family Friendly?,Game Type,Secondary Type\nJackbox Party Pack 7,Quiplash 3,3,8,15,Yes,No,Writing,\nJackbox Party Pack 7,The Devils and the Details,3,7,25,Yes,No,Strategy,",
"mode": "append"
}
```
### Response
**200 OK**
```json
{
"message": "Successfully imported 5 games",
"count": 5,
"mode": "append"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "CSV data required" }` | Missing `csvData` |
### Example
```bash
curl -X POST "http://localhost:5000/api/games/import/csv" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"csvData": "Game Pack,Game Title,Min. Players,Max. Players,Length,Audience,Family Friendly?,Game Type,Secondary Type\nJackbox Party Pack 7,Quiplash 3,3,8,15,Yes,No,Writing,\nJackbox Party Pack 7,The Devils and the Details,3,7,25,Yes,No,Strategy,",
"mode": "append"
}'
```
---
## PATCH /api/games/{id}/favor
Set favor bias for a single game. Affects weighted random selection in the picker.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Game ID |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| favor_bias | integer | Yes | `1` = favor, `-1` = disfavor, `0` = neutral |
```json
{
"favor_bias": -1
}
```
### Response
**200 OK**
```json
{
"message": "Favor bias updated successfully",
"favor_bias": -1
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "favor_bias must be 1 (favor), -1 (disfavor), or 0 (neutral)" }` | Invalid value |
| 404 | `{ "error": "Game not found" }` | Invalid ID |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/games/1/favor" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"favor_bias": -1}'
```

View File

@@ -0,0 +1,120 @@
# Picker Endpoints
Weighted random game selection. Picks from enabled games matching your filters, with favor bias affecting probability. Avoids recently played games within a session.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/pick` | No | Pick a random game with filters and repeat avoidance |
---
## POST /api/pick
Select a game using weighted random selection. Filters to only enabled games, applies favor/disfavor bias to influence probability, and optionally excludes recently played games when a session is provided.
### Authentication
None.
### Request Body
All fields optional. Provide only the filters you want to apply.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| playerCount | integer | No | Filter games where `min_players ≤ count ≤ max_players` |
| drawing | string | No | `"only"` = Drawing type only, `"exclude"` = exclude Drawing type |
| length | string | No | `"short"` (≤15 min or NULL), `"medium"` (1625 min), `"long"` (>25 min) |
| familyFriendly | boolean | No | Filter by family-friendly rating |
| sessionId | integer | No | Session ID for repeat avoidance |
| excludePlayed | boolean | No | When `true`, exclude ALL games played in session. Default: exclude last 2 only |
```json
{
"playerCount": 6,
"drawing": "exclude",
"length": "short",
"familyFriendly": true,
"sessionId": 3,
"excludePlayed": false
}
```
### Filters
- **Enabled games only:** Only games with `enabled = 1` are considered.
- **playerCount:** Filters games where `min_players ≤ playerCount ≤ max_players`.
- **drawing:** `"only"` = games with `game_type = 'Drawing'`; `"exclude"` = games that are not Drawing type.
- **length:** `"short"` = ≤15 min (includes NULL); `"medium"` = 1625 min; `"long"` = >25 min.
- **familyFriendly:** `true` or `false` filters by `family_friendly`.
### Weighted Selection
- **Game favor_bias:** `1` = 3× weight, `0` = 1× weight, `-1` = 0.2× weight.
- **Pack favor_bias:** `1` = 2× weight, `0` = 1× weight, `-1` = 0.3× weight.
- Game and pack biases multiply together.
### Repeat Avoidance (with sessionId)
- **Default (`excludePlayed: false`):** Excludes the last 2 played games in the session.
- **With `excludePlayed: true`:** Excludes ALL games played in the session.
### Response
**200 OK**
```json
{
"game": {
"id": 42,
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"min_players": 3,
"max_players": 8,
"length_minutes": 15,
"has_audience": 1,
"family_friendly": 0,
"game_type": "Writing",
"secondary_type": null,
"play_count": 0,
"popularity_score": 0,
"enabled": 1,
"favor_bias": 0,
"created_at": "2024-01-15T12:00:00.000Z"
},
"poolSize": 15,
"totalEnabled": 17
}
```
| Field | Description |
|-------|-------------|
| game | Full game object for the selected game |
| poolSize | Number of games in the eligible pool after filters |
| totalEnabled | Approximate total enabled games (includes excluded when sessionId provided) |
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "No games match the current filters", "suggestion": "Try adjusting your filters or enabling more games" }` | No games match the filters |
| 404 | `{ "error": "All eligible games have been played in this session", "suggestion": "Enable more games or adjust your filters", "recentlyPlayed": [1, 5, 12] }` | All eligible games already played in session (when `excludePlayed: true`) |
| 404 | `{ "error": "All eligible games have been played recently", "suggestion": "Enable more games or adjust your filters", "recentlyPlayed": [1, 5] }` | Last 2 games are the only matches (when `excludePlayed: false`) |
| 500 | `{ "error": "..." }` | Server error |
### Example
```bash
curl -X POST "http://localhost:5000/api/pick" \
-H "Content-Type: application/json" \
-d '{
"playerCount": 6,
"drawing": "exclude",
"length": "short",
"familyFriendly": true,
"sessionId": 3,
"excludePlayed": false
}'
```

View File

@@ -0,0 +1,919 @@
# Sessions Endpoints
Sessions represent a gaming night. Only one session can be active at a time. Games are added to the active session as they're played. Sessions track game status, room codes, player counts, and chat logs for voting.
**IMPORTANT:** In session game sub-routes like `/api/sessions/{sessionId}/games/{sessionGameId}/status`, the `sessionGameId` parameter refers to the `session_games.id` row ID, NOT `games.id`.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/sessions` | No | List all sessions with games_played count |
| GET | `/api/sessions/active` | No | Get the active session (or null) |
| GET | `/api/sessions/{id}` | No | Get a session by ID |
| POST | `/api/sessions` | Bearer | Create a new session |
| POST | `/api/sessions/{id}/close` | Bearer | Close a session |
| DELETE | `/api/sessions/{id}` | Bearer | Delete a closed session |
| GET | `/api/sessions/{id}/games` | No | List games in a session |
| POST | `/api/sessions/{id}/games` | Bearer | Add a game to a session |
| POST | `/api/sessions/{id}/chat-import` | Bearer | Import chat log for vote processing |
| GET | `/api/sessions/{id}/export` | Bearer | Export session (JSON or TXT) |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/status` | Bearer | Update session game status |
| DELETE | `/api/sessions/{sessionId}/games/{sessionGameId}` | Bearer | Remove game from session |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/room-code` | Bearer | Update room code for session game |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check` | Bearer | Start room monitor |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check` | Bearer | Stop room monitor |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/player-count` | Bearer | Update player count for session game |
---
## GET /api/sessions
List all sessions with a `games_played` count. Ordered by `created_at` DESC.
### Authentication
None.
### Response
**200 OK**
```json
[
{
"id": 5,
"notes": "Friday game night",
"is_active": 1,
"created_at": "2026-03-15T19:00:00.000Z",
"closed_at": null,
"games_played": 3
},
{
"id": 4,
"notes": "Last week's session",
"is_active": 0,
"created_at": "2026-03-08T18:30:00.000Z",
"closed_at": "2026-03-08T23:15:00.000Z",
"games_played": 5
}
]
```
### Example
```bash
curl "http://localhost:5000/api/sessions"
```
---
## GET /api/sessions/active
Get the active session. Returns the session object directly if one is active, or a wrapper with `session: null` if none.
### Authentication
None.
### Response
**200 OK** (active session exists)
```json
{
"id": 5,
"notes": "Friday game night",
"is_active": 1,
"created_at": "2026-03-15T19:00:00.000Z",
"closed_at": null,
"games_played": 3
}
```
**200 OK** (no active session)
```json
{
"session": null,
"message": "No active session"
}
```
### Example
```bash
curl "http://localhost:5000/api/sessions/active"
```
---
## GET /api/sessions/{id}
Get a single session by ID.
### Authentication
None.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Response
**200 OK**
```json
{
"id": 5,
"notes": "Friday game night",
"is_active": 1,
"created_at": "2026-03-15T19:00:00.000Z",
"closed_at": null,
"games_played": 3
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
### Example
```bash
curl "http://localhost:5000/api/sessions/5"
```
---
## POST /api/sessions
Create a new session. Only one active session is allowed at a time. Triggers WebSocket `session.started` broadcast to all authenticated clients.
### Authentication
Bearer token required.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| notes | string | No | Optional notes (e.g., "Friday game night") |
```json
{
"notes": "Friday game night"
}
```
### Response
**201 Created**
```json
{
"id": 5,
"notes": "Friday game night",
"is_active": 1,
"created_at": "2026-03-15T19:00:00.000Z",
"closed_at": null
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "An active session already exists. Please close it before creating a new one.", "activeSessionId": 5 }` | An active session already exists |
### Example
```bash
curl -X POST "http://localhost:5000/api/sessions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"notes": "Friday game night"}'
```
---
## POST /api/sessions/{id}/close
Close a session. Auto-sets all games with status `playing` to `played`. Optional body updates session notes. Triggers WebSocket `session.ended` broadcast to session subscribers.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| notes | string | No | Optional notes (updates session notes) |
```json
{
"notes": "Great session!"
}
```
### Response
**200 OK**
```json
{
"id": 5,
"notes": "Great session!",
"is_active": 0,
"created_at": "2026-03-15T19:00:00.000Z",
"closed_at": "2026-03-15T23:30:00.000Z",
"games_played": 4
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Session is already closed" }` | Session was already closed |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
### Example
```bash
curl -X POST "http://localhost:5000/api/sessions/5/close" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"notes": "Great session!"}'
```
---
## DELETE /api/sessions/{id}
Delete a session. Cannot delete active sessions — close first. Cascades: deletes `chat_logs` and `session_games`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Response
**200 OK**
```json
{
"message": "Session deleted successfully",
"sessionId": 5
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Cannot delete an active session. Please close it first." }` | Session is active |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
### Example
```bash
curl -X DELETE "http://localhost:5000/api/sessions/5" \
-H "Authorization: Bearer $TOKEN"
```
---
## GET /api/sessions/{id}/games
List all games in a session. Returns SessionGame objects joined with game data. Ordered by `played_at` ASC.
### Authentication
None.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Response
**200 OK**
```json
[
{
"id": 12,
"session_id": 5,
"game_id": 42,
"manually_added": 1,
"status": "played",
"room_code": "ABCD",
"played_at": "2026-03-15T19:15:00.000Z",
"player_count": 6,
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"game_type": "Writing",
"min_players": 3,
"max_players": 8,
"popularity_score": 12,
"upvotes": 15,
"downvotes": 3
},
{
"id": 13,
"session_id": 5,
"game_id": 38,
"manually_added": 0,
"status": "playing",
"room_code": "XY9Z",
"played_at": "2026-03-15T20:00:00.000Z",
"player_count": null,
"pack_name": "Jackbox Party Pack 6",
"title": "Trivia Murder Party 2",
"game_type": "Trivia",
"min_players": 1,
"max_players": 8,
"popularity_score": 8,
"upvotes": 10,
"downvotes": 2
}
]
```
### Example
```bash
curl "http://localhost:5000/api/sessions/5/games"
```
---
## POST /api/sessions/{id}/games
Add a game to a session. Side effects: increments game `play_count`, sets previous `playing` games to `played` (skipped games stay skipped), triggers `game.added` webhook and WebSocket event, and auto-starts room monitor if `room_code` is provided.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| game_id | integer | Yes | Game ID (from games table) |
| manually_added | boolean | No | Whether the game was manually added (default: false) |
| room_code | string | No | 4-character room code; if provided, auto-starts room monitor |
```json
{
"game_id": 42,
"manually_added": true,
"room_code": "ABCD"
}
```
### Response
**201 Created**
```json
{
"id": 14,
"session_id": 5,
"game_id": 42,
"manually_added": 1,
"status": "playing",
"room_code": "ABCD",
"played_at": "2026-03-15T20:30:00.000Z",
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"game_type": "Writing",
"min_players": 3,
"max_players": 8
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "game_id is required" }` | Missing game_id |
| 400 | `{ "error": "Cannot add games to a closed session" }` | Session is closed |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
| 404 | `{ "error": "Game not found" }` | Invalid game_id |
### Example
```bash
curl -X POST "http://localhost:5000/api/sessions/5/games" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"game_id": 42, "manually_added": true, "room_code": "ABCD"}'
```
---
## POST /api/sessions/{id}/chat-import
Import chat log and process votes. Matches votes to games by timestamp intervals. `"thisgame++"` = upvote, `"thisgame--"` = downvote. Deduplicates by SHA-256 hash of `username:message:timestamp`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| chatData | array | Yes | Array of `{ username, message, timestamp }` objects |
```json
{
"chatData": [
{
"username": "viewer1",
"message": "thisgame++",
"timestamp": "2026-03-15T20:30:00Z"
},
{
"username": "viewer2",
"message": "thisgame--",
"timestamp": "2026-03-15T20:31:00Z"
}
]
}
```
### Response
**200 OK**
```json
{
"message": "Chat log imported and processed successfully",
"messagesImported": 150,
"duplicatesSkipped": 3,
"votesProcessed": 25,
"votesByGame": {
"42": {
"title": "Quiplash 3",
"upvotes": 15,
"downvotes": 2
}
},
"debug": {
"sessionGamesTimeline": [
{
"title": "Quiplash 3",
"played_at": "2026-03-15T20:00:00.000Z",
"played_at_ms": 1742068800000
}
],
"voteMatches": []
}
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "chatData must be an array" }` | chatData missing or not an array |
| 400 | `{ "error": "No games played in this session to match votes against" }` | Session has no games |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
### Example
```bash
curl -X POST "http://localhost:5000/api/sessions/5/chat-import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"chatData":[{"username":"viewer1","message":"thisgame++","timestamp":"2026-03-15T20:30:00Z"}]}'
```
---
## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/status
Update the status of a session game. Valid values: `playing`, `played`, `skipped`. If setting to `playing`, auto-sets other `playing` games to `played`.
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| status | string | Yes | `"playing"`, `"played"`, or `"skipped"` |
```json
{
"status": "played"
}
```
### Response
**200 OK**
```json
{
"message": "Status updated successfully",
"status": "played"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Invalid status. Must be playing, played, or skipped" }` | Invalid status value |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/status" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "played"}'
```
---
## DELETE /api/sessions/{sessionId}/games/{sessionGameId}
Remove a game from a session. Stops room monitor and player count check.
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Response
**200 OK**
```json
{
"message": "Game removed from session successfully"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |
### Example
```bash
curl -X DELETE "http://localhost:5000/api/sessions/5/games/14" \
-H "Authorization: Bearer $TOKEN"
```
---
## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/room-code
Update the room code for a session game. Room code must be exactly 4 characters, uppercase AZ and 09 only (regex: `^[A-Z0-9]{4}$`).
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| room_code | string | Yes | 4 uppercase alphanumeric chars (A-Z, 0-9) |
```json
{
"room_code": "XY9Z"
}
```
### Response
**200 OK**
Returns the updated SessionGame object with joined game data.
```json
{
"id": 14,
"session_id": 5,
"game_id": 42,
"manually_added": 1,
"status": "playing",
"room_code": "XY9Z",
"played_at": "2026-03-15T20:30:00.000Z",
"pack_name": "Jackbox Party Pack 7",
"title": "Quiplash 3",
"game_type": "Writing",
"min_players": 3,
"max_players": 8,
"popularity_score": 12,
"upvotes": 15,
"downvotes": 3
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "room_code is required" }` | Missing room_code |
| 400 | `{ "error": "room_code must be exactly 4 alphanumeric characters (A-Z, 0-9)" }` | Invalid format |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/room-code" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"room_code": "XY9Z"}'
```
---
## GET /api/sessions/{id}/export
Export session data as a file download. JSON format includes structured session, games, and chat_logs. TXT format is human-readable plaintext.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |
### Query Parameters
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| format | string | No | `txt` | `"json"` or `"txt"` |
### Response
**200 OK**
- **JSON format**: Content-Type `application/json`, filename `session-{id}.json`
```json
{
"session": {
"id": 5,
"created_at": "2026-03-15T19:00:00.000Z",
"closed_at": "2026-03-15T23:30:00.000Z",
"is_active": false,
"notes": "Friday game night",
"games_played": 4
},
"games": [
{
"title": "Quiplash 3",
"pack": "Jackbox Party Pack 7",
"players": "3-8",
"type": "Writing",
"played_at": "2026-03-15T19:15:00.000Z",
"manually_added": true,
"status": "played"
}
],
"chat_logs": [
{
"username": "viewer1",
"message": "thisgame++",
"timestamp": "2026-03-15T19:20:00.000Z",
"vote": "thisgame++"
}
]
}
```
- **TXT format**: Content-Type `text/plain`, filename `session-{id}.txt` — human-readable sections with headers.
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
### Example
```bash
# JSON export
curl -o session-5.json "http://localhost:5000/api/sessions/5/export?format=json" \
-H "Authorization: Bearer $TOKEN"
# TXT export (default)
curl -o session-5.txt "http://localhost:5000/api/sessions/5/export" \
-H "Authorization: Bearer $TOKEN"
```
---
## POST /api/sessions/{sessionId}/games/{sessionGameId}/start-player-check
Start the room monitor for a session game. The game must have a room code.
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Response
**200 OK**
```json
{
"message": "Room monitor started",
"status": "monitoring"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Game does not have a room code" }` | Session game has no room_code |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |
### Example
```bash
curl -X POST "http://localhost:5000/api/sessions/5/games/14/start-player-check" \
-H "Authorization: Bearer $TOKEN"
```
---
## POST /api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check
Stop the room monitor and player count check for a session game.
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Response
**200 OK**
```json
{
"message": "Room monitor and player count check stopped",
"status": "stopped"
}
```
### Example
```bash
curl -X POST "http://localhost:5000/api/sessions/5/games/14/stop-player-check" \
-H "Authorization: Bearer $TOKEN"
```
---
## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/player-count
Manually update the player count for a session game. Sets `player_count_check_status` to `completed`. Broadcasts WebSocket `player-count.updated`.
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| player_count | integer | Yes | Non-negative player count |
```json
{
"player_count": 6
}
```
### Response
**200 OK**
```json
{
"message": "Player count updated successfully",
"player_count": 6
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "player_count is required" }` | Missing player_count |
| 400 | `{ "error": "player_count must be a positive number" }` | Invalid (NaN or negative) |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/player-count" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"player_count": 6}'
```

View File

@@ -0,0 +1,79 @@
# Stats Endpoints
Aggregate statistics about the game library, sessions, and popularity.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/stats` | No | Get aggregate statistics |
---
## GET /api/stats
Return aggregate statistics: game counts, pack count, session counts, total games played, most-played games, and top-rated games.
### Authentication
None.
### Response
**200 OK**
```json
{
"games": { "count": 89 },
"gamesEnabled": { "count": 75 },
"packs": { "count": 9 },
"sessions": { "count": 12 },
"activeSessions": { "count": 1 },
"totalGamesPlayed": { "count": 156 },
"mostPlayedGames": [
{
"id": 42,
"title": "Quiplash 3",
"pack_name": "Jackbox Party Pack 7",
"play_count": 15,
"popularity_score": 8,
"upvotes": 10,
"downvotes": 2
}
],
"topRatedGames": [
{
"id": 42,
"title": "Quiplash 3",
"pack_name": "Jackbox Party Pack 7",
"play_count": 15,
"popularity_score": 8,
"upvotes": 10,
"downvotes": 2
}
]
}
```
| Field | Description |
|-------|-------------|
| games.count | Total number of games in the library |
| gamesEnabled.count | Number of enabled games |
| packs.count | Number of distinct packs |
| sessions.count | Total sessions (all time) |
| activeSessions.count | Sessions with `is_active = 1` |
| totalGamesPlayed.count | Total game plays across all sessions |
| mostPlayedGames | Top 10 games by `play_count` DESC (only games with `play_count` > 0) |
| topRatedGames | Top 10 games by `popularity_score` DESC (only games with `popularity_score` > 0) |
### Error Responses
| Status | Body | When |
|--------|------|------|
| 500 | `{ "error": "..." }` | Server error |
### Example
```bash
curl "http://localhost:5000/api/stats"
```

View File

@@ -0,0 +1,92 @@
# Votes Endpoints
Real-time popularity voting. Bots or integrations send votes during live gaming sessions. Votes are matched to the currently-playing game using timestamp intervals.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/votes/live` | Bearer | Submit a live vote (up/down) |
---
## POST /api/votes/live
Submit a real-time up/down vote for the game currently being played. Automatically finds the active session and matches the vote to the correct game using the provided timestamp and session game intervals.
### Authentication
Bearer token required. Include in header: `Authorization: Bearer <token>`.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| username | string | Yes | Identifier for the voter (used for deduplication) |
| vote | string | Yes | `"up"` or `"down"` |
| timestamp | string | Yes | ISO 8601 timestamp when the vote occurred |
```json
{
"username": "viewer123",
"vote": "up",
"timestamp": "2026-03-15T20:30:00Z"
}
```
### Behavior
- Finds the active session (single session with `is_active = 1`).
- Matches the vote timestamp to the game being played at that time (uses interval between consecutive `played_at` timestamps).
- Updates game `upvotes`, `downvotes`, and `popularity_score` atomically in a transaction.
- **Deduplication:** Rejects votes from the same username within a 1-second window (409 Conflict).
### Response
**200 OK**
```json
{
"success": true,
"message": "Vote recorded successfully",
"session": { "id": 3, "games_played": 5 },
"game": {
"id": 42,
"title": "Quiplash 3",
"upvotes": 11,
"downvotes": 2,
"popularity_score": 9
},
"vote": {
"username": "viewer123",
"type": "up",
"timestamp": "2026-03-15T20:30:00Z"
}
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Missing required fields: username, vote, timestamp" }` | Missing required fields |
| 400 | `{ "error": "vote must be either \"up\" or \"down\"" }` | Invalid vote value |
| 400 | `{ "error": "Invalid timestamp format. Use ISO 8601 format (e.g., 2025-11-01T20:30:00Z)" }` | Invalid timestamp |
| 404 | `{ "error": "No active session found" }` | No session with `is_active = 1` |
| 404 | `{ "error": "No games have been played in the active session yet" }` | Active session has no games |
| 404 | `{ "error": "Vote timestamp does not match any game in the active session", "debug": { "voteTimestamp": "2026-03-15T20:30:00Z", "sessionGames": [{ "title": "Quiplash 3", "played_at": "..." }] } }` | Timestamp outside any game interval |
| 409 | `{ "error": "Duplicate vote detected (within 1 second of previous vote)", "message": "Please wait at least 1 second between votes", "timeSinceLastVote": 0.5 }` | Same username voted within 1 second |
| 500 | `{ "error": "..." }` | Server error |
### Example
```bash
curl -X POST "http://localhost:5000/api/votes/live" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "viewer123",
"vote": "up",
"timestamp": "2026-03-15T20:30:00Z"
}'
```

View File

@@ -0,0 +1,382 @@
# Webhooks Endpoints
HTTP callback endpoints for external integrations. Register webhook URLs to receive notifications about events like game additions. All endpoints require authentication.
## Endpoint Summary
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/webhooks` | Bearer | List all webhooks |
| GET | `/api/webhooks/{id}` | Bearer | Get single webhook |
| POST | `/api/webhooks` | Bearer | Create webhook |
| PATCH | `/api/webhooks/{id}` | Bearer | Update webhook |
| DELETE | `/api/webhooks/{id}` | Bearer | Delete webhook |
| POST | `/api/webhooks/test/{id}` | Bearer | Send test event |
| GET | `/api/webhooks/{id}/logs` | Bearer | Get webhook delivery logs |
---
## GET /api/webhooks
List all registered webhooks. `secret` is not included in responses. `events` is returned as a parsed array. `enabled` is returned as a boolean.
### Authentication
Bearer token required.
### Response
**200 OK**
```json
[
{
"id": 1,
"name": "Discord Bot",
"url": "https://example.com/webhook",
"events": ["game.added"],
"enabled": true,
"created_at": "2024-01-15T12:00:00.000Z"
}
]
```
Note: `secret` is never returned.
### Error Responses
| Status | Body | When |
|--------|------|------|
| 401 | `{ "error": "Access token required" }` | No Bearer token |
| 403 | `{ "error": "Invalid or expired token" }` | Bad or expired token |
| 500 | `{ "error": "..." }` | Server error |
### Example
```bash
curl "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer $TOKEN"
```
---
## GET /api/webhooks/{id}
Get a single webhook by ID.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Webhook ID |
### Response
**200 OK**
```json
{
"id": 1,
"name": "Discord Bot",
"url": "https://example.com/webhook",
"events": ["game.added"],
"enabled": true,
"created_at": "2024-01-15T12:00:00.000Z"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Webhook not found" }` | Invalid ID |
### Example
```bash
curl "http://localhost:5000/api/webhooks/1" \
-H "Authorization: Bearer $TOKEN"
```
---
## POST /api/webhooks
Create a new webhook.
### Authentication
Bearer token required.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | Yes | Display name for the webhook |
| url | string | Yes | Callback URL (must be valid) |
| secret | string | Yes | Secret for signing payloads |
| events | array | Yes | Event types to subscribe to (e.g., `["game.added"]`) |
```json
{
"name": "Discord Bot",
"url": "https://example.com/webhook",
"secret": "mysecret123",
"events": ["game.added"]
}
```
### Response
**201 Created**
```json
{
"id": 5,
"name": "Discord Bot",
"url": "https://example.com/webhook",
"events": ["game.added"],
"enabled": true,
"created_at": "2024-01-15T12:00:00.000Z",
"message": "Webhook created successfully"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Missing required fields: name, url, secret, events" }` | Missing fields |
| 400 | `{ "error": "events must be an array" }` | `events` is not an array |
| 400 | `{ "error": "Invalid URL format" }` | URL validation failed |
### Example
```bash
curl -X POST "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Discord Bot",
"url": "https://example.com/webhook",
"secret": "mysecret123",
"events": ["game.added"]
}'
```
---
## PATCH /api/webhooks/{id}
Update an existing webhook. At least one field must be provided.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Webhook ID |
### Request Body
All fields optional. Include only the fields to update.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | No | Display name |
| url | string | No | Callback URL (must be valid) |
| secret | string | No | New secret |
| events | array | No | Event types (must be array) |
| enabled | boolean | No | Enable or disable the webhook |
```json
{
"name": "Discord Bot Updated",
"url": "https://example.com/webhook-v2",
"enabled": true
}
```
### Response
**200 OK**
```json
{
"id": 5,
"name": "Discord Bot Updated",
"url": "https://example.com/webhook-v2",
"events": ["game.added"],
"enabled": true,
"created_at": "2024-01-15T12:00:00.000Z",
"message": "Webhook updated successfully"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "No fields to update" }` | No fields in body |
| 400 | `{ "error": "Invalid URL format" }` | Invalid URL |
| 400 | `{ "error": "events must be an array" }` | `events` not an array |
| 404 | `{ "error": "Webhook not found" }` | Invalid ID |
### Example
```bash
curl -X PATCH "http://localhost:5000/api/webhooks/5" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Discord Bot Updated", "enabled": true}'
```
---
## DELETE /api/webhooks/{id}
Delete a webhook. Cascades to `webhook_logs` (logs are deleted).
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Webhook ID |
### Response
**200 OK**
```json
{
"message": "Webhook deleted successfully",
"webhookId": 5
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Webhook not found" }` | Invalid ID |
### Example
```bash
curl -X DELETE "http://localhost:5000/api/webhooks/5" \
-H "Authorization: Bearer $TOKEN"
```
---
## POST /api/webhooks/test/{id}
Send a test `game.added` event with dummy data to the webhook URL. Delivery runs asynchronously; check `webhook_logs` for status.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Webhook ID |
### Response
**200 OK**
```json
{
"message": "Test webhook sent",
"note": "Check webhook_logs table for delivery status"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Webhook not found" }` | Invalid ID |
### Example
```bash
curl -X POST "http://localhost:5000/api/webhooks/test/5" \
-H "Authorization: Bearer $TOKEN"
```
---
## GET /api/webhooks/{id}/logs
Get delivery logs for a webhook. Payload is parsed from JSON string to object.
### Authentication
Bearer token required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| id | integer | Webhook ID |
### Query Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| limit | query | integer | No | Max number of logs (default: 50) |
### Response
**200 OK**
```json
[
{
"id": 1,
"webhook_id": 5,
"event_type": "game.added",
"payload": {
"session": { "id": 3, "is_active": true, "games_played": 2 },
"game": {
"id": 42,
"title": "Quiplash 3",
"pack_name": "Jackbox Party Pack 7",
"min_players": 3,
"max_players": 8,
"manually_added": false
}
},
"response_status": 200,
"error_message": null,
"created_at": "2024-01-15T12:00:00.000Z"
}
]
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 500 | `{ "error": "..." }` | Server error |
### Example
```bash
curl "http://localhost:5000/api/webhooks/5/logs?limit=20" \
-H "Authorization: Bearer $TOKEN"
```