Issue Nº 53 — Nov 2, 2026
The World Bank API returns HTTP 200 when it has no data for you
Issue fifty-three covers the World Bank Indicators API in the Science & Math category. It needs no key and sends a wildcard CORS header. We queried version 2 of the API live on 2 November 2026. An empty result is a 200 with a null data element. A bad indicator is a 200 with a message array. A page size above 32,767 fails, and the failure was different each time we tried.
Three kinds of nothing
We sent GET https://api.worldbank.org/v2/country/XX/indicator/SP.POP.TOTL?format=json. The API returned HTTP 200. The body was [{"page":0,"pages":0,"per_page":0,"total":0,"sourceid":null,"lastupdated":null},null]. The second element is null, not an empty array. A request with date=2027 returned the same body. So did GET /country/PRK/indicator/SE.XPD.TOTL.GD.ZS?format=json&mrv=1.
GET /country/NZ/indicator/NOT.REAL?format=json also returned HTTP 200. Its body was a one-element array with a message key. The message id was 120 and the key was "Invalid value". This body has no meta object.
A per_page above 32,767 is the only case that gave us a real error. Even then, the result changed between probes. More detail is in the next section.
Your client must handle each case. Test for a null second element. Test for a message key in the first element. Set a timeout, and check the status code and content type before you parse.
The per_page limit is 32,767
We bisected the limit on GET /country/NZ/indicator/SP.POP.TOTL?format=json. A per_page of 32767 returned HTTP 200 with 13,228 bytes in 0.10 s. That is the largest signed 16-bit integer.
Above that number, we got three different results on the same day. One probe returned HTTP 400 with an XHTML page. In a later run, the server sent no bytes for 40 to 100 seconds. In our last run, per_page=32768 returned a Cloudflare 502 HTML page of 54,079 bytes. None of these bodies is JSON.
This series needs far less. New Zealand has 66 rows for this indicator, one for each year. A per_page of 100 returns all of them in one page. Keep per_page small and page through larger sets.
The meta object changes shape
In a data response, per_page is a number. GET /country/NZ/indicator/SP.POP.TOTL?format=json&per_page=2 returned per_page 2, pages 33 and total 66.
GET /country?format=json&per_page=1 returned per_page as the string "1". That meta object also has no sourceid and no lastupdated key. A typed client that expects one shape on every path will fail on one of them.
The data rows differ too. GET /country/CUB/indicator/SE.XPD.TOTL.GD.ZS?format=json&mrv=1 returned sourceid "2" and a unit key set to an empty string. The same path with mrnev=1 returned sourceid null and no unit key. With mrnev=3, sourceid was "2" again. Do not depend on either field.
mrv skips the empty years
The most recent years are often empty. GET /country/CUB/indicator/SE.XPD.TOTL.GD.ZS?format=json&per_page=4 returned rows for 2025, 2024 and 2023 with a value of null. The 2022 row held 8.44086351669284.
With mrv=1, the API returned only the 2022 row. With mrnev=3, it returned 2022, 2021 and 2020. Both parameters skipped the null years here. Use one of them to get the latest real figure.
If a country has no value at all, you get the null body from the first section.
/country mixes countries and aggregates
GET /country?format=json&per_page=400 returned a total of 296. Of those rows, 79 have a region id of NA and a region value of Aggregates. They are groups such as income levels and world regions. The other 217 are countries and territories.
Filter on region.id before you build a country list. A dropdown that skips this step will show aggregates next to countries.
To fetch several countries in one call, join the codes with a semicolon. GET /country/NZ;AU/indicator/SP.POP.TOTL?format=json&per_page=1 returned a total of 132, which is 66 rows for each country. Every response we probed carried access-control-allow-origin: *.
World Bank, by the numbers
- GET/country?format=json
- GET/country/all/indicator/{indicator}?format=json