twisted
v1.82.0
Published
Fetching riot games api data
Maintainers
Readme
🎮 Twisted
A fully‑typed Riot Games API wrapper for Node.js
League of Legends · Teamfight Tactics · Riot Account · Data Dragon
✨ Highlights
- 🧩 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,lodashanddotenvdependencies in favor of the platform. The minimum supported Node.js version is now 18 (the first LTS shipping a globalfetch). See Migrating to v1.80.
📚 Table of contents
- Installation
- Quick start
- Core concepts
- Configuration
- Rate limiting & retries
- Error handling
- Data Dragon
- Examples
- Endpoint coverage
- Migrating to v1.80
- Contributing
📦 Installation
npm install twisted
# or
yarn add twisted
# or
pnpm add twistedRequirements: Node.js ≥ 18. Get your API key at the Riot Developer Portal.
🚀 Quick start
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.
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 }
}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 }
}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 }
}🧠 Core concepts
Response shape
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
}
}Regions vs. region groups
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 valueProviding your API key
The 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.
⚙️ Configuration
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. |
🔁 Rate limiting & retries
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.
Concurrency
// Never fire more than 10 concurrent requests per service
const api = new LolApi({ key, concurrency: 10 })🧯 Error handling
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). |
🐉 Data Dragon
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()💡 Examples
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 summoner📋 Endpoint coverage
Listed in the same order as the official Riot documentation.
ACCOUNT-V1
- [x] Get account by puuid
- [x] Get account by riot id
- [x] 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
CHAMPION-MASTERY-V4
- [x] All champion mastery entries
- [x] Champion mastery by player & champion id
- [x] Total champion mastery score
CHAMPION-V3
- [x] Champion rotation
CLASH
- [x] Players by summoner id · Team · Tournaments · Tournament by team id · Tournament by id
MATCH-V5
- [x] Match by id · Matches by puuid · Match timeline · Available replays by puuid
MATCH-V4 (deprecated)
- [x] Matches by tournament code · Match by id · Match by tournament code · Matches by summoner id · Match timeline
LEAGUE-V4
- [x] Challenger / Grandmaster / Master leagues by queue
- [x] League entries by PUUID · by summoner id · all entries
- [x] League by id · Experimental league entries
LOL-CHALLENGES-V1
- [x] Config · Percentiles · Challenge config · Leaderboards · Challenge percentiles · Player challenges
LOL-STATUS-V4
- [x] Platform status (v4) · Shard status (v3, deprecated)
SPECTATOR-V5
- [x] Current game by summoner id · Featured games (v4 deprecated)
SUMMONER-V4
- [x] By account id · By PUUID · By summoner id
TOURNAMENT(-STUB)-V4
- [ ] Not yet implemented
TFT-SUMMONER-V1
- [x] By account id · By PUUID · By summoner id
TFT-MATCH-V1
- [x] Match list by PUUID · Match details
TFT-LEAGUE-V1
- [x] Challenger / Grandmaster / Master leagues
- [x] Entries by summoner id · By tier & division
- [ ] All entries · League by id
TFT-SPECTATOR-V5
- [x] Current game by puuid · Featured games
🔀 Migrating to v1.80
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.
🤝 Contributing
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.
