For years, balldontlie was the teach-yourself-fetch sports API: www.balldontlie.io/api/v1/players, no key, no signup, real NBA data. It's in React course capstones, data-science notebooks, and a thousand "build an NBA stats app" tutorials. All of those URLs are now dead (we re-checked while writing this; our status page for it re-checks every 30 minutes):
$ curl https://www.balldontlie.io/api/v1/players
<!DOCTYPE html>β¦ // HTTP 404 β an HTML page, res.json() throws
$ curl https://api.balldontlie.io/v1/players
Unauthorized // HTTP 401 β every request needs an account API key
To be fair: balldontlie didn't die β it grew up. The current API is alive, maintained, and covers more sports than ever; it just requires creating an account and sending the key in an Authorization header, and the free tier is rate-limited to about 5 requests a minute, with advanced stats behind paid tiers. Fine for a personal project. Rough for the tutorial/classroom/CI use the old keyless API was loved for: a page of students burns 5 req/min instantly, keys don't belong in front-end example code, and a test suite shouldn't 429 because the demo API is metered.
We host a starter dataset β all 30 NBA teams with balldontlie's exact field names (abbreviation, city, conference, division, full_name, name) plus 60 notable players (first_name, last_name, position, height_feet, height_inches, weight_pounds, and a teamId link). Pipe it into the importer:
curl -s https://mockbird.mockbird.workers.dev/data/nba.db.json \
| curl -s -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?name=nba' \
--data-binary @-
The response contains your project id and an adminKey (save it). Your API is live immediately β no signup, no key, CORS on, no per-minute meter (limits are a flat 10k requests/day per project):
https://mockbird.mockbird.workers.dev/m/YOUR_ID/teams?limit=30
https://mockbird.mockbird.workers.dev/m/YOUR_ID/players
| balldontlie | Your Mockbird API |
|---|---|
/api/v1/teams | /teams?limit=30 (default page size is 20) |
/api/v1/players | /players |
/api/v1/players/28 | /players/28 |
/api/v1/players?search=curry | /players?q=curry (or ?last_name_like=curry) |
?page=2&per_page=5 | ?page=2&limit=5 |
nested "team": {β¦} object on every player | /players/28?_expand=team β joins the team record in via teamId; works on lists too |
| β (no equivalent) | /teams/10/players β a team's roster as a nested route |
| β (no equivalent) | /players?height_feet_gte=7 β range filters on any numeric field |
And the response envelope. balldontlie wraps everything in {"data": [β¦], "meta": {β¦}} β tutorial code destructures res.data everywhere. Mockbird returns a bare array by default, but a response-envelope template reproduces the original shape, either per-request or as a project-wide default:
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/players?limit=5&page=2&mock_envelope=%7B%22data%22%3A%22%24data%22%2C%22meta%22%3A%7B%22total_count%22%3A%22%24total%22%2C%22current_page%22%3A%22%24page%22%2C%22per_page%22%3A%22%24limit%22%7D%7D'
{"data":[β¦5 playersβ¦],
"meta":{"total_count":60,"current_page":2,"per_page":5}}
That URL-encoded blob is just {"data":"$data","meta":{"total_count":"$total","current_page":"$page","per_page":"$limit"}}. Set it once as the project default in the dashboard's Response envelope card and existing res.data.map(β¦) tutorial code works unchanged.
The starter rosters are a snapshot β two notable players per team as of when we wrote the dataset. NBA rosters churn constantly, and that's the point: this is your data, with full CRUD. The next blockbuster trade is a one-liner:
curl -X PATCH "https://mockbird.mockbird.workers.dev/m/YOUR_ID/players/28" \
-H 'content-type: application/json' -d '{"teamId": 1}'
GET /players/28?_expand=team now shows the new team. Add rookies with POST, cut players with DELETE, or paste a bigger roster file into the dashboard import panel (up to 1,000 records per collection). The same project also serves GraphQL over the same data, delay/error simulation for loading-state demos, and a db.json export so you can leave with your data anytime.
| balldontlie today | Your Mockbird project | |
|---|---|---|
Old tutorial URLs (/api/v1/β¦) | β 404 | n/a β new base URL either way |
| Signup / API key | account + key in every request | none |
| Rate limit (free) | ~5 requests/minute | 10k requests/day per project, no per-minute meter |
| Data | β real, live NBA data back to 1979 β games, box scores, season averages (this is what you're paying the key for, and for a real stats app it's worth it) | a frozen 30-team / 60-player starter snapshot; no games or stats |
| Writes | β read-only | β full CRUD β trade, cut, sign players |
| Deterministic for tests/teaching | β live data shifts under you | β same data every run; snapshots to save/restore states |
| Front-end safe | key ends up in client code or a proxy | keyless by design (optional mock auth if you want 401s to test) |
Use the real thing when you need real stats. If your app's value is actual NBA numbers β live scores, season averages, historical box scores β sign up for balldontlie and put the key behind a small server-side proxy; no mock can substitute for real data. Mockbird replaces the other thing balldontlie was being used for: a free, keyless, NBA-shaped REST API to teach fetch calls, build UI against, and run tests on.
The one-liner in section 1 is the whole setup β or open the dashboard and paste the dataset into the import panel. balldontlie joins the long list of once-keyless tutorial APIs that changed shape or vanished β ReqRes grew an upsell wrapper, restful-api.dev added an anonymous cap, boredapi and the Numbers API are gone outright (the live status board tracks 71 of them). Same fix works for all: ready-made quotes, activities, countries, number-facts, timezones, and currencies datasets, or host any CSV as an API.