DEV Community

chovy
chovy

Posted on Originally published at dev.profullstack.com

Live scores from leagues no US scoreboard carries

Live scores from leagues no US scoreboard carries

nichedb.dev/c/sports has two live score sources now. It used to have one, and that one only knew about the games American networks broadcast.

The new one came out of a $9 a month TheSportsDB membership we already had. Every workaround in our sports code was written against limits that membership lifts, and nobody had gone back to check.

What the free key was costing us

Same endpoints, same day, two keys:

Request Free key Subscriber key
One day of TV listings 1 row 372 rows
Every team in a league ignored, or 10 rows every team, all 63 fields
The league catalogue refused 1,544 leagues, one request
A team roster 10 names, alphabetical the whole squad
Live scores no such endpoint every game on, worldwide
Rate limit 30 a minute 100 a minute

The catalogue line is the one that mattered most. With no list endpoint answering, the only way to find leagues was to try ids one at a time from 4328 upward and stop after 25 misses in a row. That is 1,700 requests to find about 1,500 leagues, and it can never find one outside the range it already knows about. The v2 catalogue returns the whole list in a single request.

Teams were worse. We walked roughly 24,000 team ids, three quarters of which are gaps, because the per league search was capped at ten rows. On a subscriber key that same search returns every team in the league with all 63 fields, the same row the one by one lookup gives.

Here are the last run of the old code and the first run of the new one, out of the same table:

sportsdb-teams | 25 teams from 25 lookups (ids 151069 to 151093)
sportsdb-teams | 388 teams from 25 leagues (1-25 of 1544)
Enter fullscreen mode Exit fullscreen mode

Same request budget. Fifteen times the rows. A full pass over the team catalogue went from about a week to a couple of hours.

The live part

The new source asks livescore/all once and gets back every game being played anywhere, about 50 KB. First run in production returned 80 games with 38 in play: Slovak Extraliga, Finnish Liiga, African Cup of Nations qualifiers.

It does not replace the ESPN one. ESPN is deeper where it goes, with the clock, the venue, the odds and play by play. But ESPN blocks cloud egress, so every scoreboard we pull goes through a metered residential proxy at a few hundred kilobytes a time, and a run can only afford to probe a handful of leagues. The two answer different questions, so both rows are kept under their own ids and matching them stays the reader's job.

Four things that bit

search_all_teams.php?l= matches the league's display name and only that. l=MLB returns thirty teams. l=Major League Baseball returns null. The names have to come from the catalogue, never from anything a human typed.

lookup_all_teams.php is gone. It answers HTML now, not JSON, so anything checking only for a non-200 sails past it into a parse error.

The v2 API takes the key in an X-API-KEY header instead of the URL path. That is a small privacy win: v2 request URLs are safe to log, where the v1 ones never are.

Live timestamps are UTC written without a zone, like 2026-09-23T15:30:00. Date.parse reads that as local time, so a box in the wrong zone shifts every kickoff by its offset. Appending the Z is the entire fix.

Worth checking your own

The interesting part was not the code. It was that the key was already set in production while the code still ran against the free tier's shape, because the workarounds were written once and nobody re-measured them after the key arrived. If you have a paid API key somewhere in your stack, go read what your code assumes about it.

Live now at https://nichedb.dev/c/sports.

Top comments (0)