TypeScript NBA API client. Zero dependencies. Wraps 138 stats endpoints and 4 live data endpoints from NBA.com.
- 138 stats endpoints — player, team, game, draft, and league data from
stats.nba.com - 4 live endpoints — real-time scoreboard, box scores, play-by-play, and odds from
cdn.nba.com - Fully typed — every endpoint has typed params, responses, and row interfaces
- Built-in retry, rate limiting, and timeout — exponential backoff, token-bucket rate limiter, configurable timeouts
- Custom fetch injection — plug in TLS impersonation libraries to bypass Akamai bot protection
- Zero dependencies — nothing to audit, nothing to break
- Node.js >= 18, Bun, or any runtime with
fetchsupport - Stats endpoints require a residential IP + TLS impersonation (see Akamai / TLS Fingerprinting)
- Live endpoints work from anywhere
bun add nba-api-ts
# or
npm install nba-api-tsimport { NBAClient } from 'nba-api-ts';
const nba = new NBAClient();
// Stats — player career totals
const career = await nba.stats.playerCareerStats({ playerID: 203999 });
console.log(career.careerTotalsRegularSeason[0].pts);
// Stats — league-wide player dashboard
const dashboard = await nba.stats.leagueDashPlayerStats({
season: '2024-25',
perMode: 'PerGame',
});
console.log(dashboard.leagueDashPlayerStats[0].playerName);
// Live — today's scoreboard (no Akamai, works anywhere)
const scoreboard = await nba.live.scoreboard();
console.log(scoreboard.scoreboard.games.length, 'games today');const nba = new NBAClient({
stats: { timeout: 30000, maxRetries: 3, rateLimit: 600, fetch: customFetch },
live: { timeout: 10000, rateLimit: 0 },
});nba.stats—StatsClientwith 138 endpoint methodsnba.live—LiveClientwith 4 methods (scoreboard, boxscore, playByPlay, odds)
138 endpoints are available (full list). Some commonly used ones:
| Method | Description |
|---|---|
playerCareerStats({ playerID }) |
Career and season stats |
commonPlayerInfo({ playerID }) |
Player biographical info |
commonAllPlayers({ season, leagueID }) |
All players for a season |
leagueDashPlayerStats({ season }) |
League-wide player stats |
leagueDashTeamStats({ season }) |
League-wide team stats |
playerGameLog({ playerID, season }) |
Game-by-game stats |
teamGameLog({ teamID, season }) |
Team game-by-game stats |
shotChartDetail({ playerID, season }) |
Shot chart data |
leagueGameFinder({ ... }) |
Search for games |
boxScoreTraditionalV3({ gameID }) |
Box score for a game |
playByPlayV2({ gameID }) |
Play-by-play data |
scoreboardV2({ gameDate }) |
Scoreboard for a date |
leagueStandings({ season }) |
League standings |
leagueLeaders({ season, statCategory }) |
League leaders |
commonTeamRoster({ teamID }) |
Team roster |
playerIndex({ season }) |
Player index |
Stats API responses use the NBA's headers[] + rowSet[][] format, automatically parsed into typed objects with camelCase keys:
// Raw NBA response:
// { "headers": ["PLAYER_ID", "PTS"], "rowSet": [[203999, 25.0]] }
// Parsed:
// { playerId: 203999, pts: 25.0 }Common parameter types are available as string unions:
import type { PerModeDetailed, SeasonTypePlayoffs, LeagueID } from 'nba-api-ts';
import { currentSeason, formatSeason, defaults } from 'nba-api-ts';
const season = currentSeason(); // "2025-26"
const prev = formatSeason(2023); // "2023-24"import { NBAApiError, NBATimeoutError, NBANetworkError } from 'nba-api-ts';
try {
await nba.stats.playerCareerStats({ playerID: -1 });
} catch (e) {
if (e instanceof NBAApiError) {
console.log(e.statusCode, e.url);
}
}- Full endpoint list — all 138 stats + 4 live endpoints with NBA API paths
- API docs — auto-generated TypeDoc reference
bun install
bun test # unit tests (offline, fast)
bun run build
# Integration tests — hits real NBA API, requires residential IP
NBA_INTEGRATION_TESTS=1 bun test tests/integration
# Integration tests with TLS fingerprint impersonation
NBA_INTEGRATION_TESTS=1 NBA_USE_TLS=1 bun test tests/integrationPRs welcome. Run bun run lint and bun test before submitting.
All stats.nba.com endpoints are behind Akamai bot protection that blocks requests based on TLS fingerprinting. This means Node.js and Bun's built-in fetch will be blocked. Live endpoints (cdn.nba.com) work fine from any environment.
To use stats endpoints from a server, provide a custom fetch backed by a library that can impersonate a browser's TLS fingerprint:
import { NBAClient } from 'nba-api-ts';
import { ModuleClient, SessionClient } from 'tlsclientwrapper';
const moduleClient = new ModuleClient();
await moduleClient.open();
const session = new SessionClient(moduleClient, {
tlsClientIdentifier: 'chrome_131',
timeoutSeconds: 30,
defaultHeaders: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://www.nba.com/',
'Origin': 'https://www.nba.com',
},
});
const nba = new NBAClient({
stats: {
fetch: async (url) => {
const res = await session.get(url);
if (res.status === 0) throw new Error(res.body);
const headers: Record<string, string> = {};
for (const [k, v] of Object.entries(res.headers))
headers[k] = Array.isArray(v) ? v.join(', ') : String(v);
return new Response(res.body, { status: res.status, headers });
},
},
});
const career = await nba.stats.playerCareerStats({ playerID: 203999 });Any fetch-compatible function works — tlsclientwrapper, curl-impersonate, Puppeteer, or a proxy that adds the right TLS fingerprint.
Beyond TLS fingerprinting, stats.nba.com drops connections from known cloud/datacenter IP ranges. If your requests hang without any response, this is likely the cause — it affects all major providers (AWS, GCP, Azure, etc.) and CI environments like GitHub Actions.
You'll need to make requests from a residential IP, either directly (local machine, home server) or via a residential proxy.
MIT