Documentation

Setting up LoL Champ Tracker with the official Riot API

Everything from first download to a working sync: installing on both platforms, creating a Riot developer key, what each endpoint returns, how rate limits are respected, and what to do when something returns a 403.

1. How it all fits together #

LoL Champ Tracker is an Electron desktop app. It has exactly one data source — Riot's official developer API — and one place it writes to, a JSON file on your own disk. There is no server in the middle, no account to create, and nothing scraped from a website.

the whole data path
  You                Electron main process              Riot
  ───                ─────────────────────              ────
  Riot ID     ──▶    account-v1  ─────────────────▶  PUUID
  + API key          match-v5 /ids  ──────────────▶  last 100 match IDs
                     match-v5 /{id} ──────────────▶  full match detail  ✕N
                            │
                            ├─ compute mark (locally, no network)
                            ├─ detect duos  (locally, no network)
                            ▼
                     stats-db.json  ◀── your disk, nothing else touches it
                            │
                            ▼
                     Renderer window  (no network access at all)

Two details matter for everything below. First, every network request happens in the main process — the window itself runs under a content-security policy of default-src 'self', so even if something went wrong in the UI it could not phone home. Second, the app never sees a key that isn't yours; it authenticates as you, against your own rate limit.

Prerequisites A Riot account you actually play League on, and about five minutes. The API key is free and needs no application or approval for personal use.

2. Installing the app #

Neither build is code-signed — this is a personal project with no certificate behind it — so both operating systems will show a warning the first time. Steps for clearing that are below, and covered in more detail under Gatekeeper & SmartScreen.

Windows 10 & 11 (64-bit)

  1. Download LoL-Champ-Tracker.exe from the download section.
  2. Put it wherever you like — Desktop is fine. It's a portable single file: there is no installer, no Start-menu entry and nothing written to the registry.
  3. Double-click it. SmartScreen will say "Windows protected your PC" — click More info, then Run anyway.
  4. The app unpacks to a temporary folder and opens. First launch takes a few seconds; later ones are quicker.
Updating Replace the .exe with a newer one. Close the app first — a running portable build locks its own file, and the copy will fail with "file in use". Your match database lives elsewhere and is untouched by this.

First run

The window opens with the full champion grid greyed out and "Not synced yet" in the header. Nothing will populate until you add a Riot ID and an API key — that's the next section.

3. Getting a Riot API key #

Riot doesn't allow public apps to ship a shared key, and this app has no server to hide one behind — so it uses your key, stored only on your machine.

The key you actually want is a Personal API key, and you get it by registering a product on the developer portal. There's also a development key on the dashboard that works immediately but dies every 24 hours — fine for a first test, miserable to live with.

3.1 Which key you need #

Key typeExpiresHow you get itRate limit
Personal ← this one No Register Product on the portal, then wait for review. No domain verification needed. 20 req / sec, 100 req / 2 min
Development Every 24 hours Already on your dashboard the moment you log in. No application. Same as personal
Production No Full application — needs a working prototype and domain verification via a riot.txt file. Review takes roughly two weeks. Far higher; irrelevant here

Personal and development keys carry the same rate limit, so a personal key buys you exactly one thing: it stops expiring. That's the whole reason to bother. Production is for public apps with real users — this app has no users but you, so don't apply for it.

3.2 Register a product for a Personal key #

  1. Go to developer.riotgames.com and Login with the Riot account you play on. There's no separate developer signup.
  2. Accept the developer terms if prompted.
  3. On the main portal page click Register Product.
  4. Choose the Personal product type (not Production).
  5. Fill in the form — field by field below — and submit.
  6. Wait. Riot's Developer Relations team reviews it manually.

What to put in each field

FieldWhat to enter
Product type Personal. Picking Production drags you into prototype demos and domain verification you don't need, and it will be rejected for a tool only you use.
Product name Something plain and literal — LoL Champ Tracker (personal). This isn't a marketing pitch; a reviewer should understand what it is from the name alone.
Game League of Legends only. Don't also tick Teamfight Tactics, Legends of Runeterra or VALORANT — requesting access you demonstrably don't use invites questions and slows review.
Product description The field that decides your application. Be specific and honest: who uses it, what it does, which endpoints it calls, where it runs, and where the data ends up. A vague one-liner is the most common reason a personal application stalls. Copy-paste-ready example below.
Product URL / website Your GitHub repo is ideal — it lets a reviewer read the actual code. If you have nothing to link, say so plainly ("Local desktop app, not published anywhere") rather than inventing a URL or linking something unrelated.
Expected traffic / rate limit The default personal limits are plenty. If asked for numbers: a few hundred requests a day at most, in short bursts when syncing.
A description you can adapt Swap in your own details — the point is that every claim here is true of this app, and a reviewer can verify each one from the source:
product description
A personal desktop application (Electron) that I use to review my own
League of Legends match history.

It calls account-v1 to resolve my Riot ID to a PUUID, then match-v5 to
fetch my recent matches. Matches are stored in a JSON file on my own
computer. It computes a per-game performance score by comparing my
stats against the other participants in the same match, and shows
per-champion win rates.

It is used only by me. There is no website, no public deployment, no
user accounts, and no server — the app runs entirely on my machine and
does not redistribute or display Riot data to anyone else.

Source: https://github.com/furkannurovic/lolChampTracker
Expect to wait Personal applications are reviewed by hand. Some come back in a few days; people regularly report waiting a couple of weeks or more. Use the development key in the meantime — it works identically, it just needs re-pasting daily. Don't submit a second application because the first is slow; duplicates go to the back of the queue.

Riot revises this form from time to time, so a label may not match word-for-word. The substance is stable: what it is, who uses it, which game, where it lives.

3.3 The development key (works right now) #

While you wait for the personal key — or if you just want to try the app once — the dashboard at developer.riotgames.com always has a Development API key on it, right under your account details. It starts with RGAPI-. Click Regenerate API Key if the field is empty or expired, then copy it.

It behaves exactly like a personal key in this app. It just dies after 24 hours, and the instant you regenerate it.

Treat any key like a password A key authenticates as your Riot account and spends your rate limit. Don't paste it into forums, issues or screenshots. If you ever leak one, regenerate on the portal — that instantly kills the old value.

3.4 Put the key in the app #

  1. In the app, click Change next to the player name.
  2. Fill in your Riot name, your #tag (without the # — the field adds it) and pick your region.
  3. Paste the key into the RGAPI-… field.
  4. Press Test. A working key answers with "Key works — yourname #tag found." Anything else is listed under API errors.
  5. Press Save, then Sync.

Your Riot ID is the name#tag pair shown in the League client, not your old summoner name. If you're unsure of the tag, it's in the client under your profile — it's often your region code (#EUNE, #NA1) but it can be anything.

3.5 When a key stops working #

A development key dies 24 hours after it's issued, and the moment you regenerate it. When that happens the app stops syncing and shows a 403. The fix is always the same:

  1. Open the portal, click Regenerate API Key.
  2. Copy the brand-new RGAPI-… value.
  3. Change → paste over the old key → TestSave.

If a personal key starts returning 403, expiry isn't the cause — it doesn't expire. Check that it wasn't regenerated on the portal, and that your product wasn't deactivated for inactivity.

Nothing you've already synced is lost — the local database is independent of the key. Expiry only blocks fetching new games.

4. Regions & regional routing #

Riot's newer endpoints (account-v1 and match-v5) aren't addressed by your game server. They use four regional routing values, and your platform maps onto one of them. Pick the wrong one and you get a 404 even though the Riot ID is spelled perfectly — this is the single most common setup mistake.

The app does this mapping for you from the region you pick in the dropdown:

Routing valuePlatforms it coversHost used
europe EUNE, EUW, TR, RU europe.api.riotgames.com
americas NA, BR, LAN, LAS americas.api.riotgames.com
asia KR, JP asia.api.riotgames.com
sea OCE, VN, PH, SG, TH, TW sea.api.riotgames.com

The region dropdown in the app offers EUNE, EUW, NA, KR, BR, TR, OCE, LAN, LAS, RU, JP and VN. Anything it doesn't recognise falls back to europe, so if you play somewhere exotic and get a 404, that fallback is why.

Regional ≠ platform routing Older endpoints use platform hosts like eun1.api.riotgames.com. The two look interchangeable and are not. This app only ever calls regional hosts, so if you're testing by hand, use the ones in the table above.

5. The endpoints the app calls #

Three authenticated Riot endpoints, plus one public CDN. Every one of them is a GET, and every authenticated one carries your key in a header — never in the query string.

the only auth header
X-Riot-Token: RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

5.1 account-v1 — Riot ID → PUUID #

Everything starts here. Riot's match endpoints are keyed by PUUID, a stable per-account identifier, so the name and tag you typed have to be resolved first.

GET
https://{regional}.api.riotgames.com/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}

Both path parts are URL-encoded by the app, so spaces and non-Latin characters in a name are fine. A successful response is small:

200 OK
{
  "puuid": "kY3f…a very long opaque string…9Qz",
  "gameName": "yourname",
  "tagLine": "tag"
}

The app caches that PUUID against your Riot ID, so later syncs skip this call entirely unless you change who you're tracking.

5.2 match-v5 — the list of match IDs #

GET
https://{regional}.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids?start=0&count=100

Returns a plain array of match-ID strings, newest first, e.g. ["EUN1_3612345678", "EUN1_3612300000", …]. count is capped at 100 by Riot, and the app always asks for the maximum.

Worth knowing: how far back your history goes The app requests start=0 only — so a single sync sees your latest 100 matches, never older ones. Because everything already fetched stays in the local database, your history still grows without limit going forward; you just can't back-fill games older than the 100 most recent at the moment you first sync. Riot's match-v5 archive is itself finite, so very old games aren't retrievable regardless.

5.3 match-v5 — the match detail #

GET
https://{regional}.api.riotgames.com/lol/match/v5/matches/{matchId}

This is the expensive one — one request per game, and where the sync budget goes. The response is large; the app keeps only what it needs from info.participants:

Field takenUsed for
championIdMatching the game to a champion (it's the Data Dragon key).
winWin/loss records and A-to-Z ticks.
kills / deaths / assistsKDA, and the biggest single input to the mark.
totalMinionsKilled + neutralMinionsKilledCS.
totalDamageDealtToChampions, goldEarned, visionScore, damageDealtToObjectivesThe other mark metrics — read for every participant, not just you.
teamId / playerSubteamIdGrouping teammates. Arena uses subteams of two.
riotIdGameName / riotIdTaglineNaming your duo partners.
profileIconFetching your summoner picture.
info.queueIdThe queue label — Solo, Flex, ARAM, Arena, URF, Clash…

5.4 Data Dragon — champion and summoner icons #

Riot's asset CDN. No API key, no rate limit — it's a plain static host, and it's the only thing the app talks to besides the API itself.

public CDN — no auth
# current patch list — the first entry is the newest
https://ddragon.leagueoflegends.com/api/versions.json

# your summoner icon
https://ddragon.leagueoflegends.com/cdn/{version}/img/profileicon/{iconId}.png

# fallback that is always current, no version needed
https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/profile-icons/{iconId}.png

Champion portraits aren't fetched at runtime at all — all 173 are bundled inside the app, so the grid works with no connection. Only your summoner icon is downloaded, and it's converted to a data: URL before it reaches the window, which is what keeps the renderer's strict CSP intact.

6. Testing your key by hand #

If the app reports something you don't believe, go around it. These call the same endpoint the Test button does — substitute your routing value, name and tag.

PowerShell
$key = "RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$name = "yourname"
$tag  = "tag"

Invoke-RestMethod -Uri "https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/$name/$tag" `
                  -Headers @{ "X-Riot-Token" = $key }

A working key prints your puuid, gameName and tagLine. A dead one throws with the status code in the message.

Once that returns a PUUID, you can walk the same path the app does:

bash — the next two calls
# 1. the last 5 match IDs
curl -H "X-Riot-Token: $KEY" \
  "https://europe.api.riotgames.com/lol/match/v5/matches/by-puuid/$PUUID/ids?start=0&count=5"

# 2. one full match
curl -H "X-Riot-Token: $KEY" \
  "https://europe.api.riotgames.com/lol/match/v5/matches/EUN1_3612345678"
The portal has a console too Every endpoint on developer.riotgames.com/apis has a built-in "Execute Request" form that uses your key automatically. It's the quickest way to confirm a key is alive without touching a terminal.

7. Rate limits #

Development and personal keys carry the same small budget. At the time of writing Riot publishes 20 requests per second and 100 requests every 2 minutes for both — the second number is the one that actually constrains a match sync, since each game is a separate request. Getting a personal key stops the daily expiry; it does not give you more headroom. Check the dashboard for the limits attached to your own key; Riot can and does change them.

The app is built around that ceiling rather than fighting it:

GuardValueWhy
Requests per sync90 match fetchesLeaves headroom under the 100-per-2-minutes window.
Spacing between requests130 msRoughly 7–8 requests a second — comfortably inside the per-second limit.
On a 429Stop immediatelyThe sync ends early and keeps what it already fetched. Nothing is lost; run it again in a minute.
Already-stored matchesNever re-fetchedOnly genuinely new match IDs cost a request.

In practice: a first sync on a busy account will hit the 90-game cap and stop. Wait two minutes, hit Sync again, and it continues where it left off. After that, routine syncs are a handful of requests.

Rate limits are per key, not per app If you're using the same key in another tool at the same time, you share one budget and will both see 429s sooner.

8. What a sync actually does #

  1. Resolve. Riot ID → PUUID via account-v1, unless a cached PUUID for that exact Riot ID already exists.
  2. List. Fetch the latest 100 match IDs and subtract everything already in the database. What's left is new.
  3. Fetch. Pull each new match in turn, up to the 90-request cap, pausing 130 ms between them.
  4. Back-fill. If budget remains, re-fetch old matches that predate a feature — games stored before v1.2.0 have no mark or teammate data — so history heals itself over a few syncs.
  5. Derive. Compute each game's mark, group teammates, tally anyone who appears on your side twice or more into duo partners. All local, no requests.
  6. Icon. If your summoner icon changed, download it from Data Dragon and store it as a data: URL.
  7. Save. Write stats-db.json and refresh the UI.

The status line under your name reports the outcome — "+6 new, 12 back-filled via Riot API" — or the exact error if something failed. A sync already in progress can't be started twice.

9. How the mark is computed #

No external "average player" data is involved. Each game is scored purely against the other players who were in it.

  1. For every participant, seven metrics are computed: KDA, kill participation, damage to champions, gold, CS, vision score and objective damage.
  2. For each metric you get a percentile rank among all participants — 0 = worst in the lobby, 1 = best. Ties share the average rank.
  3. Those ranks are blended with fixed weights: KDA 24%, kill participation 20%, champion damage 18%, gold 12%, CS 10%, vision 10%, objective damage 6%.
  4. A small nudge is applied: +0.05 for a win, −0.03 for a loss, plus +0.06 for a pentakill or +0.03 for a quadra.
  5. The result is clamped to 0–1, multiplied by 10, and mapped to a letter: S+ ≥ 9.0, S ≥ 8.0, A ≥ 7.0, B ≥ 5.8, C ≥ 4.2, otherwise D.
The consequence worth understanding Because it's lobby-relative, a mark is not comparable across games in an absolute sense — a 7.5 in a smurf lobby is a much better game than a 7.5 in a bot-tier one. What it's good at is the thing win rate can't tell you: on this champion, in these games, were you carrying or being carried?

Arena is handled specially — teammates come from playerSubteamId (squads of two) rather than teamId, so duo detection and kill participation behave sensibly.

10. Where your data lives #

Everything — matches, settings, your API key, your cached PUUID — sits in one JSON file:

PlatformPath
Windows%APPDATA%\lol-champ-tracker\stats-db.json
macOS~/Library/Application Support/lol-champ-tracker/stats-db.json

It's plain, readable JSON — open it, back it up, sync it between machines, or delete it to start over. The app rewrites it after every sync.

What's in it

  • matches — one entry per game: champion, result, KDA, CS, duration, queue, position, mark, and the teammates you were grouped with.
  • player — your Riot name, tag, region, and your summoner icon as an embedded data: URL.
  • riot — your API key in plain text, plus the cached PUUID.
Your key is stored unencrypted It's a local file with normal user permissions, not a secret store. That's fine for a personal tool on your own machine — but don't commit this file to a repo, put it in a shared folder, or hand it to anyone. If you ever do, regenerate the key on the portal; that instantly kills the old one.

Deleting the file resets the app completely: no matches, no key, no Riot ID. Nothing is recoverable from anywhere else, because nothing was ever uploaded anywhere.

11. Troubleshooting #

11.1 API errors #

These are the messages the app surfaces, and what each one really means.

StatusWhat the app saysWhat to do
403 "Riot rejected the key (403). Development keys EXPIRE every 24h…" By far the most common one, and on a development key it's almost always expiry — it aged out, or you regenerated it and pasted the old value. Get a fresh key from the portal and re-paste it. On a personal key, which never expires, look instead at whether it was regenerated or the product deactivated (§3.5).
401 "Riot got no valid key (401)." The key field is empty or mangled. Make sure you copied the whole thing including the RGAPI- prefix — a partial paste is the usual cause.
404 "Riot ID not found for this region." Name, tag or region is wrong. Check the spelling of both, and confirm the region maps to the routing value you expect (see §4). Wrong region is more often the culprit than a typo.
429 "Riot API rate limit hit — wait a minute and sync again." Expected on big first syncs. Everything fetched so far is saved. Wait ~2 minutes and sync again.
400 / 415 "Riot rejected the request." Usually a stray character in the name or tag field — a leading space, or a # typed into the tag box.
5xx "Riot API error (HTTP 5xx)." Riot's side. Nothing to fix locally — try later.
"Riot API request timed out" Network or firewall. The app allows 15 seconds per request. Check that nothing is blocking *.api.riotgames.com.
"That doesn't look like a Riot key" Shown before any request is made, when the value doesn't start with RGAPI-. You've probably pasted a PUUID, a summoner name, or an incomplete clipboard.

11.2 Gatekeeper & SmartScreen #

Both builds are unsigned. This is a warning about provenance, not about the contents — but you should only clear it for a binary you got from a source you trust, or better, build it yourself.

Windows — "Windows protected your PC"

Click More info, then Run anyway. If the button isn't there, right-click the .exeProperties → tick Unblock at the bottom → OK.

macOS — "can't be opened because Apple cannot check it for malicious software"

Right-click (or Control-click) the app → OpenOpen. This only needs doing once.

macOS — "is damaged and can't be opened. You should move it to the Trash."

Nothing is damaged. macOS attaches a quarantine flag to anything downloaded from the internet, and for unsigned apps that message is what you get. Strip the flag:

Terminal
xattr -dr com.apple.quarantine "/Applications/LoL Champ Tracker.app"

Then open it normally. An app you built yourself on your own Mac never picks up the flag in the first place.

11.3 App behaviour #

SymptomCause & fix
Sync says "no new games" Working as intended — everything in your last 100 matches is already stored. Play a game and sync again.
Older games have no mark They were stored before v1.2.0. Each sync back-fills a batch of them; run a few syncs and they'll fill in.
A duo partner is missing Duos need two or more games together in your stored history. One shared game looks identical to a random teammate.
A random player showed up as a duo Possible but rare — it means you were genuinely matched with the same account twice. Common on small servers.
No summoner icon The icon downloads on the sync after the app first sees your profile icon ID. Sync once more.
Champion missing from the grid The bundled champion list is from a fixed patch. A champion released after that patch needs a rebuild — see §12.
Can't overwrite the .exe The running app locks its own file. Close every instance first (Task Manager → end LoL-Champ-Tracker if needed), then copy.

12. Building from source #

This is how you get a macOS build, and the honest answer to "why should I run your unsigned binary?" You need Node.js LTS and nothing else — no Xcode, no Visual Studio, no native compilation.

both platforms
# from the project folder
npm install
PowerShell
# run it live, without packaging
npm start

# build the portable exe -> dist/LoL-Champ-Tracker.exe
npm run dist:win

The portable target skips code-signing automatically — you'll see "no signing info identified" in the log, which is expected and not an error.

Refreshing the champion list

The bundled champion data is pinned to a patch. When Riot releases a new champion, regenerate the list and its icons — this hits Data Dragon only, so no API key is involved:

both platforms
node fetch-data.js   # rewrites champions.json + champions-data.js, downloads new icons
node make-icon.js    # regenerates the app icons (only if you changed them)

Project layout

FileRole
main.jsElectron main process — window, IPC handlers, all networking.
riot.jsEvery Riot API call, the mark calculation, Data Dragon fetches.
db.jsThe JSON database — storage, roll-ups, duo detection.
preload.jsThe window.lol bridge. The renderer's only route to anything.
renderer.js / index.html / styles.cssThe UI. No network access of its own.
champions-data.jsAll champions, embedded as a global — a file:// page can't fetch() a local JSON file.

13. Riot's rules #

Using the API means agreeing to Riot's developer terms. The parts that matter for a tool like this one:

  • Your key is yours. Don't share it or ship it inside anything you distribute. This app deliberately has no bundled key for that reason.
  • Respect the rate limits. Don't work around a 429 by hammering it — back off and retry, which is what the app does.
  • Don't sell it or gate it. Riot's terms rule out charging for access to their data.
  • Nothing that affects gameplay. Reading your own match history after the fact is fine; anything that acts during a live game is not.
  • Display the disclaimer if you publish something built on the API — the one in this site's footer is the standard wording.

The current terms are at developer.riotgames.com/policies/general, and the full endpoint reference at developer.riotgames.com/apis. Riot changes both from time to time; they're the authority, not this page.


Still stuck? The app prints the exact message Riot sent back — Riot's own wording is usually in the status line, after the app's explanation. That text is the fastest thing to search for.