Frame data and player ratings as JSON. Read-only, no key, CORS open. Please use it instead of fetching the HTML pages, and link back to soku.cleista.cc when you show the data.
Machine-readable: OpenAPI 3.1. For AI agents: /llms.txt. Questions: ping @maristocrat in #hisouten on Discord.
Frame data
GET /api/v1/framedata | characters, the data build id |
GET /api/v1/framedata/{character} | moves with startup, recovery, total, damage, advantage; ?kind=normal,skill,spell,system,object|all |
GET /api/v1/framedata/{character}/{move} | one move in full: frames, advantage, hit data, per hit, cancels, invulnerability and other states, bullets, variants, video; ?level=&charged=1&outcome=whiff |
GET /api/v1/framedata/resolve?character=&q= | find a move from what a player types: "f5a", "j2b", "6[a]", "alt 236b", "sc209", a card name, "5 card" |
Frame ranges are inclusive, frame 1 is the move's first frame. Advantage is measured in the corner against Sanae with no cancel; a value that was not measured is null and unavailable says why.
curl https://soku.cleista.cc/api/v1/framedata/resolve?character=okuu&q=hell%20geyser
curl https://soku.cleista.cc/api/v1/framedata/reimu/5a
Ratings
GET /api/v1/ratings | the leaderboard: ?character=&min=&status=active|provisional|inactive|all&community=&q=&limit=&offset= |
GET /api/v1/ratings/players?names=a,b,c | up to 100 players in one request, by in-game name or handle (@handle for a handle only); returns players and notFound |
POST /api/v1/ratings/players | the same from a JSON body {"names": ["a", "b"]} (Content-Type: application/json), up to 1,000 names, body up to 64 KB |
GET /api/v1/ratings/export.json | every rated player of every status in one file (about 3 MB, brotli or gzip when accepted), rebuilt with the ratings: computedAt, count, players; send If-None-Match |
GET /api/v1/ratings/players/{name} | one player; for a name more than one person plays under, its default player plus alsoPlayedAs: [{ label, handle? }] (a row with identity: true is such a second person, under its neutral label, with no profile) |
GET /api/v1/ratings/brackets | win rate and play share per character by rating bracket |
GET /api/v1/ratings/ranges | the same table for a custom range, plus the 20×20 matchup matrix: ?rmin=&rmax= (multiples of 25), gmin=&gmax= (games played), from=&to= (YYYY-MM), close=1 (hide large rating gaps); a matrix game counts only when both players are in range |
GET /api/v1/ratings/ranges/players | the 10 players with the most games in one cell of a range: the same filters plus char= (a table cell) and vs= (a matrix cell) |
GET /api/v1/ratings/communities | the communities ?community= takes |
Only players the public leaderboard shows. Look players up in batches rather than one request each, or take the export for all of them.
Character stats
GET /api/v1/stats/characters?window=7d|30d|all | every character's picks, play share and win rate over the window (30 days by default) |
GET /api/v1/stats/characters/{character}/cards?window= | card usage in the character's decks: per card the share of decks that run it, average copies, the 1-4 copy counts, and win rate with and without it (null on a small sample) |
From the public replays and the decks in their headers; within a set the same player's same deck counts once. Also served without /v1 (/api/stats/characters…). The page: Stats → Characters.
Limits and caching
- Per address: 120 requests a minute for frame data, 60 for ratings, 120 for character stats. Past that:
429 with Retry-After.
- Every answer has an
ETag; send If-None-Match and get 304 when nothing changed. Frame data changes only when the site is updated (its build changes); ratings are rebuilt at most every 10 minutes, when new games were judged (computedAt).
- Frame data: cache 5 minutes, or a year with
?build={build} (an old build redirects to the current one). Ratings: 60 seconds. Character stats: 5 minutes.
- Errors:
{"error": "code", "message": "..."}.
Versioning
Within v1 changes only add: new fields, new endpoints, new values. Ignore fields you do not know. Anything that would break a client goes to /api/v2/, and v1 stays up at least six months after that, announced in #hisouten and the changelog.