If your country-picker, flag quiz, or "where are our users" dashboard just broke: restcountries.com retired all of its legacy versions. Every request to /v1, /v2, /v3, /v3.1, or /v4 now redirects to an error envelope (verified Aug 2026):
{ "success": false,
"data": null,
"errors": [{ "message": "This API version has been deprecated. β¦" }] }
π‘ Is restcountries.comβs legacy API still dead? We check it (and 36 other classic public mock/placeholder APIs) with a plain GET every 30 minutes β it answers HTTP 200, so uptime bots call it βupβ, but we probe the body for real data. See the live status page.
The nasty part: that error comes back with HTTP 200. So fetch(...).then(r => r.ok) passes, res.json() parses fine, and your app breaks one step later when the array you mapped over is null β silently, with no failed request in the network tab to point at. A decade of tutorials, Codecademy-style projects, and Stack Overflow answers built on https://restcountries.com/v3.1/all now fail exactly this way.
There is a new v5 at api.restcountries.com, but it is not a drop-in fix: it requires an account and an API key (Bearer token on every request), the free plan is capped at 1,000 requests per month with a 20-requests-per-10-seconds ceiling, the response shape changed to a JSON:API-style data envelope, and field names moved (name.common β names.common, cca2 β codes.alpha_2, and so on). Their own migration docs estimate a 15β20 minute mapping pass per integration.
For a country picker or a tutorial project, there's a simpler question: why is this an external API dependency at all? Country names, codes, capitals, and flags barely change. Below: one curl that hosts a real 250-country dataset as your own API β no key, no monthly cap, CORS on, filters/search/sort/pagination included, and you can edit the data because it's yours.
We host a ready-made countries dataset (250 countries and territories β name, official name, ISO codes, capital, region, subregion, area, currency, languages, flag emoji, lat/lng, borders). Pipe it into the importer:
curl -s https://mockbird.mockbird.workers.dev/data/countries.db.json \
| curl -s -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?name=countries' \
--data-binary @-
The response has your project id and an adminKey (save it β it's how you manage the project or claim it into an account later). Your API is live immediately:
https://mockbird.mockbird.workers.dev/m/YOUR_ID/countries
No signup, no API key, CORS enabled β call it straight from browser JavaScript.
Everything restcountries v3.1 tutorials did, mapped to standard query params:
| restcountries v3.1 | Your Mockbird API |
|---|---|
/v3.1/all | /countries (paginate with _page/_limit; total in X-Total-Count) |
/v3.1/name/germany | /countries?q=germany (case-insensitive, searches every field) |
/v3.1/alpha/JP | /countries?code=JP (or ?cca3=JPN) |
/v3.1/region/europe | /countries?region=Europe |
/v3.1/subregion/central%20asia | /countries?subregion=Central+Asia |
/v3.1/capital/tokyo | /countries?capital=Tokyo |
?fields=name,capital,flags | ?select=name,capital,flag |
| (no equivalent) | range filters: ?area_gte=1000000, sort: ?sortBy=area&order=desc |
Try them (replace YOUR_ID, or run the import above first):
# the 53 European countries, name + capital + flag only
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/countries?region=Europe&select=name,capital,flag'
# free-text search β matches names AND capitals
# (q=stan finds Kazakhstan⦠and the Falkland Islands, capital Stanley)
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/countries?q=stan&select=name,capital'
# the 7 giants over 7M kmΒ², largest first
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/countries?area_gte=7000000&sortBy=area&order=desc&select=name,area'
# one country by ISO code
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/countries?code=JP'
# the 45 landlocked countries
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/countries?landlocked=true&select=name,region'
Unlike an external read-only API, this is a real CRUD backend:
PATCH /countries/83 {"note":"DACH"} β add whatever fields your app needs. Writes persist./m/YOUR_ID/graphql ({ countries(where:{region:"Asia"}, sortBy:"area", order:"desc") { name capital } }) with GraphiQL in the browser.borders array (["AUT","BEL","CZE",β¦]) exactly as imported.GET /m/YOUR_ID/db.json hands the whole dataset back in json-server format β no lock-in.?mock_delay=2000, ?mock_status=500, chaos and rate-limit params β drill the error handling the real outage just taught you to want.| REST Countries v5 | Your Mockbird project | |
|---|---|---|
| API key | Required (Bearer, signup) | None |
| Free requests | 1,000/month, 20 req/10s | 10,000/day per project |
| Response shape | JSON:API-style data envelope | Plain array / object (or reshape it) |
| Data freshness | Maintained β weekly ISO/UN review, population synced every 4h, 90+ fields, flag CDN | Snapshot β static facts (names, codes, capitals, regions, area, flags) |
| Population data | β live-synced | β not included (it changes; we won't serve stale numbers as fresh) |
| Writes / custom fields | Read-only | Full CRUD, add any field |
| Old v3.1 URLs | Dead (error envelope, HTTP 200) | n/a β your URLs, they stay up |
The dataset is derived from the open-source mledoze/countries project β the same community dataset the original REST Countries API was built on. It contains information from mledoze/countries, which is made available here under the Open Database License (ODbL) v1.0. Flattened for import: one record per country, arrays like capital reduced to their first value, languages/currencies joined into strings, borders kept as-is. Spot something wrong? It's your copy β PATCH it.
The one-liner in section 1 is the whole setup β or open the dashboard and paste the dataset into the import panel. The same project can also mock any third-party API your app calls, serve weather- or news-shaped endpoints for the classic tutorial apps, add the matching currencies + exchange-rates or timezones datasets, or host any CSV as an API.