{"openapi":"3.0.3","info":{"title":"Pitch Connect API","version":"1.0","description":"Read club squad data, upcoming fixtures, tournament metadata, and tournament matches.\nWrite match scores, goals, highlights, and match event markers.\n\n**Auth:** `Authorization: Bearer \u003ctoken\u003e`\n\nSupported token types:\n- **Connected app token** — one token per connected user/app; use it with explicit `/clubs/:slug` and `/tournaments/:slug` endpoints\n- **Club static key** — a single shared key generated by the club owner\n- **Tournament static key** — a shared tournament key for direct integrations\n- **Legacy per-resource app tokens** — older club/tournament-specific app connections still supported by generic endpoints\n\n**Manage tokens:** `https://pitch-connect.space/clubs/\u003cslug\u003e/integrations`\n\n**OAuth flow:** `https://pitch-connect.space/api/auth/connect?app_name=MyApp\u0026redirect_uri=https://myapp.com/callback`\n","contact":{"url":"https://pitch-connect.space/api/docs"}},"servers":[{"url":"https://pitch-connect.space/api/v1","description":"Current environment"}],"security":[{"BearerToken":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer","description":"Accepted token types:\n- **Connected app token**\n- **Club static key**\n- **Tournament static key**\n- **Legacy per-resource app tokens**\n\nClub integrations page: https://pitch-connect.space/clubs/{your-club-slug}/integrations\n"}},"schemas":{"Player":{"type":"object","properties":{"id":{"type":"integer","description":"User ID — use to match players in game team lists"},"slug":{"type":"string"},"name":{"type":"string"},"jersey_number":{"type":"integer","nullable":true,"description":"Squad number (1–99). null if not assigned by club staff"},"club_position":{"type":"string","nullable":true,"enum":["goalkeeper","defender","midfielder","forward"],"description":"Position assigned by the club. null if not set"},"effective_position":{"type":"string","nullable":true,"description":"club_position if set, otherwise preferred_position. Use this for lineup display"},"preferred_position":{"type":"string","nullable":true,"enum":["goalkeeper","defender","midfielder","forward"]},"secondary_positions":{"type":"array","items":{"type":"string"}},"skill_level":{"type":"string","enum":["beginner","intermediate","advanced"]},"role":{"type":"string","enum":["Owner","Moderator","Member"]},"joined_at":{"type":"string","format":"date-time"},"profile_url":{"type":"string","format":"uri"}}},"PlayerInGame":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"description":"User ID for player_type=user, junior-member ID for player_type=junior, or null for unnamed guests"},"name":{"type":"string"},"is_guest":{"type":"boolean"},"player_type":{"type":"string","enum":["user","junior","guest"],"description":"Identity namespace to use with marker player IDs"},"is_captain":{"type":"boolean"},"is_substitute":{"type":"boolean"},"pitch_role":{"type":"string","nullable":true},"checked_in":{"type":"boolean"}}},"Team":{"type":"object","properties":{"number":{"type":"integer"},"name":{"type":"string"},"jersey_color":{"type":"string","description":"Hex color code of the team's jersey, e.g. \"#3b82f6\". Defaults to blue for team 1, white for team 2."},"players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerInGame"}},"maybe_players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerInGame"}}}},"Matchup":{"type":"object","properties":{"home_team":{"type":"integer"},"home_name":{"type":"string"},"away_team":{"type":"integer","nullable":true},"away_name":{"type":"string","nullable":true}}},"Game":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"matchup":{"type":"string","description":"e.g. \"Reds vs Blues · Greens vs Yellows\""},"date_time":{"type":"string","format":"date-time","description":"Kick-off time in UTC"},"timezone":{"type":"string"},"location":{"type":"string","nullable":true},"field":{"type":"string","nullable":true},"pitch":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"max_players":{"type":"integer"},"players_joined":{"type":"integer"},"spots_left":{"type":"integer"},"skill_level":{"type":"string","nullable":true,"enum":["beginner","intermediate","advanced"]},"cost_per_player":{"type":"number","nullable":true},"is_public":{"type":"boolean"},"organizer":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}},"teams":{"type":"array","items":{"$ref":"#/components/schemas/Team"}},"matchups":{"type":"array","items":{"$ref":"#/components/schemas/Matchup"}},"unassigned_players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerInGame"}},"maybe_unassigned_players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerInGame"}},"score":{"type":"string","nullable":true,"description":"e.g. \"Reds 2 · Blues 1\""},"url":{"type":"string","format":"uri"}}},"MatchResult":{"type":"object","description":"Score and goal details for a completed game","properties":{"scores":{"type":"array","description":"Score per team","items":{"type":"object","properties":{"team":{"type":"string"},"score":{"type":"integer"}}}},"scorers":{"type":"array","description":"Aggregated goal tally per player, sorted by goals desc","items":{"type":"object","properties":{"name":{"type":"string"},"goals":{"type":"integer"}}}},"goals":{"type":"array","description":"Individual goal events in recorded order","items":{"type":"object","properties":{"scorer":{"type":"string","nullable":true},"assister":{"type":"string","nullable":true},"team":{"type":"string","nullable":true},"own_goal":{"type":"boolean"}}}}}},"CompletedGame":{"allOf":[{"$ref":"#/components/schemas/Game"},{"type":"object","properties":{"result":{"$ref":"#/components/schemas/MatchResult"}}}]},"Club":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"city":{"type":"string","nullable":true},"location":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"member_count":{"type":"integer"},"is_private":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"badge_url":{"type":"string","nullable":true,"description":"Club logo (200×200). null if not uploaded"},"cover_photo_url":{"type":"string","nullable":true,"description":"Club banner (1200×400). null if not uploaded"}}},"TournamentTeam":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"club_id":{"type":"integer","nullable":true},"club_slug":{"type":"string","nullable":true},"group_name":{"type":"string","nullable":true},"points":{"type":"integer"},"matches_played":{"type":"integer"},"wins":{"type":"integer"},"draws":{"type":"integer"},"losses":{"type":"integer"},"goals_for":{"type":"integer"},"goals_against":{"type":"integer"},"goal_difference":{"type":"integer"}}},"TournamentMatch":{"type":"object","properties":{"id":{"type":"integer"},"stage":{"type":"string"},"group_name":{"type":"string","nullable":true},"round":{"type":"integer","nullable":true},"knockout_round":{"type":"string","nullable":true},"knockout_round_label":{"type":"string","nullable":true},"status":{"type":"string"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"played_at":{"type":"string","format":"date-time","nullable":true},"started_at":{"type":"string","format":"date-time","nullable":true},"ended_at":{"type":"string","format":"date-time","nullable":true},"period":{"type":"string","nullable":true},"field":{"type":"string","nullable":true},"pitch":{"type":"string","nullable":true},"home_team":{"type":"object","properties":{"id":{"type":"integer","nullable":true},"slug":{"type":"string","nullable":true},"name":{"type":"string"},"placeholder":{"type":"string","nullable":true}}},"away_team":{"type":"object","properties":{"id":{"type":"integer","nullable":true},"slug":{"type":"string","nullable":true},"name":{"type":"string"},"placeholder":{"type":"string","nullable":true}}},"lineups":{"$ref":"#/components/schemas/MatchLineups"},"score":{"type":"object","properties":{"home":{"type":"integer","nullable":true},"away":{"type":"integer","nullable":true},"home_penalties":{"type":"integer","nullable":true},"away_penalties":{"type":"integer","nullable":true}}},"winner_club_id":{"type":"integer","nullable":true},"goals":{"type":"array","items":{"type":"object"}},"cards":{"type":"array","items":{"type":"object"}},"commentary":{"type":"array","items":{"type":"object"}},"stream_url":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}}},"MatchLineups":{"type":"object","properties":{"home":{"$ref":"#/components/schemas/MatchTeamLineup"},"away":{"$ref":"#/components/schemas/MatchTeamLineup"}}},"MatchTeamLineup":{"type":"object","properties":{"starters":{"type":"array","items":{"$ref":"#/components/schemas/LineupPlayer"}},"substitutes":{"type":"array","items":{"$ref":"#/components/schemas/LineupPlayer"}}}},"LineupPlayer":{"type":"object","properties":{"id":{"type":"integer","description":"User ID"},"slug":{"type":"string","description":"Player slug"},"name":{"type":"string","description":"Full name of player"},"avatar_url":{"type":"string","nullable":true,"description":"Profile avatar image URL"},"jersey_number":{"type":"integer","nullable":true,"description":"Jersey number for this team"},"captain":{"type":"boolean","description":"Whether the player is team captain"},"position":{"type":"string","nullable":true,"description":"Tactical pitch role or default position"},"pitch_x":{"type":"number","format":"float","nullable":true,"description":"X coordinate on pitch layout (0-100)"},"pitch_y":{"type":"number","format":"float","nullable":true,"description":"Y coordinate on pitch layout (0-100)"}}},"Tournament":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"format":{"type":"string"},"tournament_type":{"type":"string"},"description":{"type":"string","nullable":true},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"location":{"type":"string","nullable":true},"host_club":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"}}}}},"Goal":{"type":"object","properties":{"team":{"type":"string","description":"Team number as string, e.g. \"1\""},"scorer":{"type":"string"},"user_id":{"type":"integer","nullable":true},"minute":{"type":"string","nullable":true},"penalty":{"type":"boolean"},"own_goal":{"type":"boolean"},"assister":{"type":"string","nullable":true},"assister_user_id":{"type":"integer","nullable":true}}},"GameResult":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"team_scores":{"type":"object","additionalProperties":{"type":"integer"}},"score":{"type":"string","nullable":true},"goals":{"type":"array","items":{"$ref":"#/components/schemas/Goal"}}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}}}}},"paths":{"/resources":{"get":{"summary":"Accessible resources for a connected app token","description":"Returns the clubs and tournaments currently accessible to the connected app token's user.","operationId":"listAccessibleResources","tags":["Auth"],"responses":{"200":{"description":"Accessible resources","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string"}}},"clubs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"role":{"type":"string"}}}},"tournaments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"role":{"type":"string"}}}}}},"example":{"user":{"id":7,"name":"Alex Johnson","email":"alex@example.com"},"clubs":[{"id":12,"name":"Westminster FC","slug":"westminster-fc","role":"owner"}],"tournaments":[{"id":18,"name":"GMC Cup","slug":"gmc-cup","status":"group_stage","role":"organizer"}]}}}},"403":{"description":"This endpoint requires a connected app token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournament":{"get":{"summary":"Tournament profile","description":"Returns tournament metadata, accepted teams, and the tournament match list.","operationId":"getTournament","tags":["Tournament"],"responses":{"200":{"description":"Tournament data","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Tournament"},{"type":"object","properties":{"teams":{"type":"array","items":{"$ref":"#/components/schemas/TournamentTeam"}},"players":{"type":"array","items":{"type":"object"}},"upcoming_match_count":{"type":"integer"},"completed_match_count":{"type":"integer"},"matches":{"type":"array","items":{"$ref":"#/components/schemas/TournamentMatch"}},"url":{"type":"string","format":"uri"}}}]},"example":{"id":18,"name":"GMC Cup","slug":"gmc-cup","status":"group_stage","format":"tournament","tournament_type":"inter_club","description":"Summer 7v7 tournament.","start_date":"2026-07-20","end_date":"2026-08-03","location":"Denver, CO","host_club":{"id":7,"name":"GMC FC","slug":"gmc-fc"},"teams":[{"id":101,"name":"Home FC","club_id":12,"club_slug":"home-fc","group_name":"A","points":3,"matches_played":1,"wins":1,"draws":0,"losses":0,"goals_for":2,"goals_against":1,"goal_difference":1}],"players":[{"id":42,"slug":"alex-rivera","name":"Alex Rivera","avatar_url":"https://pitch-connect.space/rails/active_storage/...jpg","jersey_number":10,"team_position":"midfielder","preferred_position":"midfielder","secondary_positions":["forward"],"skill_level":"intermediate","captain":true,"team_id":101,"team_name":"Home FC","club_id":12,"club_slug":"home-fc","profile_url":"https://pitch-connect.space/players/alex-rivera"}],"upcoming_match_count":2,"completed_match_count":1,"matches":[{"id":42,"stage":"group","group_name":"A","round":1,"knockout_round":null,"knockout_round_label":null,"status":"completed","scheduled_at":"2026-07-20T18:00:00Z","played_at":"2026-07-20T18:00:00Z","started_at":"2026-07-20T18:01:00Z","ended_at":"2026-07-20T18:52:00Z","period":"full_time","field":"Field 1","pitch":"North","home_team":{"id":12,"slug":"home-fc","name":"Home FC","placeholder":null},"away_team":{"id":15,"slug":"away-fc","name":"Away FC","placeholder":null},"score":{"home":2,"away":1,"home_penalties":null,"away_penalties":null},"winner_club_id":12,"goals":[{"team":"home","scorer":"Alex Rivera","minute":"9"}],"cards":[],"commentary":[],"stream_url":null,"notes":null}],"url":"https://pitch-connect.space/tournaments/gmc-cup"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This endpoint requires a tournament API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournament/matches":{"get":{"summary":"Tournament matches","description":"Returns the tournament match list for import and sync workflows.","operationId":"getTournamentMatches","tags":["Tournament"],"responses":{"200":{"description":"Tournament matches","content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"format":{"type":"string"},"tournament_type":{"type":"string"}}},"matches":{"type":"array","items":{"$ref":"#/components/schemas/TournamentMatch"}}}},"example":{"tournament":{"id":18,"name":"GMC Cup","slug":"gmc-cup","status":"group_stage","format":"tournament","tournament_type":"inter_club"},"matches":[{"id":42,"stage":"group","group_name":"A","round":1,"knockout_round":null,"knockout_round_label":null,"status":"completed","scheduled_at":"2026-07-20T18:00:00Z","played_at":"2026-07-20T18:00:00Z","started_at":"2026-07-20T18:01:00Z","ended_at":"2026-07-20T18:52:00Z","period":"full_time","field":"Field 1","pitch":"North","home_team":{"id":12,"slug":"home-fc","name":"Home FC","placeholder":null},"away_team":{"id":15,"slug":"away-fc","name":"Away FC","placeholder":null},"score":{"home":2,"away":1,"home_penalties":null,"away_penalties":null},"winner_club_id":12,"goals":[{"team":"home","scorer":"Alex Rivera","minute":"9"}],"cards":[],"commentary":[],"stream_url":null,"notes":null}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This endpoint requires a tournament API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournament/players":{"get":{"summary":"Tournament players","description":"Returns the tournament player list with team affiliation, profile URLs, and avatar URLs. In intra-club tournaments, registration details such as jersey preferences and waiver acceptance are also included.","operationId":"getTournamentPlayers","tags":["Tournament"],"responses":{"200":{"description":"Tournament players","content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"format":{"type":"string"},"tournament_type":{"type":"string"}}},"players":{"type":"array","items":{"type":"object"}}}},"example":{"tournament":{"id":18,"name":"GMC Cup","slug":"gmc-cup","status":"group_stage","format":"tournament","tournament_type":"inter_club"},"players":[{"id":42,"slug":"alex-rivera","name":"Alex Rivera","avatar_url":"https://pitch-connect.space/rails/active_storage/...jpg","jersey_number":10,"team_position":"midfielder","preferred_position":"midfielder","secondary_positions":["forward"],"skill_level":"intermediate","captain":true,"team_id":101,"team_name":"Home FC","club_id":12,"club_slug":"home-fc","profile_url":"https://pitch-connect.space/players/alex-rivera"}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This endpoint requires a tournament API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournaments/{tournament_slug}":{"get":{"summary":"Tournament profile via explicit resource path","description":"Recommended endpoint for connected app tokens. Legacy alias for static tournament keys and older resource tokens is `/tournament`.","operationId":"getTournamentBySlug","tags":["Tournament"],"parameters":[{"name":"tournament_slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tournament data","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Tournament"},{"type":"object","properties":{"teams":{"type":"array","items":{"$ref":"#/components/schemas/TournamentTeam"}},"players":{"type":"array","items":{"type":"object"}},"upcoming_match_count":{"type":"integer"},"completed_match_count":{"type":"integer"},"matches":{"type":"array","items":{"$ref":"#/components/schemas/TournamentMatch"}},"url":{"type":"string","format":"uri"}}}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Tournament not accessible for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournaments/{tournament_slug}/players":{"get":{"summary":"Tournament players via explicit resource path","description":"Recommended endpoint for connected app tokens. Legacy alias is `/tournament/players`.","operationId":"getTournamentPlayersBySlug","tags":["Tournament"],"parameters":[{"name":"tournament_slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tournament players","content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"object"},"players":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Tournament not accessible for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournaments/{tournament_slug}/matches":{"get":{"summary":"Tournament matches via explicit resource path","description":"Recommended endpoint for connected app tokens. Legacy alias is `/tournament/matches`.","operationId":"getTournamentMatchesBySlug","tags":["Tournament"],"parameters":[{"name":"tournament_slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tournament matches","content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"object"},"matches":{"type":"array","items":{"$ref":"#/components/schemas/TournamentMatch"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Tournament not accessible for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/club":{"get":{"summary":"Club profile","description":"Returns full club data — details, all active players, and next 20 upcoming games.","operationId":"getClub","tags":["Club"],"responses":{"200":{"description":"Club data","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Club"},{"type":"object","properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}},"upcoming_games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}}}]}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clubs/{club_slug}":{"get":{"summary":"Club profile via explicit resource path","description":"Recommended endpoint for connected app tokens. Legacy alias for static club keys and older resource tokens is `/club`.","operationId":"getClubBySlug","tags":["Club"],"parameters":[{"name":"club_slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Club data","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Club"},{"type":"object","properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}},"upcoming_games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}}}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Club not accessible for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/club/players":{"get":{"summary":"Squad members","description":"Active members with jersey numbers, club-assigned positions, and skill levels.","operationId":"getPlayers","tags":["Club"],"responses":{"200":{"description":"Player list","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"type":"string"},"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clubs/{club_slug}/players":{"get":{"summary":"Squad members via explicit resource path","description":"Recommended endpoint for connected app tokens. Legacy alias is `/club/players`.","operationId":"getClubPlayersBySlug","tags":["Club"],"parameters":[{"name":"club_slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Player list","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"type":"string"},"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Club not accessible for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/club/upcoming_games":{"get":{"summary":"Upcoming fixtures","description":"Next 20 games ordered by kick-off time, with team breakdowns and match pairings.","operationId":"getUpcomingGames","tags":["Club"],"responses":{"200":{"description":"Game list","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clubs/{club_slug}/upcoming_games":{"get":{"summary":"Upcoming fixtures via explicit resource path","description":"Recommended endpoint for connected app tokens. Legacy alias is `/club/upcoming_games`.","operationId":"getClubUpcomingGamesBySlug","tags":["Club"],"parameters":[{"name":"club_slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Game list","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Club not accessible for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/club/past_games":{"get":{"summary":"Past / completed games","description":"Recently completed games in reverse chronological order (newest first).\nEach game includes the full score breakdown, goal scorers, and assist data.\n\n**Pagination** — use `page` and `limit` to page through results.\n","operationId":"getPastGames","tags":["Club"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"minimum":1,"maximum":50},"description":"Number of games to return (max 50)"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"minimum":1},"description":"Page number for pagination"}],"responses":{"200":{"description":"Completed game list","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"type":"string"},"page":{"type":"integer"},"limit":{"type":"integer"},"games":{"type":"array","items":{"$ref":"#/components/schemas/CompletedGame"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/games/{slug}/scores":{"patch":{"summary":"Update match scores","description":"Set the score for each team. The game must belong to the club identified by your token.\n\nScores and goals are independent — call both endpoints to keep them in sync.\n","operationId":"updateScores","tags":["Match Results"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Game slug, e.g. `thursday-kickabout-2026-06-03`"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scores"],"properties":{"scores":{"type":"object","additionalProperties":{"type":"integer"},"example":{"1":2,"2":1}}}}}}},"responses":{"200":{"description":"Scores updated","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"game":{"$ref":"#/components/schemas/GameResult"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Game not found or not owned by this club","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Missing or invalid scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/games/{slug}/goals":{"patch":{"summary":"Update goal scorers","description":"Replace the full goal list. Send the complete array — this overwrites all existing goals.\n\nUse player `id` from `/club/players` for `user_id` and `assister_user_id`.\n","operationId":"updateGoals","tags":["Match Results"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Game slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["goals"],"properties":{"goals":{"type":"array","items":{"type":"object","required":["team","scorer"],"properties":{"team":{"type":"string","description":"Team number as string, e.g. \"1\""},"scorer":{"type":"string","description":"Scorer display name"},"user_id":{"type":"integer","description":"Scorer user ID from /players. Omit for guests"},"minute":{"type":"string","description":"Minute of goal, e.g. \"45\""},"penalty":{"type":"boolean","default":false},"own_goal":{"type":"boolean","default":false},"assister":{"type":"string","description":"Assister display name"},"assister_user_id":{"type":"integer","description":"Assister user ID from /players"}}}}}},"example":{"goals":[{"team":"1","scorer":"Alex Johnson","user_id":42,"minute":"23","penalty":false,"own_goal":false,"assister":"Sam Smith","assister_user_id":15},{"team":"2","scorer":"Chris Lee","user_id":31,"minute":"67"}]}}}},"responses":{"200":{"description":"Goals updated","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"game":{"$ref":"#/components/schemas/GameResult"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Game not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"goals must be an array","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/games/{slug}/marker":{"post":{"summary":"Record a match event marker","description":"Upserts a single event marker for a game. Idempotent — send the same `id` again to update the marker.\n\nThe game must belong to the club identified by your API token.\n\nWhen `game_mode_on` is `true` on the game, only the simplified event set is expected:\n`Start`, `Goal`, `YellowCard`, `RedCard`, `Substitution`, `Break`, `Resume`.\n","operationId":"createMarker","tags":["Match Markers"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Game slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInput"},"example":{"id":"e305e94b-4b10-4bc3-be9d-472d2fb2d2e1","timestamp":"2026-06-02T14:15:22Z","type":"Save","primary_player_pc_id":"8432","secondary_player_pc_id":"9021","label":"Tipped over crossbar"}}}},"responses":{"200":{"description":"Marker updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerResponse"}}}},"201":{"description":"Marker created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Game not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/games/{slug}/markers":{"get":{"summary":"List all markers for a game","description":"Returns all event markers ordered by timestamp.","operationId":"listMarkers","tags":["Match Markers"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Marker list","content":{"application/json":{"schema":{"type":"object","properties":{"game":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"}}},"game_mode":{"type":"boolean","description":"true = simplified event set only"},"markers":{"type":"array","items":{"$ref":"#/components/schemas/Marker"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/games/{slug}/markers/{id}":{"delete":{"summary":"Delete a game marker","description":"Deletes a game marker by client `id`. Reverts associated goal and team score if marker type is Goal/PenaltyScored.","operationId":"deleteMarker","tags":["Match Markers"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Marker deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"404":{"description":"Marker not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournaments/{tournament_slug}/matches/{match_id}/markers":{"get":{"summary":"List all markers for a tournament match","description":"Returns all event markers ordered by timestamp for a tournament match.","operationId":"listTournamentMarkers","tags":["Tournament Markers"],"parameters":[{"name":"tournament_slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"match_id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Tournament marker list","content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"},"name":{"type":"string"}}},"match":{"type":"object"},"markers":{"type":"array","items":{"$ref":"#/components/schemas/Marker"}}}},"example":{"tournament":{"id":18,"slug":"gmc-cup","name":"GMC Cup"},"match":{"id":42,"status":"live","home_team_name":"Home FC","away_team_name":"Away FC"},"markers":[{"id":"marker-start-1","timestamp":"2026-07-20T18:01:00Z","type":"Start","primary_player_pc_id":null,"secondary_player_pc_id":null,"label":null,"primary_player":null,"secondary_player":null},{"id":"marker-goal-1","timestamp":"2026-07-20T18:10:00Z","type":"Goal","primary_player_pc_id":"42","secondary_player_pc_id":null,"label":"Home FC","primary_player":{"id":42,"name":"Alex Rivera"},"secondary_player":null}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Token is not allowed to access this tournament match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Record a tournament match event marker","description":"Creates a marker for a tournament match. Idempotent on `id`.\n\nAccepts tournament API tokens directly, and also accepts club tokens when that club is authorized for the tournament match.\n","operationId":"createTournamentMarker","tags":["Tournament Markers"],"parameters":[{"name":"tournament_slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"match_id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerInput"},"example":{"id":"marker-123","timestamp":"2026-07-14T18:45:00Z","type":"Goal","team":"home","scorer":"Alex","minute":"12"}}}},"responses":{"200":{"description":"Marker already existed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerResponse"}}}},"201":{"description":"Tournament marker created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerResponse"},"example":{"status":"success","marker_id":"marker-123"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Token is not allowed to access this tournament match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tournaments/{tournament_slug}/matches/{match_id}/markers/{id}":{"delete":{"summary":"Delete a tournament match marker","description":"Deletes a tournament match marker by client `id`. Reverts associated goal and recalculates match score if marker type is Goal/PenaltyScored.","operationId":"deleteTournamentMarker","tags":["Tournament Markers"],"parameters":[{"name":"tournament_slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"match_id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Marker deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"match":{"type":"object"}}}}}},"404":{"description":"Marker not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components_extra":{"schemas":{"MarkerInput":{"type":"object","required":["id","timestamp","type"],"properties":{"id":{"type":"string","format":"uuid","description":"Client-generated UUID — used to prevent duplicates (upsert key)"},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 datetime of the event"},"type":{"type":"string","enum":["Start","Break","Resume","End","PenaltyShootout","ResetMatch","Keeper","Goal","Miss","Save","Shot","ChanceCreated","Defense","Foul","YellowCard","RedCard","Substitution","Penalty","PenaltyScored","PenaltyMissed","Event"],"description":"Event type. Lifecycle: Start, Break, Resume, End, PenaltyShootout, ResetMatch. Goals: Goal (own goal via label), PenaltyScored, PenaltyMissed. Shots: Shot (off target, missed frame), Miss (missed goal entirely), Save (GK save — primary_player is GK; shot attributed to opposing team for stats). Discipline: YellowCard, RedCard, Foul. Tactical: Substitution (primary=out, secondary=in), Penalty (structural foul → penalty). Other: Event (free-form timeline entry).\n"},"primary_player_pc_id":{"type":"string","nullable":true,"description":"Player ID from the game roster. May identify a user or junior."},"primary_player_type":{"type":"string","nullable":true,"enum":["user","junior"],"description":"Player identity namespace. Recommended for pickup-game juniors; omitted types are inferred from the game roster."},"secondary_player_pc_id":{"type":"string","nullable":true,"description":"Player ID from the game roster. May identify a user or junior."},"secondary_player_type":{"type":"string","nullable":true,"enum":["user","junior"],"description":"Player identity namespace. Recommended for pickup-game juniors; omitted types are inferred from the game roster."},"label":{"type":"string","nullable":true,"description":"Optional free-text note"}}},"MarkerResponse":{"type":"object","properties":{"status":{"type":"string","example":"success"},"marker_id":{"type":"string","format":"uuid"}}},"Marker":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"timestamp":{"type":"string","format":"date-time"},"type":{"type":"string"},"primary_player_pc_id":{"type":"string","nullable":true},"primary_player_type":{"type":"string","nullable":true,"enum":["user","junior"]},"secondary_player_pc_id":{"type":"string","nullable":true},"secondary_player_type":{"type":"string","nullable":true,"enum":["user","junior"]},"label":{"type":"string","nullable":true},"primary_player":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"name":{"type":"string"},"type":{"type":"string","enum":["user","junior"]}}},"secondary_player":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"name":{"type":"string"},"type":{"type":"string","enum":["user","junior"]}}}}}}},"tags":[{"name":"Club","description":"Read club data — players, positions, jersey numbers, upcoming fixtures"},{"name":"Match Results","description":"Write match scores and goal scorers"},{"name":"Match Markers","description":"Record and retrieve match event markers (iOS app integration)"}]}