{"openapi":"3.1.0","info":{"title":"Fenpai Open Read API","version":"v1","description":"Read-only OpenAPI discovery contract for bot/AI consumers. Discovery stays at /api/open/v1, while the richer public data surface is exposed under /api/v1/data."},"servers":[{"url":"/","description":"Current Fenpai server"}],"security":[{"bearerAuth":[]}],"paths":{"/api/open/v1/meta/version":{"get":{"summary":"Get Open Read API version metadata","security":[],"responses":{"200":{"description":"Open Read API metadata"}}}},"/api/open/v1/meta/capabilities":{"get":{"summary":"Get capability groups and first-wave endpoints","security":[],"responses":{"200":{"description":"Open Read API capability metadata"}}}},"/api/open/v1/openapi.json":{"get":{"summary":"Download the machine-readable OpenAPI document","security":[],"responses":{"200":{"description":"OpenAPI JSON document"}}}},"/api/open/v1/live/match":{"get":{"summary":"Read the current live match summary","responses":{"200":{"description":"Current match summary including a lightweight trackPool summary with trackPoolName, currentTrack, currentSelection, remainingTracks, and lastPlayedTrack."},"404":{"description":"The live match exists but is not public."},"500":{"description":"The server failed to build the live match summary."},"503":{"description":"The live state is temporarily unavailable."}}}},"/api/open/v1/players":{"get":{"summary":"List canonical players for bot/AI lookup","responses":{"200":{"description":"Player listing"}}}},"/api/open/v1/players/{playerKey}":{"get":{"summary":"Read one player profile by canonical player key or display name","description":"Player profile. Track stats are track-scoped; cross-track average absolute time is intentionally omitted.","parameters":[{"name":"playerKey","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Player profile"},"404":{"description":"The requested player profile was not found."},"500":{"description":"The server failed to load the player profile."}}}},"/api/open/v1/matches":{"get":{"summary":"List archived matches with lightweight summaries","description":"Returns public historical match summaries. Duo/trio summaries include teams[] and winnerTeams[] so clients can identify teammates and spray-color teams without inferring from rank order.","parameters":[{"name":"mode","in":"query","required":false,"description":"Optional mode filter.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Title keyword filter.","schema":{"type":"string"}},{"name":"player","in":"query","required":false,"description":"Player keyword filter.","schema":{"type":"string"}},{"name":"track","in":"query","required":false,"description":"Same-track filter after whitespace-insensitive normalization.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Pagination limit.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Match list"},"500":{"description":"The server failed to load the match list."}}}},"/api/open/v1/matches/{matchId}":{"get":{"summary":"Get one archived match by id","description":"Returns one public historical match detail. Historical details include event metadata, duo/trio team context, teammate ids, spray colors, Chinese-readable finishTimeText fields, and title phrases such as 35图 or 抢300 that must be interpreted with the terminology dictionary instead of being paraphrased as stages.","parameters":[{"name":"matchId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Match detail"},"404":{"description":"The requested public match was not found."},"500":{"description":"The server failed to load the match detail."}}}},"/api/open/v1/leaderboards/players":{"get":{"summary":"Get the global player leaderboard","parameters":[{"name":"sortBy","in":"query","required":false,"description":"Sort key such as firstPlaceCount or firstPlaceRate. Legacy values wins and firstRate are compatibility aliases for round first-place count/rate, not whole-match wins.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Optional leaderboard row limit. Omit it to return the full player leaderboard.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Player leaderboard. firstPlaceCount means round first-place count, and firstPlaceRate means round first-place rate."}}}},"/api/open/v1/insights/head-to-head":{"get":{"summary":"Compare two players using historical head-to-head analysis","description":"Head-to-head data is round-based. encounters, winsAgainst, lossesAgainst, and winRate describe direct round results between the two players, not whole-match records.","parameters":[{"name":"playerA","in":"query","required":true,"schema":{"type":"string"}},{"name":"playerB","in":"query","required":true,"schema":{"type":"string"}},{"name":"track","in":"query","required":false,"description":"Optional same-track filter after whitespace-insensitive normalization.","schema":{"type":"string"}}],"responses":{"200":{"description":"Head-to-head insight. Prefer roundEncounters, roundWinsAgainst, roundLossesAgainst, and roundWinRate when present."},"400":{"description":"Missing required playerA/playerB query params."},"404":{"description":"No public head-to-head data matched the request."},"500":{"description":"The server failed to load the head-to-head insight."}}}},"/api/open/v1/overview":{"get":{"summary":"Read global history overview metrics","responses":{"200":{"description":"Global overview"},"500":{"description":"The server failed to load the overview."}}}},"/api/v1/data":{"get":{"summary":"Read the authenticated public data API capability document","responses":{"200":{"description":"Capability document for the /api/v1/data base path"},"401":{"description":"Bearer token is required."}}}},"/api/v1/data/current-match":{"get":{"summary":"Read the current public live match summary","responses":{"200":{"description":"Current public match summary including a lightweight trackPool summary with trackPoolName, currentTrack, currentSelection, remainingTracks, and lastPlayedTrack."},"404":{"description":"The live match exists but is not public."},"500":{"description":"The server failed to build the current public match summary."},"503":{"description":"The live state is temporarily unavailable."}}}},"/api/v1/data/players":{"get":{"summary":"List public players with localized display names","parameters":[{"name":"lang","in":"query","required":false,"description":"Localization target such as zh-Hans, zh-Hant, en, ko, or ja.","schema":{"type":"string"}}],"responses":{"200":{"description":"Localized public player directory"}}}},"/api/v1/data/dictionaries":{"get":{"summary":"List public dictionary namespaces","responses":{"200":{"description":"Dictionary namespace index including players, tracks, metrics, and terminology."}}}},"/api/v1/data/dictionaries/{namespace}":{"get":{"summary":"Read one public dictionary namespace such as players, tracks, metrics, or terminology","parameters":[{"name":"namespace","in":"path","required":true,"description":"Dictionary namespace name.","schema":{"type":"string"}},{"name":"lang","in":"query","required":false,"description":"Localization target for displayName values.","schema":{"type":"string"}}],"responses":{"200":{"description":"Dictionary namespace entries. metrics is the field glossary for public stat names and legacy aliases. terminology is the competition wording glossary for 局/小局/场/比赛/轮次, 35图, 抢300, and RT/未完成, and may expose preferredPhrasing plus avoidPhrasing metadata."},"404":{"description":"The requested dictionary namespace was not found."},"500":{"description":"The server failed to load the dictionary namespace."}}}},"/api/v1/data/tracks":{"get":{"summary":"List public tracks with localized names and metadata","parameters":[{"name":"lang","in":"query","required":false,"description":"Localization target. Requests may pass global language codes such as zh-Hans, zh-Hant, en, ko, or ja, but track translation objects themselves only expose zh-Hans, zh-Hant, and ko and otherwise fall back to zh-Hans.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Keyword search across track names and themes.","schema":{"type":"string"}},{"name":"theme","in":"query","required":false,"description":"Filter by theme key or localized theme name (Simplified Chinese, Traditional Chinese, or Korean).","schema":{"type":"string"}}],"responses":{"200":{"description":"Localized track catalog"},"500":{"description":"The server failed to load the public track catalog."}}}},"/api/v1/data/players/{name}/profile":{"get":{"summary":"Read one public player profile by display name","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player profile. Track stats are track-scoped; cross-track average absolute time is intentionally omitted."}}}},"/api/v1/data/players/{name}/recent-form":{"get":{"summary":"Read fixed player recent-form windows","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"description":"Optional mode filter such as individual, duo, or trio.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player recent-form bundle with fixed windows 3d/7d/10d/14d/30d/all."}}}},"/api/v1/data/players/{name}/recent-form/window":{"get":{"summary":"Read one custom player recent-form window","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Advanced rolling-day window. Use a positive integer such as 17 when you need one custom recent slice instead of the default fixed bundle.","schema":{"type":"integer"}},{"name":"startDate","in":"query","required":false,"description":"Advanced explicit date-range start in YYYY-MM-DD format. Must be paired with endDate, and when both are present they take precedence over days.","schema":{"type":"string"}},{"name":"endDate","in":"query","required":false,"description":"Advanced explicit date-range end in YYYY-MM-DD format. Must be paired with startDate.","schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"description":"Optional mode filter such as individual, duo, or trio.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player recent-form payload for one custom window. Supports rolling-day and date-range queries and returns one normalized window object instead of the fixed bundle."}}}},"/api/v1/data/players/{name}/track-stats/{track}":{"get":{"summary":"Read a player track-specific statistics entry","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}},{"name":"track","in":"path","required":true,"description":"Track name.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player track statistics for one track. Includes bestTimeMs/bestTimeText and should only be compared within the same track."}}}},"/api/v1/data/players/{name}/consistency":{"get":{"summary":"Read a player consistency summary","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player consistency summary"}}}},"/api/v1/data/players/{name}/records":{"get":{"summary":"Read a player notable records bundle","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player records bundle"}}}},"/api/v1/data/players/{name}/rates":{"get":{"summary":"Read a player rate metrics bundle","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}}],"responses":{"200":{"description":"Player rates bundle"}}}},"/api/v1/data/players/{name}/speed-profile":{"get":{"summary":"Read a player same-track speed profile","parameters":[{"name":"name","in":"path","required":true,"description":"Player display name or lookup token.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows to return for ranked sublists such as topByFirstPlaceRate (legacy alias: topByWinRate).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Player same-track speed profile. Returns trackRecords instead of a cross-track raw-time ranking such as topByPb."}}}},"/api/v1/data/head-to-head":{"get":{"summary":"Compare two players with historical head-to-head data","description":"Head-to-head data is round-based. encounters, winsAgainst, lossesAgainst, and winRate describe direct round results between the two players, not whole-match records.","parameters":[{"name":"playerA","in":"query","required":true,"description":"First player display name.","schema":{"type":"string"}},{"name":"playerB","in":"query","required":true,"description":"Second player display name.","schema":{"type":"string"}},{"name":"track","in":"query","required":false,"description":"Optional track filter.","schema":{"type":"string"}}],"responses":{"200":{"description":"Head-to-head analysis. Prefer roundEncounters, roundWinsAgainst, roundLossesAgainst, and roundWinRate when present."}}}},"/api/v1/data/leaderboard":{"get":{"summary":"Read the public player leaderboard","parameters":[{"name":"sortBy","in":"query","required":false,"description":"Sort metric such as firstPlaceCount or firstPlaceRate. Legacy values wins and firstRate are compatibility aliases for round first-place count/rate, not whole-match wins.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Optional maximum rows to return. Omit it to return the full player leaderboard.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Public leaderboard. firstPlaceCount means round first-place count, and firstPlaceRate means round first-place rate."}}}},"/api/v1/data/recent-form-ranking":{"get":{"summary":"Read fixed public recent-form ranking windows","parameters":[{"name":"limit","in":"query","required":false,"description":"Optional maximum rows to return inside each fixed window. Omit it to keep the full ranking list for every returned window.","schema":{"type":"integer"}},{"name":"mode","in":"query","required":false,"description":"Optional mode filter such as individual or duo.","schema":{"type":"string"}}],"responses":{"200":{"description":"Recent-form ranking bundle with fixed windows 3d/7d/10d/14d/30d/all. winRate/firstPlaceRate use a 0-1 ratio, while firstPlaceRatePercent uses a 0-100 percentage."}}}},"/api/v1/data/recent-form-ranking/window":{"get":{"summary":"Read one custom public recent-form ranking window","parameters":[{"name":"limit","in":"query","required":false,"description":"Optional maximum rows to return. Omit it to keep the full qualifying player list for this custom window.","schema":{"type":"integer"}},{"name":"days","in":"query","required":false,"description":"Advanced rolling-day window. Use a positive integer when you need one custom recent slice instead of the default fixed bundle.","schema":{"type":"integer"}},{"name":"startDate","in":"query","required":false,"description":"Advanced explicit date-range start in YYYY-MM-DD format. Must be paired with endDate, and when both are present they take precedence over days.","schema":{"type":"string"}},{"name":"endDate","in":"query","required":false,"description":"Advanced explicit date-range end in YYYY-MM-DD format. Must be paired with startDate.","schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"description":"Optional mode filter such as individual or duo.","schema":{"type":"string"}}],"responses":{"200":{"description":"Recent-form ranking payload for one custom window. Supports rolling-day and date-range queries and returns one normalized window object plus one ranking payload."}}}},"/api/v1/data/tracks/{name}/leaderboard":{"get":{"summary":"Read a same-track full-history leaderboard","parameters":[{"name":"name","in":"path","required":true,"description":"Track name.","schema":{"type":"string"}},{"name":"sortBy","in":"query","required":false,"description":"Sort metric such as pb, played, or winRate (legacy name for same-track first-place rate).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Optional maximum rows to return. Omit it to return the full same-track leaderboard.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Track leaderboard. PB and displayed time values are only comparable within this one track."}}}},"/api/v1/data/tracks/{name}/speed-ranking":{"get":{"summary":"Read fixed same-track speed-ranking windows","parameters":[{"name":"name","in":"path","required":true,"description":"Track name.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Optional maximum rows to return inside each fixed window. Omit it to keep the full same-track result list for every returned window.","schema":{"type":"integer"}},{"name":"mode","in":"query","required":false,"description":"Optional mode filter such as individual, duo, or trio.","schema":{"type":"string"}}],"responses":{"200":{"description":"Track speed-ranking bundle with fixed windows 3d/7d/10d/14d/30d/all. finishTimeMs and finishTimeText are only comparable within this track and within the same returned window."}}}},"/api/v1/data/tracks/{name}/speed-ranking/window":{"get":{"summary":"Read one custom same-track speed-ranking window","parameters":[{"name":"name","in":"path","required":true,"description":"Track name.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Optional maximum rows to return. Omit it to keep the full qualifying same-track result list for this custom window.","schema":{"type":"integer"}},{"name":"days","in":"query","required":false,"description":"Advanced rolling-day window. Use a positive integer when you need one custom same-track slice instead of the default fixed bundle.","schema":{"type":"integer"}},{"name":"startDate","in":"query","required":false,"description":"Advanced explicit date-range start in YYYY-MM-DD format. Must be paired with endDate, and when both are present they take precedence over days.","schema":{"type":"string"}},{"name":"endDate","in":"query","required":false,"description":"Advanced explicit date-range end in YYYY-MM-DD format. Must be paired with startDate.","schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"description":"Optional mode filter such as individual, duo, or trio.","schema":{"type":"string"}}],"responses":{"200":{"description":"Track speed-ranking payload for one custom window. Supports rolling-day and date-range queries and returns one normalized window object. finishTimeMs and finishTimeText are still only comparable within this track."}}}},"/api/v1/data/matches":{"get":{"summary":"List public archived match summaries","description":"Returns public historical match summaries. Duo/trio summaries include teams[] and winnerTeams[] so clients can identify teammates and spray-color teams without inferring from rank order.","parameters":[{"name":"mode","in":"query","required":false,"description":"Optional mode filter.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Title keyword filter.","schema":{"type":"string"}},{"name":"player","in":"query","required":false,"description":"Player keyword filter.","schema":{"type":"string"}},{"name":"track","in":"query","required":false,"description":"Same-track filter after whitespace-insensitive normalization.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Pagination limit.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Public match summaries"}}}},"/api/v1/data/matches/by-day":{"get":{"summary":"Browse public matches grouped by day","parameters":[{"name":"mode","in":"query","required":false,"description":"Optional mode filter.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Title keyword filter.","schema":{"type":"string"}},{"name":"player","in":"query","required":false,"description":"Player keyword filter.","schema":{"type":"string"}},{"name":"track","in":"query","required":false,"description":"Same-track filter after whitespace-insensitive normalization.","schema":{"type":"string"}}],"responses":{"200":{"description":"Public matches grouped by day"}}}},"/api/v1/data/match-history":{"get":{"summary":"Read public match-history rows using the legacy alias endpoint","parameters":[{"name":"mode","in":"query","required":false,"description":"Optional mode filter.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Title keyword filter.","schema":{"type":"string"}},{"name":"player","in":"query","required":false,"description":"Player keyword filter.","schema":{"type":"string"}},{"name":"track","in":"query","required":false,"description":"Same-track filter after whitespace-insensitive normalization.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Pagination limit.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Legacy public match-history rows"}}}},"/api/v1/data/matches/{id}":{"get":{"summary":"Read one full public match detail by id","parameters":[{"name":"id","in":"path","required":true,"description":"Match id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Public match detail. Historical rounds expose roundStats.avgTimeText and results.finishTimeText in Chinese-readable race-time format, and title phrases such as 35图 or 抢300 must be interpreted with the terminology dictionary instead of being paraphrased as stages."},"404":{"description":"The requested public match was not found."},"500":{"description":"The server failed to load the public match detail."}}}},"/api/v1/data/overview":{"get":{"summary":"Read public aggregate overview metrics","responses":{"200":{"description":"Public aggregate overview"}}}},"/api/open/v1/live/track-pool":{"get":{"summary":"Read the current live track-pool detail","responses":{"200":{"description":"Current live track-pool detail with trackPoolName, remaining tracks, used tracks, currentSelection, and pending resolutions."},"404":{"description":"The live match exists but its track-pool context is not public."},"500":{"description":"The server failed to build the live track-pool detail."},"503":{"description":"The live state is temporarily unavailable."}}}},"/api/v1/data/current-track-pool":{"get":{"summary":"Read the current public live track-pool detail","responses":{"200":{"description":"Current public live track-pool detail with trackPoolName, remaining tracks, used tracks, currentSelection, pending resolutions, and per-item status."},"404":{"description":"The live match exists but its track-pool context is not public."},"500":{"description":"The server failed to build the current public live track-pool detail."},"503":{"description":"The live state is temporarily unavailable."}}}},"/api/v1/data/track-pool-templates":{"get":{"summary":"List public track-pool templates","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Optional fuzzy template-name query. Accepts human phrases such as G风哥图池 or X哥图池."}],"responses":{"200":{"description":"Public track-pool template library with trackPoolName, trackCount, updatedAt, and optional query by name."},"500":{"description":"The server failed to load the public track-pool template library."}}}},"/api/v1/data/track-pool-templates/{templateKey}":{"get":{"summary":"Read one public track-pool template detail","parameters":[{"name":"templateKey","in":"path","required":true,"schema":{"type":"string"},"description":"Template id or fuzzy human name such as G风哥图池. Fuzzy name lookup resolves to the best public template detail."}],"responses":{"200":{"description":"Public track-pool template detail with tracks array, rawText, lookup metadata, and fuzzy name lookup support."},"404":{"description":"The requested track-pool template was not found."},"500":{"description":"The server failed to load the public track-pool template detail."}}}},"/api/open/v1/track-pool-templates":{"get":{"summary":"List public track-pool templates","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Optional fuzzy template-name query. Accepts human phrases such as G风哥图池 or X哥图池."}],"responses":{"200":{"description":"Public track-pool template library with trackPoolName, trackCount, updatedAt, and optional query by name."},"500":{"description":"The server failed to load the public track-pool template library."}}}},"/api/open/v1/track-pool-templates/{templateKey}":{"get":{"summary":"Read one public track-pool template detail","parameters":[{"name":"templateKey","in":"path","required":true,"schema":{"type":"string"},"description":"Template id or fuzzy human name such as G风哥图池. Fuzzy name lookup resolves to the best public template detail."}],"responses":{"200":{"description":"Public track-pool template detail with tracks array, rawText, lookup metadata, and fuzzy name lookup support."},"404":{"description":"The requested track-pool template was not found."},"500":{"description":"The server failed to load the public track-pool template detail."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key"}},"schemas":{"SuccessEnvelope":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object"},"meta":{"$ref":"#/components/schemas/Meta"},"links":{"type":"object"}}},"ErrorEnvelope":{"type":"object","required":["ok","error","meta"],"properties":{"ok":{"type":"boolean","const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object"}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"Meta":{"type":"object","required":["apiVersion","generatedAt"],"properties":{"apiVersion":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"analysisScope":{"type":"string"},"source":{"$ref":"#/components/schemas/SourceMeta"},"citations":{"type":"array","items":{"$ref":"#/components/schemas/Citation"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"SourceMeta":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"},"confidence":{"type":"number"}}},"Citation":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"}}},"PaginationMeta":{"type":["object","null"],"properties":{"offset":{"type":"integer"},"limit":{"type":["integer","null"]},"total":{"type":"integer"}}}}}}