npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

twisted

v1.82.0

Published

Fetching riot games api data

Readme

🎮 Twisted

A fully‑typed Riot Games API wrapper for Node.js

League of Legends · Teamfight Tactics · Riot Account · Data Dragon

npm version npm downloads node types license


✨ Highlights

  • 🧩 Complete coverage — League of Legends, Teamfight Tactics, Riot Account and Data Dragon in one package.
  • 🪶 Lightweight — built on the native fetch API. No axios, no lodash, no dotenv.
  • 🔤 First‑class TypeScript — every endpoint, parameter and response is typed. Great autocompletion out of the box.
  • 🔁 Automatic rate‑limit retries429/503 responses are retried honoring Riot's Retry-After header.
  • 🚦 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.


📚 Table of contents


📦 Installation

npm install twisted
# or
yarn add twisted
# or
pnpm add twisted

Requirements: 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 value

Providing 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 options

Since dotenv is no longer bundled, load a .env file 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 pass new 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.