Complete REST API reference for the 10 Minute Pokemon backend server.
Base URL: http://localhost:3001 (development) or http://localhost/api (Docker)
Content-Type: application/json
- Health Check
- Admin Authentication
- Kiosk Registration
- Admin - Kiosk Management
- Save States
- Game Turns
- Statistics
- Error Responses
Health check endpoint (served by nginx in Docker).
Request:
curl http://localhost/healthResponse: 200 OK
OK
Authenticate as admin to access protected endpoints.
Request Body:
{
"password": "your-admin-password"
}Response: 200 OK
{
"success": true,
"token": "ABCdef123xyz..."
}Error Response: 401 Unauthorized
{
"success": false,
"error": "Invalid password"
}Notes:
- Returns a session token for use in Authorization header
- Token format:
Bearer <token>
End admin session.
Headers:
Authorization: Bearer <token>
Response: 200 OK
{
"success": true
}Register a new kiosk with a unique token. Called by kiosk frontend on startup.
Request Body:
{
"token": "abc123xyz789def4",
"kioskId": "kiosk-001",
"kioskName": "Main Lobby Kiosk"
}Response: 201 Created
{
"token": "abc123xyz789def4",
"status": "pending",
"message": "Kiosk registered. Waiting for admin activation."
}Error Responses:
400 Bad Request - Missing required fields
{
"error": "token and kioskId are required"
}400 Bad Request - Invalid token format
{
"error": "Invalid token format. Must be 12-32 alphanumeric characters."
}403 Forbidden - Another kiosk is already active
{
"error": "Another kiosk is already active",
"message": "Only one kiosk can be active at a time. Please contact an organizer."
}Poll for kiosk activation status.
URL Parameters:
token(string, required): Kiosk registration token
Response (Pending): 200 OK
{
"status": "pending",
"isActive": false
}Response (Active): 200 OK
{
"status": "active",
"activatedAt": "2025-01-26T12:05:00.000Z",
"isActive": true
}Error Response: 404 Not Found
{
"error": "Kiosk not registered"
}All admin endpoints require authentication via Authorization: Bearer <token> header.
Activate a pending kiosk.
Headers:
Authorization: Bearer <token>
Request Body:
{
"token": "abc123xyz789def4"
}Response: 200 OK
{
"token": "abc123xyz789def4",
"kioskId": "kiosk-001",
"kioskName": "Main Lobby Kiosk",
"status": "active",
"activatedAt": "2025-01-26T12:05:00.000Z",
"message": "Kiosk activated successfully"
}Error Responses:
401 Unauthorized - Missing or invalid auth
{
"error": "Admin authentication required"
}404 Not Found - Kiosk not found
{
"error": "Kiosk not found"
}409 Conflict - Another kiosk already active
{
"error": "Another kiosk is already active",
"message": "Only one kiosk can be active at a time. Please disconnect the current kiosk first.",
"activeKiosk": {
"kioskId": "kiosk-002",
"kioskName": "Other Kiosk"
}
}Disconnect and remove a kiosk (active or pending).
Headers:
Authorization: Bearer <token>
Request Body:
{
"token": "abc123xyz789def4"
}Response: 200 OK
{
"message": "Kiosk disconnected successfully",
"kioskId": "kiosk-001",
"kioskName": "Main Lobby Kiosk"
}Error Response: 404 Not Found
{
"error": "Kiosk not found"
}List all registered kiosks (pending and active).
Headers:
Authorization: Bearer <token>
Response: 200 OK
{
"kiosks": [
{
"token": "abc123xyz789def4",
"status": "pending",
"kioskId": "kiosk-001",
"kioskName": "Main Lobby Kiosk",
"registeredAt": "2025-01-26T12:00:00.000Z"
},
{
"token": "xyz789abc123ghi4",
"status": "active",
"kioskId": "kiosk-002",
"kioskName": "Side Room Kiosk",
"registeredAt": "2025-01-26T11:00:00.000Z",
"activatedAt": "2025-01-26T11:05:00.000Z"
}
]
}Restore game to a previous turn, invalidating all newer turns.
Headers:
Authorization: Bearer <token>
Request Body:
{
"turnId": 123
}Response: 200 OK
{
"success": true,
"restoredTurn": {
"id": 123,
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 2,
"turnEndedAt": "2025-01-26T12:00:00.000Z",
"saveStateUrl": "main-game/turn-123.sav"
},
"invalidatedCount": 5
}Error Responses:
400 Bad Request - Turn has no save state
{
"error": "Turn has no save state to restore"
}404 Not Found - Turn not found
{
"error": "Turn not found"
}List all save states from MinIO storage.
Response: 200 OK
{
"saves": [
{
"objectKey": "main-game/turn-123.sav",
"size": 524288,
"lastModified": "2025-01-26T12:34:56.789Z",
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 2,
"turnId": 123
}
]
}Get the latest valid (non-invalidated) save state.
Response: 200 OK
{
"turnId": 123,
"saveStateUrl": "main-game/turn-123.sav",
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 2,
"turnEndedAt": "2025-01-26T12:34:56.789Z"
}Error Response: 404 Not Found
{
"error": "No saves found"
}Download a specific save state file.
URL Parameters:
objectKey(string, required): The MinIO object key (e.g.,main-game/turn-123.sav)
Response: 200 OK
- Content-Type:
application/octet-stream - Body: Binary save state data
Error Response: 404 Not Found
{
"error": "Save not found"
}Upload a save state.
Request Body:
{
"saveData": "base64EncodedSaveStateData...",
"gameData": {
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 0
}
}Response: 200 OK
{
"success": true,
"saveUrl": "main-game/save-1706274896789.sav",
"message": "Save state uploaded successfully"
}Error Response: 400 Bad Request
{
"error": "saveData is required"
}Create a new game turn record when a player finishes their turn.
Request Body:
{
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 0,
"playtime": 3600,
"money": 3000,
"partyData": [
{
"species": "Pikachu",
"level": 5,
"hp": 20,
"maxHp": 20
}
],
"turnDuration": 600,
"saveState": "base64EncodedSaveStateData..."
}Response: 201 Created
{
"id": 123,
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 0,
"playtime": 3600,
"money": 3000,
"partyData": [...],
"turnDuration": 600,
"saveStateUrl": "main-game/turn-123.sav",
"turnEndedAt": "2025-01-26T12:34:56.789Z",
"createdAt": "2025-01-26T12:34:56.789Z",
"updatedAt": "2025-01-26T12:34:56.789Z"
}Validation Errors: 400 Bad Request
{"error": "Valid playerName is required"}
{"error": "badgeCount must be a number between 0 and 8"}
{"error": "playtime must be a non-negative number"}
{"error": "money must be a non-negative number"}
{"error": "turnDuration must be a non-negative number"}Notes:
playerName(required): Player's namesaveState(optional): If provided, uploads to MinIO as turn save- If MinIO upload fails, turn is still created with
warningfield
List game turns with pagination and optional filtering.
Query Parameters:
limit(number, optional): Results per page (default: 50)offset(number, optional): Skip N results (default: 0)playerName(string, optional): Filter by player nameincludeInvalidated(string, optional): Include invalidated turns (default:true)
Request:
# Get first 50 turns
curl http://localhost:3001/api/game-turns
# Exclude invalidated turns
curl http://localhost:3001/api/game-turns?includeInvalidated=false
# Filter by player
curl http://localhost:3001/api/game-turns?playerName=AshResponse: 200 OK
{
"data": [
{
"id": 123,
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 0,
"playtime": 3600,
"money": 3000,
"partyData": [...],
"turnDuration": 600,
"saveStateUrl": "main-game/turn-123.sav",
"turnEndedAt": "2025-01-26T12:34:56.789Z",
"invalidatedAt": null,
"invalidatedByRestoreToTurnId": null
}
],
"pagination": {
"total": 123,
"limit": 50,
"offset": 0,
"hasMore": true
}
}Get a specific game turn by ID.
Response: 200 OK
{
"id": 123,
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 0,
"playtime": 3600,
"money": 3000,
"partyData": [...],
"turnDuration": 600,
"saveStateUrl": "main-game/turn-123.sav",
"turnEndedAt": "2025-01-26T12:34:56.789Z"
}Error Response: 404 Not Found
{
"error": "Game turn not found"
}Get game statistics and leaderboard.
Response: 200 OK
{
"totalTurns": 123,
"uniquePlayers": 45,
"latestTurn": {
"id": 123,
"playerName": "Ash",
"location": "Pallet Town",
"badgeCount": 2,
"playtime": 3600,
"turnEndedAt": "2025-01-26T12:34:56.789Z"
},
"topPlayers": [
{
"player_name": "Ash",
"turn_count": "15",
"max_badges": "3"
},
{
"player_name": "Gary",
"turn_count": "12",
"max_badges": "4"
}
]
}All error responses follow this format:
{
"error": "Description of what went wrong"
}| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Successful request |
| 201 | Created | Resource created |
| 400 | Bad Request | Invalid data |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | Action not allowed |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Resource conflict |
| 500 | Internal Server Error | Server error |
- Max JSON Body Size: 10MB
- CORS: Configured via
CORS_ORIGINandCORS_ORIGIN_ADMINenv vars
Save states are stored in MinIO with the following naming conventions:
Turn-end saves:
{sessionId}/turn-{turnId}.sav
Access:
- MinIO Console:
http://localhost:9001(exposed in docker-compose)