{"openapi":"3.1.0","info":{"title":"QuakeJS API","description":"Browser FPS Arena — Game Servers, Leaderboards, and Game Data API.\n\nThis API is part of the PlatphormNews game platform ecosystem:\n- Platform hub: https://platphormnews.com\n- Games directory: https://games.platphormnews.com\n- MCP / AI tool server: https://mcp.platphormnews.com/api/mcp\n- MCP tool docs: https://mcp.platphormnews.com/api/docs\n- Calendar events: https://calendar.platphormnews.com/api/docs\n- Jobs board: https://jobs.platphormnews.com/api/docs\n- Network graph: https://platphormnews.com/api/network/graph\n- Platform docs: https://platphormnews.com/api/docs\n- Claws network: https://claws.platphormnews.com\n\n## Authentication\nMost read endpoints are public. Write endpoints (POST /matches, POST /webhooks) require\nan `X-API-Key` header or a signed `X-Webhook-Signature` (HMAC-SHA256) for webhooks.\n\n## Idempotency\nPOST endpoints accept an optional `X-Idempotency-Key` header to safely retry requests.\n\n## Rate Limiting\nResponses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers.","version":"1.0.0","contact":{"name":"QuakeJS","url":"https://quake.platphormnews.com"},"license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"}},"externalDocs":{"description":"MCP Tool Server (AI agent integration)","url":"https://mcp.platphormnews.com/api/docs"},"servers":[{"url":"/api/v1","description":"v1 — Game data endpoints"},{"url":"/api","description":"Root — Health, docs, events, webhooks, MCP"}],"tags":[{"name":"Servers","description":"Game server browser"},{"name":"Leaderboard","description":"Player rankings"},{"name":"Matches","description":"Match history and recording"},{"name":"Stats","description":"Global aggregate statistics"},{"name":"Events","description":"Event outbox for polling integrations"},{"name":"Webhooks","description":"Inbound webhook receiver"},{"name":"MCP","description":"Model Context Protocol — AI agent tool server"},{"name":"System","description":"Health and discovery"}],"paths":{"/v1/servers":{"get":{"tags":["Servers"],"summary":"List game servers","description":"Returns a paginated list of active QuakeJS game servers. Filter by mode or region.","operationId":"listServers","parameters":[{"name":"mode","in":"query","description":"Game mode filter","schema":{"type":"string","enum":["ffa","tdm","duel","ctf","all"]}},{"name":"region","in":"query","description":"Region substring filter (e.g. 'us-east')","schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"Server list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerListResponse"}}}}}},"post":{"tags":["Servers"],"summary":"Register a game server","description":"Register a new QuakeJS game server with the hub for multiplayer support. Returns the assigned server ID.","operationId":"registerServer","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServerRequest"}}}},"responses":{"201":{"description":"Server registered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServerResponse"}}}},"400":{"description":"Invalid JSON body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error — missing or invalid fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/servers/{id}":{"get":{"tags":["Servers"],"summary":"Get server details","description":"Returns full details for a specific server including connected players and rule set.","operationId":"getServer","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Server details with connected players and rules","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerDetailResponse"}}}},"404":{"description":"Server not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Servers"],"summary":"Update server status","description":"Update a game server's status, player count, or current map. Used for heartbeat and state synchronisation in multi-server deployments.","operationId":"updateServer","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateServerRequest"}}}},"responses":{"200":{"description":"Server updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateServerResponse"}}}},"400":{"description":"Invalid JSON body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Server not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error — invalid field value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Servers"],"summary":"Deregister a game server","description":"Remove a game server from the hub. Called when a server shuts down or is decommissioned.","operationId":"deregisterServer","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Server deregistered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeregisterServerResponse"}}}},"404":{"description":"Server not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/leaderboard":{"get":{"tags":["Leaderboard"],"summary":"Get player leaderboard","description":"Returns ranked players sortable by frags, deaths, K/D ratio, or wins.","operationId":"getLeaderboard","parameters":[{"name":"sort","in":"query","schema":{"type":"string","enum":["frags","deaths","kd","wins"],"default":"frags"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"Leaderboard entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeaderboardResponse"}}}}}}},"/v1/stats":{"get":{"tags":["Stats"],"summary":"Global game statistics","description":"Returns aggregate statistics: total players, active matches, total frags, top weapon, and per-mode breakdowns.","operationId":"getStats","responses":{"200":{"description":"Aggregate statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsResponse"}}}}}}},"/v1/matches":{"get":{"tags":["Matches"],"summary":"List recent matches","description":"Returns a paginated list of recently completed matches.","operationId":"listMatches","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"Paginated list of recent matches","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchListResponse"}}}}}},"post":{"tags":["Matches"],"summary":"Record a match result","description":"Submit a completed match result. Supports idempotency via `X-Idempotency-Key` header.","operationId":"recordMatch","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"X-Idempotency-Key","in":"header","description":"Optional idempotency key to safely retry submissions","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordMatchRequest"}}}},"responses":{"201":{"description":"Match recorded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordMatchResponse"}}}},"400":{"description":"Invalid JSON body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error — missing or invalid fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/webhooks":{"post":{"tags":["Webhooks"],"summary":"Receive webhook event","description":"Receives signed webhook events from external systems. Requires a valid\n`X-Webhook-Signature` header (HMAC-SHA256 of the raw request body, prefixed with `sha256=`).\nSupports idempotency via `X-Idempotency-Key` header.\n\nSupported events: `match.started`, `match.ended`, `player.joined`, `player.left`,\n`server.created`, `server.deleted`, `leaderboard.updated`.","operationId":"receiveWebhook","parameters":[{"name":"X-Webhook-Signature","in":"header","required":true,"description":"HMAC-SHA256 signature of the raw request body (sha256=...)","schema":{"type":"string"}},{"name":"X-Idempotency-Key","in":"header","description":"Optional idempotency key","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Already processed (idempotent replay)"},"202":{"description":"Webhook accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAcceptedResponse"}}}},"400":{"description":"Invalid body or missing event type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid or missing webhook signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unsupported event type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/events":{"get":{"tags":["Events"],"summary":"Query event outbox","description":"Poll the event outbox for domain events. Filter by type, delivery status, or timestamp. Suitable for polling-based integrations.","operationId":"listEvents","parameters":[{"name":"type","in":"query","description":"Filter by event type (e.g. 'match.ended')","schema":{"type":"string"}},{"name":"delivered","in":"query","description":"Filter by delivery status","schema":{"type":"boolean"}},{"name":"since","in":"query","description":"ISO 8601 timestamp — return events after this time","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"Event outbox entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"}}}}}}},"/mcp":{"get":{"tags":["MCP"],"summary":"MCP server manifest","description":"Returns the Model Context Protocol (MCP) server manifest including server info,\nprotocol version, capabilities, and the full list of available tools.\nAI agents (Claude, GPT, Gemini, etc.) can use this endpoint to discover available tools.\n\nSee also: https://mcp.platphormnews.com/api/mcp","operationId":"getMcpManifest","responses":{"200":{"description":"MCP server manifest","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpManifest"}}}}}},"post":{"tags":["MCP"],"summary":"MCP JSON-RPC endpoint","description":"Handles Model Context Protocol requests using JSON-RPC 2.0.\n\nSupported methods:\n- `initialize` — Negotiate protocol version and capabilities\n- `notifications/initialized` — Client confirmation (no-op response)\n- `tools/list` — List all available tools\n- `tools/call` — Invoke a tool by name\n\nAvailable tools: `list_servers`, `get_server`, `get_leaderboard`, `get_global_stats`, `list_matches`, `register_server`","operationId":"mcpJsonRpc","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"},"examples":{"initialize":{"summary":"Initialize MCP session","value":{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"my-agent","version":"1.0.0"}}}},"toolsList":{"summary":"List available tools","value":{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}},"toolsCall":{"summary":"Call list_servers tool","value":{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_servers","arguments":{"mode":"ffa","limit":5}}}}}}}},"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"400":{"description":"Parse error or invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcErrorResponse"}}}}}}},"/health":{"get":{"tags":["System"],"summary":"Service health check","description":"Returns service health status and version. Used by load balancers and monitoring.","operationId":"getHealth","responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for write operations"}},"schemas":{"GameServer":{"type":"object","properties":{"id":{"type":"string","example":"srv-001"},"name":{"type":"string","example":"Fragfest #1"},"map":{"type":"string","example":"Q3DM17 - The Longest Yard"},"mode":{"type":"string","enum":["ffa","tdm","duel","ctf"]},"players":{"type":"integer","example":8},"maxPlayers":{"type":"integer","example":16},"ping":{"type":"integer","example":24},"status":{"type":"string","enum":["online","starting","full"]},"region":{"type":"string","example":"us-east"},"version":{"type":"string","example":"1.0.0"},"password":{"type":"boolean","example":false}}},"ServerRules":{"type":"object","properties":{"fragLimit":{"type":"integer","example":30},"timeLimit":{"type":"integer","description":"Seconds","example":600},"friendlyFire":{"type":"boolean"},"powerups":{"type":"boolean"},"instagib":{"type":"boolean"}}},"ConnectedPlayer":{"type":"object","properties":{"name":{"type":"string"},"frags":{"type":"integer"},"ping":{"type":"integer"},"team":{"type":"string","enum":["red","blue"],"nullable":true}}},"LeaderboardEntry":{"type":"object","properties":{"rank":{"type":"integer","example":1},"name":{"type":"string","example":"Fatal1ty"},"frags":{"type":"integer","example":9842},"deaths":{"type":"integer","example":1204},"kd":{"type":"number","example":8.17},"wins":{"type":"integer","example":312},"playtime":{"type":"string","example":"142h 30m"},"country":{"type":"string","example":"US"}}},"Match":{"type":"object","properties":{"id":{"type":"string","example":"match-1001"},"mode":{"type":"string","enum":["ffa","tdm","duel","ctf"]},"map":{"type":"string","example":"Q3DM17 - The Longest Yard"},"duration":{"type":"integer","description":"Seconds","example":480},"playerCount":{"type":"integer"},"winner":{"type":"string","nullable":true},"completedAt":{"type":"string","format":"date-time"}}},"Pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"},"hasMore":{"type":"boolean"}}},"Meta":{"type":"object","properties":{"version":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"ServerListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GameServer"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}},"ServerDetailResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/components/schemas/GameServer"},{"type":"object","properties":{"rules":{"$ref":"#/components/schemas/ServerRules"},"players":{"type":"array","items":{"$ref":"#/components/schemas/ConnectedPlayer"}}}}]},"meta":{"$ref":"#/components/schemas/Meta"}}},"LeaderboardResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LeaderboardEntry"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}},"MatchListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Match"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}},"StatsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"totalPlayers":{"type":"integer"},"activePlayers":{"type":"integer"},"totalMatches":{"type":"integer"},"matchesInProgress":{"type":"integer"},"totalServers":{"type":"integer"},"onlineServers":{"type":"integer"},"totalFrags":{"type":"integer"},"topWeapon":{"type":"string"},"averageMatchDuration":{"type":"string"},"peakConcurrent":{"type":"integer"},"gameModes":{"type":"object","additionalProperties":{"type":"object","properties":{"matches":{"type":"integer"},"players":{"type":"integer"}}}},"weaponStats":{"type":"object","additionalProperties":{"type":"object","properties":{"kills":{"type":"integer"},"accuracy":{"type":"number"}}}}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"RecordMatchRequest":{"type":"object","required":["mode","map","players"],"properties":{"mode":{"type":"string","enum":["ffa","tdm","duel","ctf"]},"map":{"type":"string"},"duration":{"type":"integer","description":"Match duration in seconds"},"players":{"type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"frags":{"type":"integer"},"deaths":{"type":"integer"},"team":{"type":"string","enum":["red","blue"]}}}},"winner":{"type":"string","nullable":true},"winningTeam":{"type":"string","enum":["red","blue"],"nullable":true}}},"RecordMatchResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"mode":{"type":"string"},"map":{"type":"string"},"duration":{"type":"integer"},"players":{"type":"integer"},"winner":{"type":"string","nullable":true},"winningTeam":{"type":"string","nullable":true},"recordedAt":{"type":"string","format":"date-time"}}},"status":{"type":"string","example":"recorded"},"requestId":{"type":"string","format":"uuid"}}},"WebhookPayload":{"type":"object","required":["event"],"properties":{"event":{"type":"string","enum":["match.started","match.ended","player.joined","player.left","server.created","server.deleted","leaderboard.updated"]},"data":{"type":"object","additionalProperties":true},"timestamp":{"type":"string","format":"date-time"}}},"WebhookAcceptedResponse":{"type":"object","properties":{"status":{"type":"string","example":"accepted"},"event":{"type":"string"},"requestId":{"type":"string","format":"uuid"},"processedAt":{"type":"string","format":"date-time"}}},"EventEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","example":"match.ended"},"data":{"type":"object","additionalProperties":true},"timestamp":{"type":"string","format":"date-time"},"delivered":{"type":"boolean"}}},"EventListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EventEntry"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"pending":{"type":"integer"},"version":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}},"HealthResponse":{"type":"object","properties":{"status":{"type":"string","example":"healthy"},"version":{"type":"string","example":"1.0.0"},"timestamp":{"type":"string","format":"date-time"},"uptime":{"type":"number","nullable":true},"services":{"type":"object","properties":{"game_engine":{"type":"string","example":"operational"},"server_browser":{"type":"string","example":"operational"},"leaderboard":{"type":"string","example":"operational"},"multiplayer":{"type":"string","example":"operational"}}}}},"McpTool":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"inputSchema":{"type":"object","additionalProperties":true}}},"McpManifest":{"type":"object","properties":{"server":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"displayName":{"type":"string"},"description":{"type":"string"}}},"protocol":{"type":"string","example":"mcp"},"protocolVersion":{"type":"string","example":"2024-11-05"},"capabilities":{"type":"object","additionalProperties":true},"tools":{"type":"array","items":{"$ref":"#/components/schemas/McpTool"}},"links":{"type":"object","properties":{"docs":{"type":"string","format":"uri"},"llms":{"type":"string","format":"uri"},"platform":{"type":"string","format":"uri"},"games":{"type":"string","format":"uri"}}}}},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"nullable":true},"method":{"type":"string"},"params":{"type":"object","additionalProperties":true}}},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"nullable":true},"result":{"type":"object","additionalProperties":true}}},"JsonRpcErrorResponse":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"nullable":true},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"ErrorResponse":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string","format":"uuid"}}},"RegisterServerRequest":{"type":"object","required":["name","map","mode","maxPlayers","region"],"properties":{"name":{"type":"string","example":"Frag Palace #1"},"map":{"type":"string","example":"Q3DM17 - The Longest Yard"},"mode":{"type":"string","enum":["ffa","tdm","duel","ctf"]},"maxPlayers":{"type":"integer","example":12},"region":{"type":"string","example":"us-east"},"password":{"type":"boolean","default":false}}},"RegisterServerResponse":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/components/schemas/GameServer"},{"type":"object","properties":{"registeredAt":{"type":"string","format":"date-time"}}}]},"status":{"type":"string","example":"registered"},"requestId":{"type":"string","format":"uuid"}}},"UpdateServerRequest":{"type":"object","properties":{"players":{"type":"integer","description":"Current connected player count."},"map":{"type":"string","description":"Current map name."},"status":{"type":"string","enum":["online","starting","full"],"description":"Server status."}}},"UpdateServerResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/GameServer"},"status":{"type":"string","example":"updated"},"requestId":{"type":"string","format":"uuid"},"meta":{"$ref":"#/components/schemas/Meta"}}},"DeregisterServerResponse":{"type":"object","properties":{"status":{"type":"string","example":"deregistered"},"id":{"type":"string","example":"srv-001"},"requestId":{"type":"string","format":"uuid"},"removedAt":{"type":"string","format":"date-time"}}}}}}