I built this because I needed NBA data for a side project (WedgieTracker: https://www.wedgietracker.com, yes, it tracks basketball wedgies). The Python ecosystem has swar/nba_api which is great, but the JS/TS side only had abandoned packages from 3-6 years ago with no types and piles of dependencies.
Some things that were interesting to work through:
TLS fingerprinting: stats.nba.com sits behind Akamai bot protection that rejects requests based on TLS fingerprints. Node's and Bun's built-in fetch get blocked immediately. The client accepts a custom fetch function so you can plug in something like tlsclientwrapper or curl-impersonate to spoof a browser TLS handshake.
IP blocking: On top of that, the API silently drops connections from datacenter IPs. AWS, GCP, Azure, all blocked.
Response parsing: The NBA returns data in a compact headers[] + rowSet[][] format instead of objects. The client parses these into typed objects with camelCase keys (PLAYER_ID → playerId), so you get full autocomplete on every response.
Rate limiting: Built-in retry with exponential backoff, token-bucket rate limiting, and configurable timeouts. The rate limits are undocumented so I settled on a conservative 600ms default through trial and error.
The live endpoints (scoreboard, box scores, play-by-play, odds) are on cdn.nba.com with no protection, so those work from anywhere.
gek0z•1h ago
Some things that were interesting to work through:
TLS fingerprinting: stats.nba.com sits behind Akamai bot protection that rejects requests based on TLS fingerprints. Node's and Bun's built-in fetch get blocked immediately. The client accepts a custom fetch function so you can plug in something like tlsclientwrapper or curl-impersonate to spoof a browser TLS handshake.
IP blocking: On top of that, the API silently drops connections from datacenter IPs. AWS, GCP, Azure, all blocked.
Response parsing: The NBA returns data in a compact headers[] + rowSet[][] format instead of objects. The client parses these into typed objects with camelCase keys (PLAYER_ID → playerId), so you get full autocomplete on every response.
Rate limiting: Built-in retry with exponential backoff, token-bucket rate limiting, and configurable timeouts. The rate limits are undocumented so I settled on a conservative 600ms default through trial and error.
The live endpoints (scoreboard, box scores, play-by-play, odds) are on cdn.nba.com with no protection, so those work from anywhere.
npm: https://www.npmjs.com/package/nba-api-ts
Docs: https://nba-api-ts.riccardo.lol