League of Legends · Teamfight Tactics · Riot Account · Data Dragon
- 🧩 Complete coverage — League of Legends, Teamfight Tactics, Riot Account and Data Dragon in one package.
- 🪶 Lightweight — built on the native
fetchAPI. Noaxios, nolodash, nodotenv. - 🔤 First‑class TypeScript — every endpoint, parameter and response is typed. Great autocompletion out of the box.
- 🔁 Automatic rate‑limit retries —
429/503responses are retried honoring Riot'sRetry-Afterheader. - 🚦 Concurrency control — cap how many requests hit Riot in parallel.
- 🧪 Battle‑tested — used in production by real projects.
Important
v1.80 drops the axios, lodash and dotenv dependencies in favor of the platform.
The minimum supported Node.js version is now 18 (the first LTS shipping a global fetch).
See Migrating to v1.80.
- Installation
- Quick start
- Core concepts
- Configuration
- Rate limiting & retries
- Error handling
- Data Dragon
- Examples
- Endpoint coverage
- Migrating to v1.80
- Contributing
npm install twisted
# or
yarn add twisted
# or
pnpm add twistedRequirements: Node.js ≥ 18. Get your API key at the Riot Developer Portal.
The three entry points are RiotApi (account), LolApi (League of Legends) and TftApi (Teamfight Tactics).
Every call returns { response, rateLimits } — your data lives in response.
Riot Account — resolve a Riot ID into a PUUID
import { RiotApi, Constants } from 'twisted'
const api = new RiotApi({ key: 'RGAPI-xxxxxxxx' })
async function getAccount () {
// Use the routing value closest to your server: AMERICAS, ASIA or EUROPE
const { response } = await api.Account.getByRiotId(
'Hide on bush', // gameName
'KR1', // tagLine (the part after the #)
Constants.RegionGroups.ASIA
)
return response // -> { puuid, gameName, tagLine }
}League of Legends — summoner, ranked & matches
import { LolApi, Constants } from 'twisted'
const api = new LolApi({ key: 'RGAPI-xxxxxxxx' })
async function getRanked (puuid: string) {
const summoner = (await api.Summoner.getByPUUID(puuid, Constants.Regions.KOREA)).response
const ranked = (await api.League.byPUUID(puuid, Constants.Regions.KOREA)).response
const matchIds = (await api.MatchV5.list(puuid, Constants.RegionGroups.ASIA, { count: 5 })).response
const lastGame = (await api.MatchV5.get(matchIds[0], Constants.RegionGroups.ASIA)).response
return { summoner, ranked, lastGame }
}Teamfight Tactics — TFT summoner & matches
import { TftApi, Constants } from 'twisted'
const api = new TftApi({ key: 'RGAPI-xxxxxxxx' })
async function tftHistory (puuid: string) {
const summoner = (await api.Summoner.getByPUUID(puuid, Constants.Regions.AMERICA_NORTH)).response
const matchIds = (await api.Match.list(puuid, Constants.RegionGroups.AMERICAS, { count: 5 })).response
return { summoner, matchIds }
}Every API method (except Data Dragon) resolves to an ApiResponseDTO<T>:
{
response: T // the parsed payload
rateLimits: { // parsed from Riot's response headers
AppRateLimit, AppRateLimitCount,
MethodRateLimit, MethodRatelimitCount,
RetryAfter, Type, EdgeTraceId
}
}Riot exposes three different routing concepts. Twisted enforces the right one at the type level, so the compiler tells you when you pass the wrong kind.
| Concept | Type | Values | Used by |
|---|---|---|---|
| Platform region | Regions |
NA1, EUW1, KR, BR1, … |
Summoner, League, Champion Mastery, Spectator, Status |
| Region group | RegionGroups |
AMERICAS, ASIA, EUROPE, SEA |
Match‑V5, TFT Match |
| Account routing | AccountAPIRegionGroups |
AMERICAS, ASIA, EUROPE |
Account‑V1 |
import { Constants } from 'twisted'
Constants.Regions.EU_WEST // 'EUW1' — platform region
Constants.RegionGroups.EUROPE // 'EUROPE' — routing valueThe key is read from process.env.RIOT_API_KEY, or you can pass it explicitly:
new LolApi('RGAPI-xxxxxxxx') // shorthand
new LolApi({ key: 'RGAPI-xxxxxxxx' }) // with optionsSince
dotenvis no longer bundled, load a.envfile with Node's built‑in flag (Node ≥ 20.6):node --env-file=.env app.js, or set the variable in your shell.
import { LolApi } from 'twisted'
const api = new LolApi({
key: 'RGAPI-xxxxxxxx',
rateLimitRetry: true,
rateLimitRetryAttempts: 1,
concurrency: undefined,
debug: {
logTime: false,
logUrls: false,
logRatelimits: false
}
})| Option | Type | Default | Description |
|---|---|---|---|
key |
string |
process.env.RIOT_API_KEY |
Your Riot Games API key. |
rateLimitRetry |
boolean |
true |
Retry the request when Riot answers 429 / 503. |
rateLimitRetryAttempts |
number |
1 |
How many times to retry after a rate‑limit response. |
concurrency |
number |
Infinity |
Max concurrent requests per service (Summoner, Match, …). |
baseURL |
string |
https://$(region).api.riotgames.com/:game |
Point requests at a rate‑limiting proxy. $(region) and :game are substituted. |
debug.logTime |
boolean |
false |
Log each method's execution time. |
debug.logUrls |
boolean |
false |
Log the URL of every request. |
debug.logRatelimits |
boolean |
false |
Log whenever the client is waiting on a rate limit. |
When Riot returns 429 Too Many Requests or 503 Service Unavailable, Twisted automatically waits
(honoring the Retry-After header) and re‑issues the request up to rateLimitRetryAttempts times — query
parameters included. Disable it with rateLimitRetry: false if you manage limits yourself.
// Never fire more than 10 concurrent requests per service
const api = new LolApi({ key, concurrency: 10 })Failed requests throw typed errors you can branch on:
import { LolApi, Constants, GenericError, RateLimitError, ServiceUnavailable, ApiKeyNotFound } from 'twisted'
try {
await api.Summoner.getByPUUID(puuid, Constants.Regions.KOREA)
} catch (e) {
if (e instanceof RateLimitError) { /* 429 — retries exhausted */ }
if (e instanceof ServiceUnavailable) { /* 503 */ }
if (e instanceof ApiKeyNotFound) { /* missing key */ }
if (e instanceof GenericError) { console.log(e.status, e.body) }
}| Error | When |
|---|---|
ApiKeyNotFound |
No API key was provided. |
RateLimitError |
429 and retries are exhausted/disabled. |
ServiceUnavailable |
503 from the Riot API. |
GenericError |
Any other non‑2xx response (status and body attached). |
Static game assets (champions, items, runes, versions…). Data Dragon hits the public CDN directly — no API key,
no rate limiting — so these methods return the raw payload instead of an ApiResponseDTO.
const api = new LolApi()
const versions = await api.DataDragon.getVersions() // ['15.x.1', …]
const champs = await api.DataDragon.getChampionList() // all champions
const aatrox = await api.DataDragon.getChampion(Constants.Champions.AATROX)
const runes = await api.DataDragon.getRunesReforged()A runnable example exists for every endpoint under /example.
# Run them all
RIOT_API_KEY=RGAPI-xxxx yarn example
# Run a subset by (case-insensitive) name match
RIOT_API_KEY=RGAPI-xxxx yarn example summonerListed in the same order as the official Riot documentation.
Riot Account
- Get account by puuid
- Get account by riot id
- Get active region (lol and tft)
- Get account by puuid — ESPORTS
- Get account by riot id — ESPORTS
- Get active shard for a player
- Get account by access token
League of Legends
- All champion mastery entries
- Champion mastery by player & champion id
- Total champion mastery score
- Champion rotation
- Players by summoner id · Team · Tournaments · Tournament by team id · Tournament by id
- Match by id · Matches by puuid · Match timeline · Available replays by puuid
- Matches by tournament code · Match by id · Match by tournament code · Matches by summoner id · Match timeline
- Challenger / Grandmaster / Master leagues by queue
- League entries by PUUID · by summoner id · all entries
- League by id · Experimental league entries
- Config · Percentiles · Challenge config · Leaderboards · Challenge percentiles · Player challenges
- Platform status (v4) · Shard status (v3, deprecated)
- Current game by summoner id · Featured games (v4 deprecated)
- By account id · By PUUID · By summoner id
- Not yet implemented
Teamfight Tactics
- By account id · By PUUID · By summoner id
- Match list by PUUID · Match details
- Challenger / Grandmaster / Master leagues
- Entries by summoner id · By tier & division
- All entries · League by id
- Current game by puuid · Featured games
This release removes three runtime dependencies in favor of native platform features:
| Removed | Replaced by |
|---|---|
axios |
Native fetch (Node ≥ 18) |
lodash |
Native JS (Object.entries, spreads, …) |
dotenv |
node --env-file=.env or your own loader |
What you need to do
- Run on Node 18 or newer.
- If you relied on Twisted auto‑loading a
.env, load it yourself — e.g.node --env-file=.env, or passnew LolApi({ key }).
The public API is otherwise unchanged — your existing calls keep working.
yarn install # install dependencies
yarn build # compile TypeScript -> dist/
yarn lint # eslint
yarn jest # run the test suite (coverage is always collected)PRs are welcome! For new endpoints: declare it in src/endpoints, add the service method, model the response DTO,
add an example, and wire it into the relevant entry class.
A larger real‑world project built on Twisted lives at twisted‑gg.
Released under the MIT License.