@flexhd/primeleague-api (0.7.0)
Installation
@flexhd:registry=npm install @flexhd/primeleague-api@0.7.0"@flexhd/primeleague-api": "0.7.0"About this package
Prime League community API
An experimental, read-only JSON wrapper for public data on primeleague.gg. It discovers seasons, divisions/groups, teams, standings, matches, brackets, rosters, player histories, circuit points, Division 1 coverage, and public match lineups.
This is not an official Prime League API. Prime League currently exposes server-rendered pages plus undocumented /ajax/* routes. Those routes can change without notice, and several JSON responses contain HTML fragments. The site also uses a Bunny Shield browser challenge, so this project uses Playwright's bundled headless Chromium instead of plain fetch/curl. Google Chrome is not required.
Quick start
Requirement: Node.js 22.5 or newer. npm install downloads a compatible Chromium build automatically.
npm install
cp .env.example .env
npm start
The default base URL is http://127.0.0.1:3000. Runtime data is stored in data/primeleague.sqlite and the persistent browser profile stays in data/browser-profile.
Linux hosts may need Chromium's system libraries once:
npx playwright install-deps chromium
The Docker image below already contains these libraries. To override bundled Chromium, set PRIMELEAGUE_BROWSER_EXECUTABLE_PATH.
Install as a module
Install the project folder, a packed .tgz file, or the Forgejo package:
npm config set @flexhd:registry https://forgejo.createaloft.net/api/packages/Flexhd/npm/
npm install @flexhd/primeleague-api
npx primeleague-api
Embed it in an existing Node application:
import { createPrimeLeagueApi } from "@flexhd/primeleague-api";
const api = await createPrimeLeagueApi({
databasePath: "./data/primeleague.sqlite",
profileDir: "./data/browser-profile",
});
await api.listen({ host: "0.0.0.0", port: 3000 });
Call await api.close() during application shutdown. This closes Chromium and SQLite cleanly. The package also exports the parsers, loadConfig, AppConfig, and SqliteStore.
Docker
The included multi-stage image bundles Node, Chromium, and its Linux dependencies:
docker build -t primeleague-api .
docker run --rm -p 3000:3000 -v primeleague-data:/app/data primeleague-api
Persist /app/data so the SQLite database, response history, cache, and browser profile survive container replacement.
Forgejo package publishing
The .forgejo/workflows/publish.yml action publishes tags to the Flexhd npm registry. In the repository's Actions secrets, create PACKAGE_TOKEN containing an owner personal access token with package-write permission. Then publish a matching version tag:
git tag v0.7.0
git push origin v0.7.0
The action refuses to publish when the tag and package.json version differ. For local publishing, copy .npmrc.example to .npmrc, provide FORGEJO_TOKEN, and run npm publish; .npmrc is ignored by Git.
Endpoints
Seasons
GET /v1/seasons?page=1&mode=completed&league=prm
Uses the upstream GET /ajax/list_load route. It returns season IDs, slugs, names, start/end Unix timestamps, source URLs, and pagination.
Season structure: divisions/groups and brackets
GET /v1/seasons/3220/spring-split-202526/structure
The response includes stages, every group with its division number, per-division group counts, and playoff bracket links. For season 3220, the inspected data contained 139 numbered groups across Divisions 2–8, three additional calibration/relegation/starter groups, and five playoff/decider brackets.
Division discovery
GET /v1/seasons/3220/spring-split-202526/divisions
GET /v1/seasons/3220/spring-split-202526/divisions/5
The collection route returns every numbered regular-season division, its groups, group count, detail and schedule URLs, plus unnumbered groups and playoff links. The division-specific route returns only the groups belonging to one division. At inspection time, Division 5 contained 31 groups, from Group 5.1 through Group 5.31.
Season 3220 was live-tested across every community division: Division 2 has 2 groups, Division 3 has 4, Division 4 has 12, Division 5 has 31, Division 6 has 40, Division 7 has 37, and Division 8 has 13. A representative group in every division returned both standings and matches. Division 1 remains available through the separate coverage endpoint.
Division teams and team search
GET /v1/seasons/3220/spring-split-202526/divisions/7/teams?search=serpents
GET /v1/seasons/3220/spring-split-202526/divisions/7/teams?page=1&size=5
The search form is optimized for organizations with several league teams: it searches the season participants, enriches each candidate with its roster, then keeps only teams whose group belongs to the requested division. Every result includes its exact group and a ready-to-use group endpoint. With no search term, the endpoint pages through the division's groups and returns every team plus its standing and group membership.
Division schedule
GET /v1/seasons/3220/spring-split-202526/divisions/5/schedule?page=1&size=5&state=all×=upcoming
Fetches and combines the schedules for a page of groups. Each match includes its group, playday, teams, score, IDs, logos, and source URL. The group pages do not contain exact dates, so the default times=upcoming enriches unfinished matches from the match-detail endpoint with their Unix start time and status. Use times=all to enrich historical matches too, or times=none for the lightest request. state accepts all, upcoming, or finished; size is capped at 10 groups to keep upstream traffic controlled. Follow pagination.pages to ingest all 31 Division 5 groups.
Broadcast-normalized output
GET /v1/broadcast/seasons/3220/spring-split-202526/divisions/5/schedule?page=1&size=5&state=upcoming×=upcoming
GET /v1/broadcast/seasons/3220/spring-split-202526/brackets/512/playoffs/1502/playoffs-2-liga
These endpoints provide a stable schemaVersion, ISO and Unix timestamps, explicit home/away teams, score objects, match state, winner side/team ID, round grouping, source links, and pagination. The raw endpoints remain unchanged for backward compatibility. Bracket progression edges are explicitly marked unavailable instead of being guessed.
Season participants/signups
GET /v1/seasons/3220/spring-split-202526/participants?page=1&size=25&search=serpents
Returns teams, IDs, country codes, logos, signup/completion timestamps, and statuses such as Participating, Disqualified, or Not enough players. The optional search query is forwarded to Prime League's participant search.
Group/division detail
Use the IDs and slugs returned by the structure endpoint:
GET /v1/seasons/3220/spring-split-202526/groups/509/group-stage/5498/group-2-1?times=all
Returns standings and all visible group matches, including match IDs, team IDs, abbreviations, logos, rounds, and scores. Add times=upcoming to resolve dates for unfinished matches, or times=all to resolve every public match date/status.
Bracket
GET /v1/seasons/3220/spring-split-202526/brackets/512/playoffs/1502/playoffs-2-liga?times=all
Returns the bracket's complete public match list. Add times=all to resolve every public match date/status. The upstream visual bracket does not expose stable graph edges in its HTML, so the API returns the full match set without inventing winner/loser relationships.
Team and roster
GET /v1/seasons/3220/spring-split-202526/teams/211352/cgn-esports
Returns the team identity, logo, country, creation timestamp, current group/result, remaining roster additions, OP.GG multi-search link, group memberships, and season matches. Roster members include their role, eligibility/status, public Riot ID, and OP.GG link. Match entries now include their scheduled Unix timestamp when the page exposes it.
Player profile and season history
GET /v1/seasons/3220/spring-split-202526/players/380873/agurin
Returns the public player identity, current team, country, role/status when available, Riot accounts, played-match history, and the visible most-played-champion summaries (games, record, and KDA).
Circuit / split points
GET /v1/circuits/460/standings
Returns all ranked teams in the circuit, their per-season point breakdown, total points, logos, country codes, and source links. Circuit 460 is the inspected PRM Liga 2 split-points circuit for 2025/26.
Division 1 coverage
GET /v1/coverages/33286
Division 1 is published through Prime League's separate coverage system rather than the community-league season/group pages. This endpoint returns the coverage title and format description, prize-money distribution, season-point awards, current standings, and the visible playday schedule/results. Coverage 33286 is the inspected 1. Liga Summer Split 2026 page.
Match and submitted lineups
GET /v1/matches/1163509
Combines the public match page with GET /ajax/leagues_match?action=init. It returns status, timestamp, score, teams, best-of format when available, submitted players, readiness/stand-in flags, public Riot IDs, OP.GG links, and multi-search URLs. It also exposes the public scheduling reply window, lineup mode/deadline, ready-up state, hosting platform/status/game, reporting start, workflow steps, and visible match sections.
Operational guidance
- The SQLite upstream cache has a default TTL of five minutes and survives process restarts.
- Expired cache records are used as an offline fallback when Prime League is temporarily unreachable. Set
PRIMELEAGUE_ALLOW_STALE_CACHE=falseto disable this. - Keep upstream concurrency at one and do not lower the default 1.2-second request interval without permission from the site owner.
- Do not mirror private/logged-in fields, comments, admin logs, or write actions.
- Store stable numeric IDs as primary keys; names and slugs change.
- Treat upstream HTML and JSON as untrusted input.
- Pin parser tests to captured, non-sensitive fixtures before production deployment.
- Contact Prime League/onethreefive gaming for official API access or written scraping permission before operating this at scale.
SQLite persistence
No external database server is required. Node's built-in SQLite driver stores:
upstream_cache: persistent read-through cache for upstream HTML and JSON;resource_snapshots: the latest normalized JSON for every successful API request path;resource_history: deduplicated historical versions, added only when the response content changes.
Storage inspection endpoints:
GET /v1/storage/stats
GET /v1/storage/snapshots
GET /v1/storage/snapshots?path=/v1/seasons/3220/spring-split-202526/divisions/5/schedule?page=1%26size=5%26state=all&history=5
Override the database location with PRIMELEAGUE_DATABASE_PATH. Back up the SQLite file together with its -wal file while the API is running, or stop the API before copying just the main database file.
Known limitations
- Node currently labels its built-in
node:sqlitemodule experimental, so it prints anExperimentalWarningat startup even though the tested API is stable enough for this local service. - No official API contract or published rate limit was found.
- Plain HTTP clients currently receive a CDN
403challenge (errorcode 112). /ajax/list_loadand some other routes return JSON envelopes containing HTML.- Historical seasons can use different stage IDs and layouts. Always discover structure from the season page.
- The current bracket parser returns the ordered public match list, not explicit dependency edges.
- Champion detail rows and draft/player performance data are loaded by additional undocumented POST widgets; this version returns the stable summaries visible in the initial page only.
- Coverage IDs are not the same as community season IDs and must currently be discovered from Prime League's coverage navigation.
- Public team-finder listings exist, but are intentionally excluded because they aggregate player availability and profile details that are unnecessary for league/bracket ingestion.
- Language selection is controlled by
PRIMELEAGUE_LANGUAGE=en|de; some upstream AJAX links may still use the browser session's language.
Development
npm test
npm run build
npm run dev
Dependencies
Dependencies
| ID | Version |
|---|---|
| cheerio | ^1.1.2 |
| fastify | ^5.6.2 |
| playwright | 1.61.1 |
Development dependencies
| ID | Version |
|---|---|
| @types/node | ^24.5.2 |
| tsx | ^4.20.5 |
| typescript | ^5.9.2 |