{"openapi":"3.0.0","info":{"title":"Poker Planning API","version":"2.4.0","description":"Collaborative poker planning web application for agile team estimation using the Fibonacci sequence.\n\n## Features\n- Dynamic rooms with shareable 6-character codes\n- Real-time synchronization with Server-Sent Events\n- Anonymous votes until collective reveal\n- Session-based authentication with httpOnly cookies\n- Automatic cleanup of inactive members (5 minutes)\n\n## Authentication\nAuthentication is handled via httpOnly cookies (`session_id`). The cookie is automatically set when joining a room.","contact":{"name":"GitHub Repository","url":"https://github.com/Slashgear/poker-planning"},"license":{"name":"ISC","url":"https://opensource.org/licenses/ISC"}},"servers":[{"url":"https://poker.slashgear.dev/api","description":"Production server"},{"url":"http://localhost:3001/api","description":"Local development server"}],"components":{"schemas":{"Stats":{"type":"object","properties":{"cumulative":{"type":"object","properties":{"rooms":{"type":"integer","example":1523},"participants":{"type":"integer","example":8947},"votes":{"type":"integer","example":45231}},"required":["rooms","participants","votes"],"description":"Total historical statistics"},"active":{"type":"object","properties":{"rooms":{"type":"integer","example":12},"participants":{"type":"integer","example":68},"votes":{"type":"integer","example":203}},"required":["rooms","participants","votes"],"description":"Currently active statistics"}},"required":["cumulative","active"]},"RoomInfo":{"type":"object","properties":{"code":{"type":"string","example":"ABC123"},"memberCount":{"type":"integer","example":5},"currentMember":{"$ref":"#/components/schemas/MemberInfo"}},"required":["code","memberCount","currentMember"]},"MemberInfo":{"type":"object","nullable":true,"properties":{"id":{"type":"string","example":"abc123def456"},"name":{"type":"string","example":"Alice"}},"required":["id","name"],"description":"Information about the current member (if authenticated)"},"Error":{"type":"object","properties":{"error":{"type":"string","example":"Room not found"}},"required":["error"]},"Success":{"type":"object","properties":{"success":{"type":"boolean","example":true}},"required":["success"]}},"parameters":{}},"paths":{"/health":{"get":{"tags":["health"],"responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}},"required":["status"]}}}}}}},"/stats":{"get":{"tags":["stats"],"summary":"Get global statistics","description":"Retrieves global platform statistics including both cumulative (all-time) and active (current) metrics","responses":{"200":{"description":"Statistics retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}}}}},"/rooms":{"post":{"tags":["rooms"],"summary":"Create a new room","description":"Creates a new poker planning room with a unique 6-character code","responses":{"200":{"description":"Room created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"}},"required":["code"]}}}}}}},"/rooms/{code}":{"get":{"tags":["rooms"],"summary":"Get room information","description":"Retrieves basic information about a room","parameters":[{"schema":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"},"required":true,"description":"6-character room code","name":"code","in":"path"}],"responses":{"200":{"description":"Room information retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomInfo"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/rooms/{code}/join":{"post":{"tags":["rooms","members"],"summary":"Join a room","description":"Join an existing room with a unique name. Sets a session cookie for authentication.","parameters":[{"schema":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"},"required":true,"description":"6-character room code","name":"code","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":50,"example":"Alice","description":"Member name (unique within room)"}},"required":["name"]}}}},"responses":{"200":{"description":"Successfully joined the room","headers":{"Set-Cookie":{"schema":{"type":"string","example":"session_id=abc123; Path=/; HttpOnly; SameSite=Lax; Max-Age=7200"},"required":true}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"memberId":{"type":"string","example":"abc123def456"},"name":{"type":"string","example":"Alice"}},"required":["success","memberId","name"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Name already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/rooms/{code}/vote":{"post":{"tags":["voting"],"summary":"Submit a vote","description":"Submit or update your vote for the current estimation","security":[{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"},"required":true,"description":"6-character room code","name":"code","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"anyOf":[{"type":"string","enum":["1","2","3","5","8","13","21","34","55","89"]},{"type":"integer"},{"type":"string","enum":["?","☕"]},{"nullable":true}],"example":5,"description":"Fibonacci number, '?' for unsure, '☕' for break, or null"}},"required":["value"]}}}},"responses":{"200":{"description":"Vote submitted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"description":"Invalid vote value or room code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of this room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/rooms/{code}/reveal":{"post":{"tags":["voting"],"summary":"Reveal all votes","description":"Reveal all votes in the room. Any member can trigger this.","security":[{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"},"required":true,"description":"6-character room code","name":"code","in":"path"}],"responses":{"200":{"description":"Votes revealed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Not a member of this room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/rooms/{code}/reset":{"post":{"tags":["voting"],"summary":"Reset the voting session","description":"Reset the current voting session. Clears all votes and hides results.","security":[{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"},"required":true,"description":"6-character room code","name":"code","in":"path"}],"responses":{"200":{"description":"Voting session reset successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Not a member of this room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Room not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/rooms/{code}/members/{memberId}":{"delete":{"tags":["members"],"summary":"Remove a member from the room","description":"Remove a member from the room. Any member can remove others.","security":[{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":6,"maxLength":6,"pattern":"^[A-Z0-9]{6}$","example":"ABC123","description":"6-character room code"},"required":true,"description":"6-character room code","name":"code","in":"path"},{"schema":{"type":"string","example":"abc123def456"},"required":true,"name":"memberId","in":"path"}],"responses":{"200":{"description":"Member removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"403":{"description":"Not a member of this room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Room or member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}