{"openapi":"3.1.0","info":{"title":"soku.cleista.cc public API","version":"1.0.0","summary":"Frame data and player ratings for Touhou Hisoutensoku (Touhou 12.3).","description":"Read-only JSON. No key or sign-in. CORS open for GET and the one lookup POST. Limits per address: 120 requests a minute for frame data, 60 for ratings, 120 for stats; past them 429 with Retry-After. Every 200 has an ETag: send If-None-Match and get 304 when nothing changed. Within v1 changes are additive only (new fields, new endpoints); anything else becomes /api/v2/ with v1 kept for at least 6 months. Please link back to https://soku.cleista.cc. Contact: @maristocrat in #hisouten on Discord. Human docs: https://soku.cleista.cc/api","contact":{"name":"@maristocrat in #hisouten (Discord)","url":"https://soku.cleista.cc/api"},"license":{"name":"Frame data and ratings: attribution requested (link to soku.cleista.cc). Game data belongs to Team Shanghai Alice and Twilight Frontier."}},"servers":[{"url":"https://soku.cleista.cc"}],"externalDocs":{"description":"API documentation","url":"https://soku.cleista.cc/api"},"tags":[{"name":"framedata","description":"Frame data for all 20 characters, simulated from the game."},{"name":"ratings","description":"The public rating leaderboard."},{"name":"stats","description":"Per-character picks, win rates and card usage in decks, from the public replays."},{"name":"meta"}],"paths":{"/api/v1/framedata":{"get":{"tags":["framedata"],"operationId":"framedataIndex","summary":"Characters, the data build and links","parameters":[{"name":"build","in":"query","description":"Pin a build: with the current build id the answer is cacheable for a year; an older id redirects (302) to the current one.","schema":{"type":"string"}}],"responses":{"200":{"description":"The index","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FramedataIndex"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/framedata/resolve":{"get":{"tags":["framedata"],"operationId":"framedataResolve","summary":"Find a move by notation, alias, card name or spell cost","description":"The resolver the site's Discord bot uses. Characters: keys, names in English, Japanese and Chinese, and common aliases (okuu, remi, patchy...). Moves: slug, notation (\"5A\", \"f5a\", \"j2b\", \"6[a]\" or \"6a charged\" for the held version, \"alt 236b\" for an alternative skill), card id (\"sc209\"), card name in any language, or a spell cost (\"5 card\"). One result is `match`; several are `candidates`.","parameters":[{"name":"character","in":"query","description":"A character key, name or alias.","schema":{"type":"string"},"required":true,"example":"reimu"},{"name":"q","in":"query","description":"What was typed for the move. Empty lists every move.","schema":{"type":"string"},"example":"623b"},{"name":"locale","in":"query","description":"Language of `name`: en, ja, zh-CN or ko.","schema":{"type":"string","enum":["en","ja","zh-CN","ko"]}},{"name":"limit","in":"query","description":"At most this many candidates (1-25).","schema":{"type":"integer","minimum":1,"maximum":25}},{"name":"build","in":"query","description":"Pin a build: with the current build id the answer is cacheable for a year; an older id redirects (302) to the current one.","schema":{"type":"string"}}],"responses":{"200":{"description":"A match, or candidates (both empty when nothing matched)","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveResult"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/framedata/{character}":{"get":{"tags":["framedata"],"operationId":"framedataCharacter","summary":"A character's moves with summary fields","parameters":[{"name":"character","in":"path","required":true,"description":"Character key (from the index).","schema":{"type":"string"},"example":"reimu"},{"name":"kind","in":"query","description":"Comma list of normal, skill, spell, system, object, or \"all\". Default: normal,skill,spell (spells a deck can hold).","schema":{"type":"string"}},{"name":"build","in":"query","description":"Pin a build: with the current build id the answer is cacheable for a year; an older id redirects (302) to the current one.","schema":{"type":"string"}}],"responses":{"200":{"description":"The move list","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveList"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/framedata/{character}/{move}":{"get":{"tags":["framedata"],"operationId":"framedataMove","summary":"One move in full","description":"Frames, advantage, hit properties, per-hit data, cancels, other properties, state windows, bullets and other objects, variants and the move's video when rendered. Values that were not measured are null; `unavailable` says why.","parameters":[{"name":"character","in":"path","required":true,"description":"Character key.","schema":{"type":"string"},"example":"reimu"},{"name":"move","in":"path","required":true,"description":"Move slug (from the move list or the resolver).","schema":{"type":"string"},"example":"5a"},{"name":"level","in":"query","description":"Skill level (0-4) for skills that have levels; ignored otherwise.","schema":{"type":"integer","minimum":0}},{"name":"charged","in":"query","description":"1 for the held (charged) version, where the move has one.","schema":{"type":"string","enum":["0","1"]}},{"name":"outcome","in":"query","description":"\"whiff\" for the version with the opponent out of reach, where it differs; \"hit\" (default) otherwise.","schema":{"type":"string","enum":["hit","whiff"]}},{"name":"build","in":"query","description":"Pin a build: with the current build id the answer is cacheable for a year; an older id redirects (302) to the current one.","schema":{"type":"string"}}],"responses":{"200":{"description":"The move","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveDetail"}}}},"301":{"description":"A renamed or old slug: Location is the move's current URL."},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/ratings":{"get":{"tags":["ratings"],"operationId":"ratingsLeaderboard","summary":"The leaderboard, filtered and paged","parameters":[{"name":"character","in":"query","description":"Rank by this character's rating (a character key).","schema":{"type":"string"}},{"name":"min","in":"query","description":"At least this many rated games (overall, or as the character). Default 1.","schema":{"type":"integer","minimum":1}},{"name":"status","in":"query","description":"active (default), provisional, inactive or all.","schema":{"type":"string","enum":["active","provisional","inactive","all"]}},{"name":"community","in":"query","description":"A community id from /api/v1/ratings/communities.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Keep players whose name or handle contains this (case- and width-insensitive). Ranks are unchanged.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, 1-1000. Default 100.","schema":{"type":"integer","minimum":1,"maximum":1000}},{"name":"offset","in":"query","description":"Rows to skip. Default 0.","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"A page of the leaderboard","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Leaderboard"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/ratings/players":{"get":{"tags":["ratings"],"operationId":"ratingsPlayers","summary":"Look up to 100 players at once","description":"Each name is tried as an in-game name (the account that claimed it, else the name itself; case- and width-insensitive), then as a site handle. \"@name\" is a handle only. Only players the public leaderboard shows can be found.","parameters":[{"name":"names","in":"query","description":"Comma-separated names, at most 100 in all.","schema":{"type":"string"},"example":"Alice,Bob"},{"name":"name","in":"query","description":"One name; may repeat. For names that contain a comma.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"Found players and the names that matched nobody","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerBatch"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["ratings"],"operationId":"ratingsPlayersPost","summary":"Look up to 1000 players at once (JSON body)","description":"The same lookup and answer as GET, for more names than a URL holds. Duplicate names collapse. Content-Type must be application/json; the body is at most 64 KB. Not cached. For every player at once use GET /api/v1/ratings/export.json.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["names"],"properties":{"names":{"type":"array","maxItems":1000,"items":{"type":"string","maxLength":64},"description":"In-game names or handles; \"@name\" is a handle only."}},"example":{"names":["Alice","@bob","Carol"]}}}}},"responses":{"200":{"description":"Found players and the names that matched nobody","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerBatch"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/ratings/export.json":{"get":{"tags":["ratings"],"operationId":"ratingsExport","summary":"Every rated player, all statuses, in one file","description":"The public leaderboard's players of every status with the fields a lookup carries (without `query`), built once per ratings rebuild and sent precompressed (brotli or gzip by Accept-Encoding; about 3 MB raw, under 0.5 MB compressed for 7,000 players). `rank` is the rank on the default board (active, overall), null otherwise. Poll with If-None-Match; check `computedAt` before downloading again.","responses":{"200":{"description":"All players","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"count":{"type":"integer"},"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}}}}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/ratings/players/{name}":{"get":{"tags":["ratings"],"operationId":"ratingsPlayer","summary":"One player by in-game name or handle","parameters":[{"name":"name","in":"path","required":true,"description":"In-game name or handle (URL-encoded); \"@handle\" for a handle only.","schema":{"type":"string"},"example":"Alice"}],"responses":{"200":{"description":"The player","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"type":"object","required":["apiVersion","computedAt","player"],"properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"player":{"$ref":"#/components/schemas/PlayerMatch"}}}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/ratings/brackets":{"get":{"tags":["ratings"],"operationId":"ratingsBrackets","summary":"Per-character win rate and play share by rating bracket","responses":{"200":{"description":"The bracket table","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brackets"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/ratings/ranges":{"get":{"tags":["ratings"],"operationId":"ratingsRanges","summary":"Per-character win rate, play share and the matchup matrix for a custom range","description":"Each pick is placed by its own player's rating and earlier rated games just before the game. The table has the brackets' shape: \"all\" is the whole range, b0-b4 the bracket columns clipped to it (settled players), \"new\" players under settledGames earlier games. matrix.games[a][b] counts games of characters[a] against characters[b] where BOTH players are in range (rule \"both\"); matrix.wins[a][b] is a's wins (null on the diagonal: mirrors are not in win rate). Unknown or repeated parameters are a 400.","parameters":[{"name":"rmin","in":"query","description":"Lowest pre-game rating (inclusive), a multiple of 25 from 0. Default 0.","schema":{"type":"integer","minimum":0,"maximum":4000,"multipleOf":25}},{"name":"rmax","in":"query","description":"Rating the range stops below (exclusive), a multiple of 25 up to 4000. Default 4000 (no limit). Above rmin.","schema":{"type":"integer","minimum":25,"maximum":4000,"multipleOf":25}},{"name":"gmin","in":"query","description":"Fewest earlier rated games the player had (inclusive). Default 0.","schema":{"type":"integer","enum":[0,10,20,50,100,200,500]}},{"name":"gmax","in":"query","description":"Earlier rated games the player stays under (exclusive). Default none. Above gmin.","schema":{"type":"integer","enum":[10,20,50,100,200,500]}},{"name":"from","in":"query","description":"First UTC month, YYYY-MM. Default: the first game.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"}},{"name":"to","in":"query","description":"Last UTC month (inclusive), YYYY-MM. Default: the latest game.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"}},{"name":"close","in":"query","description":"1: only games whose two pre-game ratings were at most maxGap (200) apart.","schema":{"type":"string","enum":["0","1"]}}],"responses":{"200":{"description":"The range","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Range"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/ratings/ranges/players":{"get":{"tags":["ratings"],"operationId":"ratingsRangePlayers","summary":"The players with the most games in one cell of a range","description":"Up to 10 players, most games first, among those the public leaderboard shows. Without vs: the table's cell (picks of char by players in range). With vs: the matrix cell (char against vs, both players in range). Mirrors count in games only.","parameters":[{"name":"rmin","in":"query","description":"Lowest pre-game rating (inclusive), a multiple of 25 from 0. Default 0.","schema":{"type":"integer","minimum":0,"maximum":4000,"multipleOf":25}},{"name":"rmax","in":"query","description":"Rating the range stops below (exclusive), a multiple of 25 up to 4000. Default 4000 (no limit). Above rmin.","schema":{"type":"integer","minimum":25,"maximum":4000,"multipleOf":25}},{"name":"gmin","in":"query","description":"Fewest earlier rated games the player had (inclusive). Default 0.","schema":{"type":"integer","enum":[0,10,20,50,100,200,500]}},{"name":"gmax","in":"query","description":"Earlier rated games the player stays under (exclusive). Default none. Above gmin.","schema":{"type":"integer","enum":[10,20,50,100,200,500]}},{"name":"from","in":"query","description":"First UTC month, YYYY-MM. Default: the first game.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"}},{"name":"to","in":"query","description":"Last UTC month (inclusive), YYYY-MM. Default: the latest game.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"}},{"name":"close","in":"query","description":"1: only games whose two pre-game ratings were at most maxGap (200) apart.","schema":{"type":"string","enum":["0","1"]}},{"name":"char","in":"query","description":"A character key.","schema":{"type":"string"},"required":true,"example":"reimu"},{"name":"vs","in":"query","description":"The opponent's character key, for a matrix cell.","schema":{"type":"string"}}],"responses":{"200":{"description":"The players","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RangePlayers"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/ratings/communities":{"get":{"tags":["ratings"],"operationId":"ratingsCommunities","summary":"The player communities the leaderboard filter takes","responses":{"200":{"description":"Communities","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"communities":{"type":"array","items":{"$ref":"#/components/schemas/Community"}}}}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/stats/characters":{"get":{"tags":["stats"],"operationId":"statsCharacters","summary":"Picks, play share and win rate over a window","description":"From every public replay; a game is two picks. Win rates are over games with a result, mirrors excluded. `decks` counts the decks of the character as the card endpoint does. The same body is served at /api/stats/characters.","parameters":[{"name":"window","in":"query","description":"The time window, ending now: 7d, 30d (default) or all.","schema":{"type":"string","enum":["7d","30d","all"],"default":"30d"}}],"responses":{"200":{"description":"The overview","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CharacterOverview"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/stats/characters/{character}/cards":{"get":{"tags":["stats"],"operationId":"statsCharacterCards","summary":"Card usage in the decks of a character","description":"Decks come from the replay headers. Each player-game counts once, and within one set the same player bringing the same deck counts as one deck (its result is its share of wins there), so a long set does not dominate. Per card: `share` of the counted decks that run it; `avgCopies` and the `copies` distribution over the decks that run it; `winRate` / `winRateWithout` over counted decks with a result that do / do not run it, null unless both have at least 30; `games` the player-games with a result whose deck ran it. Only cards some deck ran are listed. The same body is served at /api/stats/characters/{character}/cards.","parameters":[{"name":"character","in":"path","required":true,"description":"Character key.","schema":{"type":"string"},"example":"reimu"},{"name":"window","in":"query","description":"The time window, ending now: 7d, 30d (default) or all.","schema":{"type":"string","enum":["7d","30d","all"],"default":"30d"}}],"responses":{"200":{"description":"Card usage","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CharacterCards"}}}},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/openapi.json":{"get":{"tags":["meta"],"operationId":"openapi","summary":"This document","responses":{"200":{"description":"OpenAPI 3.1","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"headers":{"ETag":{"description":"Weak validator of the body; send it back as If-None-Match.","schema":{"type":"string"}},"CacheControl":{"description":"public, max-age=300 for frame data (a year, immutable, with ?build=); public, max-age=60 for ratings; public, max-age=300 for stats.","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"A parameter is invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such character, move or player","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PayloadTooLarge":{"description":"The body is over the limit, or a POST names too many players","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnsupportedMediaType":{"description":"A POST needs Content-Type: application/json","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Over the per-address budget; wait Retry-After seconds","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"Frame data is not built on this server","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"A stable code, e.g. unknown_move, invalid_min, rate_limited."},"message":{"type":"string"},"retryAfter":{"type":"integer"}}},"Names":{"type":"object","description":"Localized names by locale (en, ja, zh-CN, ko).","additionalProperties":{"type":"string"}},"FramedataIndex":{"type":"object","required":["apiVersion","build","characters"],"properties":{"apiVersion":{"const":1},"build":{"type":"string","description":"The data build id; changes when any frame data changes."},"dataVersion":{"type":["integer","null"]},"characters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"names":{"$ref":"#/components/schemas/Names"},"moves":{"type":"integer"},"api":{"type":"string","format":"uri"},"url":{"type":"string","format":"uri"}}}},"links":{"type":"object","additionalProperties":{"type":"string"}}}},"ResolveCandidate":{"type":"object","properties":{"slug":{"type":"string"},"notation":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string"},"card":{"type":["integer","null"]},"cost":{"type":["integer","null"]},"variant":{"type":["object","null"],"properties":{"level":{"type":["integer","null"]},"charged":{"type":"boolean"}}},"api":{"type":"string","format":"uri"},"url":{"type":"string","format":"uri"}}},"ResolveResult":{"type":"object","properties":{"apiVersion":{"const":1},"build":{"type":"string"},"query":{"type":"string"},"locale":{"type":"string"},"character":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"names":{"$ref":"#/components/schemas/Names"}}},"match":{"oneOf":[{"$ref":"#/components/schemas/ResolveCandidate"},{"type":"null"}]},"candidates":{"type":"array","items":{"$ref":"#/components/schemas/ResolveCandidate"}}}},"MoveListEntry":{"type":"object","properties":{"slug":{"type":"string"},"notation":{"type":"string"},"notations":{"type":"array","items":{"type":"string"}},"names":{"$ref":"#/components/schemas/Names"},"kind":{"type":"string","enum":["normal","skill","spell","system","object"]},"card":{"type":["integer","null"]},"cost":{"type":["integer","null"],"description":"Spell card cost."},"icon":{"type":["string","null"],"format":"uri"},"summary":{"type":"object","description":"The values the character page's move table shows.","properties":{"startup":{"type":["integer","null"]},"recovery":{"type":["integer","null"]},"total":{"type":["integer","null"]},"damage":{"type":["integer","null"]},"advantageOnHit":{"type":["integer","null"]},"advantageOnBlock":{"type":["integer","null"]},"advantageOnWrongBlock":{"type":["integer","null"]},"damagePerHit":{"type":["array","null"],"items":{"type":"integer"},"description":"The .pat hit data per hit at the skill level, distinct values."},"damageTotal":{"type":["integer","null"],"description":"What the dummy lost in the measured run, where several hits land or the move has no hit data."}}},"api":{"type":"string","format":"uri"},"url":{"type":"string","format":"uri"}}},"MoveList":{"type":"object","properties":{"apiVersion":{"const":1},"build":{"type":"string"},"character":{"type":"object","properties":{"key":{"type":"string"},"names":{"$ref":"#/components/schemas/Names"}}},"kinds":{"type":"array","items":{"type":"string"}},"moves":{"type":"array","items":{"$ref":"#/components/schemas/MoveListEntry"}}}},"MoveDetail":{"type":"object","properties":{"apiVersion":{"const":1},"build":{"type":"string"},"character":{"type":"object","properties":{"key":{"type":"string"},"names":{"$ref":"#/components/schemas/Names"}}},"move":{"type":"object","properties":{"slug":{"type":"string"},"notation":{"type":"string"},"notations":{"type":"array","items":{"type":"string"}},"names":{"$ref":"#/components/schemas/Names"},"kind":{"type":"string"},"card":{"type":["integer","null"]},"cost":{"type":["integer","null"]},"icon":{"type":["string","null"]},"url":{"type":"string","format":"uri","description":"The move's page, this variant."},"variant":{"type":"object","properties":{"level":{"type":["integer","null"]},"charged":{"type":"boolean"},"outcome":{"type":"string","enum":["hit","whiff"]}}},"variants":{"type":"array","items":{"type":"object","properties":{"level":{"type":["integer","null"]},"charged":{"type":"boolean"},"outcome":{"type":"string"},"query":{"type":"string"},"api":{"type":"string","format":"uri"}}}},"measured":{"type":"boolean","description":"Whether frame values were measured by simulation for this build."},"frames":{"type":"object","properties":{"startup":{"type":["integer","null"]},"startupFrom":{"type":["string","null"],"description":"\"projectile\" when startup is the first bullet's hit."},"startupRelease":{"type":["integer","null"]},"firstActive":{"type":["integer","null"]},"active":{"type":["array","null"],"items":{"type":"array","items":{"type":"integer"},"minItems":2,"maxItems":2},"description":"Inclusive [first, last] frame ranges, frame 1 being the first frame of the move."},"recovery":{"type":["integer","null"]},"total":{"type":["integer","null"]},"loops":{"type":"boolean"},"spellFlash":{"type":["object","null"],"description":"Spell cards: the declaration freeze (spellflash). `frames` frozen frames come after frame `after`; no frame number here counts them.","properties":{"after":{"type":"integer"},"frames":{"type":"integer"}}}}},"advantage":{"type":"object","description":"Frame advantage, measured in the corner against Sanae with no cancel; null when not measured.","properties":{"onHit":{"type":["integer","null"]},"onBlock":{"type":["integer","null"]},"onWrongBlock":{"type":["integer","null"]}}},"hit":{"type":"object","properties":{"damage":{"type":["integer","null"],"description":"One hit: its .pat damage at the skill level. Several hits, or no hit data: the measured total. Null for neither."},"damagePerHit":{"type":["array","null"],"items":{"type":"integer"}},"damageTotal":{"type":["integer","null"]},"spiritDamage":{"type":["number","null"]},"spiritOrbs":{"type":["number","null"]},"meterOnHit":{"type":["integer","null"]},"hitstun":{"type":["integer","null"]},"blockstun":{"type":["integer","null"]},"wrongBlockstun":{"type":["integer","null"]},"untech":{"type":["integer","null"]},"hitstop":{"type":["array","null"]},"blockstop":{"type":["array","null"]},"hits":{"type":["integer","null"]},"comboRate":{"type":["number","null"],"description":"Proration after the last hit, 0-1."},"limit":{"type":["integer","null"]}}},"startupFlash":{"type":["object","null"],"description":"Spell cards: startup split at the flash (pre + post, flash frames not counted); flashFrame and firstActiveFrame count ticks from the press, flash included."},"perHit":{"type":"array","items":{"type":"object"}},"cancels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"frames":{"type":"array","items":{"type":"array","items":{"type":"integer"},"minItems":2,"maxItems":2},"description":"Inclusive [first, last] frame ranges, frame 1 being the first frame of the move."},"into":{"type":"array","items":{"type":"string"}},"condition":{"type":["string","null"]}}}},"properties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"frames":{"type":"array","items":{"type":"array","items":{"type":"integer"},"minItems":2,"maxItems":2},"description":"Inclusive [first, last] frame ranges, frame 1 being the first frame of the move."}}}},"states":{"type":"object","description":"Frame ranges per state (Melee invul, Grazing, Guard point...).","additionalProperties":{"type":"array","items":{"type":"array","items":{"type":"integer"},"minItems":2,"maxItems":2},"description":"Inclusive [first, last] frame ranges, frame 1 being the first frame of the move."}},"objects":{"type":"array","items":{"type":"object","properties":{"action":{"type":["integer","null"]},"sequence":{"type":["integer","null"]},"name":{"type":"string"},"role":{"type":["string","null"]},"count":{"type":"integer"},"hits":{"type":"boolean"},"spawnFrame":{"type":["number","null"]},"firstHitFrame":{"type":["number","null"]},"hitLimit":{"type":["object","null"],"properties":{"min":{"type":"number"},"max":{"type":"number"}}},"speed":{"type":["object","null"],"properties":{"min":{"type":"number"},"max":{"type":"number"}}},"damagePerHit":{"type":"array","items":{"type":"number"}},"density":{"type":"array","items":{"type":"string"}},"graze":{"type":["string","null"]}}}},"video":{"type":["object","null"],"properties":{"status":{"type":"string","enum":["ready","missing","queued","running","failed","unavailable"]},"url":{"type":"string","format":"uri"},"poster":{"type":["string","null"]},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}},"unavailable":{"type":"object","additionalProperties":{"type":"string"},"description":"Why a value is null."}}}}},"Player":{"type":"object","required":["rank","name","rating","rd","status"],"properties":{"rank":{"type":["integer","null"],"description":"Leaderboard rank within the request's filters; in a lookup, the rank on the default board (active players, overall), null when not active."},"name":{"type":"string"},"handle":{"type":["string","null"],"description":"Site handle of an account (profile at /u/{handle}); null for an unclaimed in-game name."},"playedAs":{"type":["string","null"],"description":"The in-game name, for an unclaimed name."},"identity":{"type":"boolean","description":"Present (true) when the row is one of two people who share an in-game name: `name` is then a neutral label such as \"inaba · 2\", and the row has no handle, playedAs or profile."},"alsoPlayedAs":{"type":"array","description":"Name lookups only, when other people also play under the name asked for: each one's label, and its handle when it is a public account. The row itself is the name's default player.","items":{"type":"object","required":["label"],"properties":{"label":{"type":"string"},"handle":{"type":"string"},"profileUrl":{"type":"string","format":"uri"}}}},"country":{"type":"string","pattern":"^[A-Z]{2}$","description":"The flag the site shows for the player (ISO 3166-1 alpha-2); absent when none, or when an account chose not to show one."},"rating":{"type":"number"},"rd":{"type":"number","description":"Rating deviation (uncertainty), grown since the last game."},"games":{"type":"integer"},"wins":{"type":"integer"},"losses":{"type":"integer"},"status":{"type":"string","enum":["active","provisional","inactive"]},"lastAt":{"type":["integer","null"],"description":"Last rated game, ms since the epoch."},"community":{"type":["string","null"]},"characters":{"type":"array","items":{"type":"object","properties":{"character":{"type":"string"},"rating":{"type":"number"},"games":{"type":"integer"}}}},"character":{"type":"object","description":"With ?character=: that character's rating."},"profileUrl":{"type":["string","null"],"format":"uri","description":"The account's profile, the unclaimed name's replays; null for an identity row."}}},"PlayerMatch":{"allOf":[{"$ref":"#/components/schemas/Player"},{"type":"object","properties":{"query":{"type":"string","description":"The name as asked."}}}]},"Leaderboard":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"],"description":"When the ratings were last rebuilt, ms since the epoch."},"filters":{"type":"object"},"total":{"type":"integer"},"offset":{"type":"integer"},"limit":{"type":"integer"},"players":{"type":"array","items":{"$ref":"#/components/schemas/Player"}}}},"PlayerBatch":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerMatch"}},"notFound":{"type":"array","items":{"type":"string"}}}},"Brackets":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"brackets":{"type":"array"},"cells":{"type":"object"},"characters":{"type":"array","items":{"type":"string"}},"close":{"type":"object"}}},"CharacterOverview":{"type":"object","required":["window","games","characters"],"properties":{"window":{"type":"string","enum":["7d","30d","all"]},"from":{"type":"integer","description":"Window start, ms since the epoch."},"to":{"type":"integer"},"games":{"type":"integer"},"generatedAt":{"type":"integer"},"characters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"picks":{"type":"integer"},"share":{"type":["number","null"],"description":"Of all picks, 0-1."},"wins":{"type":"integer"},"losses":{"type":"integer"},"winRate":{"type":["number","null"],"description":"Over games with a result, mirrors excluded."},"decks":{"type":"integer","description":"Counted decks (see the cards endpoint)."},"prevShare":{"type":["number","null"],"description":"The share in the window before this one (null for all)."},"prevWinRate":{"type":["number","null"]}}}}}},"CharacterCards":{"type":"object","required":["character","window","decks","cards"],"properties":{"character":{"type":"string"},"window":{"type":"string","enum":["7d","30d","all"]},"decks":{"type":"integer","description":"Counted decks."},"cards":{"type":"array","items":{"type":"object","required":["id","kind","share","avgCopies","copies","winRate","winRateWithout","games"],"properties":{"id":{"type":"integer","description":"Card id in the game: 0-20 system, 100+ skill, 200+ spell."},"kind":{"type":"string","enum":["system","skill","spell"]},"share":{"type":"number","minimum":0,"maximum":1},"avgCopies":{"type":"number"},"copies":{"type":"object","properties":{"1":{"type":"integer"},"2":{"type":"integer"},"3":{"type":"integer"},"4":{"type":"integer"}}},"winRate":{"type":["number","null"],"minimum":0,"maximum":1},"winRateWithout":{"type":["number","null"],"minimum":0,"maximum":1},"games":{"type":"integer"}}}}}},"Range":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"filters":{"type":"object"},"characters":{"type":"array","items":{"type":"string"}},"settledGames":{"type":"integer"},"maxGap":{"type":"integer"},"gamesEdges":{"type":"array","items":{"type":"integer"}},"brackets":{"type":"array"},"cells":{"type":"object"},"matrix":{"type":"object","properties":{"rule":{"const":"both"},"games":{"type":"array","items":{"type":"array","items":{"type":"integer"}}},"wins":{"type":"array","items":{"type":"array","items":{"type":["integer","null"]}}}}}}},"RangePlayers":{"type":"object","properties":{"apiVersion":{"const":1},"computedAt":{"type":["integer","null"]},"filters":{"type":"object"},"distinct":{"type":"integer","description":"How many players the cell has in all, listed or not."},"players":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"handle":{"type":["string","null"]},"playedAs":{"type":["string","null"]},"games":{"type":"integer"},"wins":{"type":"integer"},"losses":{"type":"integer"},"winRate":{"type":["number","null"]},"profileUrl":{"type":"string","format":"uri"}}}}}},"Community":{"type":"object","properties":{"id":{"type":"string","description":"What ?community= takes."},"name":{"type":["string","null"]},"number":{"type":["integer","null"]},"players":{"type":"integer","description":"Players on the public leaderboard in it."}}}}}}