A senior full-stack developer writing on dev.to under the name Rinat has published a first-person account of four successive attempts to feed match data into a personal League of Legends analytics project called Riftmind. The post is a community write-up rather than a vendor release or a peer-reviewed study, so every performance figure below is the author's own reported measurement and has not been independently verified. The author states he has been developing professionally and for fun for more than eleven years.
The project's stated purpose is to analyze raw match data from the game and surface win rates by role, rank and region, along with synergies, counterpicks and draft analysis. The author says Riftmind was never intended to generate revenue, a constraint that shaped the engineering problem because infrastructure spending had to stay minimal. He describes splitting the system into three applications: an ingestion service running continuously, pulling from Riot's public API and writing raw data to its own database; a processing service run on a schedule or on demand that computes aggregates and copies them to a production database; and a published web app hosted on Render holding only a small database of aggregated data. Ingestion and processing first ran on his local PC, where collected data reached roughly 150GB, before moving to a cheap VPS.
Two constraints drive all four attempts. The first is rate limiting: the author writes that small projects get about 100 calls per two minutes per server, and that match data flows through only four regional servers, which he puts at roughly 200 match API calls per minute worldwide. Calls about players and ranks go to fourteen other game servers, each with its own limit. He also notes there is no endpoint to fetch matches by rank directly — you can retrieve a specific player's matches, or retrieve players belonging to a rank, but not matches filtered by rank.
The second constraint is statistical. The author writes that confirming one champion is stronger than another requires at least 1,000 matches between them, and even then the margin of error is around ±3%, while most matchup effects are only 1–2% and the strongest counterpicks reach 5–8%. Riot also ships a new patch roughly every two weeks, so the data must be both large and fresh.
The first attempt was a crawler. The author manually selected active players from a third-party site as seeds, fetched their games, then expanded to the other players appearing in those games, since each match contains ten participants. This worked for a month or two but degraded. Rank balance could not be maintained reliably, because fetching a match reveals only the rank of the player whose game was requested, and the queue drifted toward lower ranks at higher tiers. Choosing which player to crawl next required an ad-hoc scoring system, and the logic grew more complex without improving results. By the time he abandoned it, the crawler was 1,790 lines of code with 17 tunable settings and a player queue capped at 250,000 entries.
The second attempt abandoned crawling for the ladder endpoint, which returns lists of players in a given rank at about 200 per page. The algorithm fetched players from the least-represented rank, pulled each player's 20 most recent match IDs, downloaded games from the current patch and stopped at the first older-patch game, then placed those players on a 48-hour cooldown. The code was roughly half the size and the crawler logic was deleted the same day, but most sampled players had no new games. Over patch 16.19, a two-week span, a random player yielded only 0.66 new matches on average, and about 38 of every 100 match API calls returned a new match — roughly 2,900 new matches per hour.
The third attempt tested Riot's Spectator endpoint, which reports whether a player is currently in a game and returns the game ID, allowing the match to be downloaded with a single call once it ends. Spectator calls go to the fourteen game servers whose limits were barely used, so the plan was to spend those cheap calls on discovery and reserve expensive match calls for downloads. Run for ninety minutes as a side experiment that recorded games without downloading them, it found only 156 players in a game from 5,340 calls, yielding 62 unique games. The author estimates that even using the full spare limit across all fourteen servers would find under 500 games per hour, against roughly 1,700 per hour from random sampling at that time. He dropped the approach but credits it with clarifying what he actually needed: a cheap signal for which players had played recently.
The fourth attempt combined the ladder with that signal. Instead of sampling random players, the ingestion requested 200 players from the ladder in a single call — ladder calls use the same fourteen servers with spare limits, so the author treats them as effectively free — waited 30 minutes, requested the same page again, and checked whose game count (wins plus losses) had changed. Only players with actual new games were fetched. In a seven-hour side experiment, the signal proved precise: a player whose wins plus losses grew by one had on average 0.95 new games available, by two had 1.95, and by three had 2.91. He switched the same day.
Under the present arrangement, 20 ladder pages for each division get re-read every two hours; 30 minutes proved too heavy for the small VPS database, so games are pulled from the most active players first, and random players serve only as a fallback once active ones are exhausted on the ladder. During week one, the author reports that each checked player yielded 1.7 new matches, whereas random sampling produced 0.66 — with new matches returned on about 62 of every 100 match API calls rather than about 38, and roughly 7,100 new matches hourly rather than 2,900, a number he credits in part to a couple of other fixes applied that same day. Nine days of the current patch, he adds, accumulated 1.23 million matches, compared with 0.98 million across two weeks for the patch before it.
The author's stated lessons are process-oriented rather than product-oriented: define what you actually need before choosing an algorithm, identify the bottleneck and measure every option against it, use cheap calls to decide where to spend expensive ones, and test new ideas alongside the working system in separate tables so a bad idea cannot break production. He frames crawling as a solution looking for a problem, since the ladder already sorted players by rank.
For developers building data pipelines against rate-limited third-party APIs, the transferable pattern is the change-detection trick: a cheap, high-volume endpoint polled twice with a delay can reveal which entities produced new activity, letting the expensive endpoint be spent only on those. The author's numbers suggest the approach roughly doubled throughput per expensive call in his case, though the gain depends on how many of the polled entities are actually active in the interval.
Several things remain unknown or unverified. The performance figures come from a single hobby project on unspecified hardware and a small VPS, with no published methodology, code or raw data, and the author notes that part of the improvement came from unrelated fixes made the same day, which blurs attribution. The 1,000-match threshold and ±3% margin are stated without derivation. Nothing in the post indicates whether Riot's rate limits, endpoints or patch cadence have changed since publication, and the author's invitation for readers to share alternative solutions means no independent replication is reported.
The practical takeaway for freelancers and developers working with metered APIs is to treat discovery and retrieval as separate budgets: cheap calls should narrow the candidate set, and expensive calls should only confirm it. The caveat is that this only pays off when the cheap signal is genuinely predictive, which the author demonstrated with a short side experiment before committing — a step worth copying regardless of the API involved.