Endpoints
Four files and a rule for walking them.
There is no key, no account and no rate limit. Every path below is a static object behind a CDN, so a request costs you a fetch and costs this service nothing.
Calling it
Base URL
https://api.placedb.org
- Authentication
- None. There is no key to send and no header to set.
- From a browser
-
Allowed from any origin for
GETandHEAD, so you can call it directly from client code with no proxy. - Caching
-
Data files are served with
max-age=86400;index.jsonusesmax-age=60because it is the record of what is published. Files are replaced only when the dataset publishes a new release, and changed files are evicted from the edge at that point. - Missing paths
-
A
404is an answer, not an error — see the autocomplete walk below, where it means the level above was already complete.
Countries
Every country, in one file
GET /v1/countries.json
255 countries and territories with ISO codes, calling codes, currency, capital, flag, and the number of divisions and settlements each one contains. Around 105 KB, so it is reasonable to fetch once and keep.
{"countries":[{"calling_code":["+247"],"capital_name":"Georgetown",
"code":"AC","flag_emoji":"🇦🇨","id":31890709,"iso_3166_1_alpha3":"ASC",
"name":"Ascension","official_language":"en","regions":2,
"settlements":4…
Divisions
Administrative divisions of one country
GET /v1/regions/<CC>.json
<CC> is the ISO 3166-1 alpha-2 code from
countries.json. Each division carries its
id, which is the key you need for the settlements file, and
its own settlement count.
{"country":"IS","regions":[{"id":203304,"iso_3166_2":"IS-1",
"lat":64.2,"lon":-21.7,"name":"Capital Region","population":233034,
"settlements":16,"slug":"capital-region"},{"id":220663,
"iso_3166_2":"IS-7","lat":65…
Settlements
Every settlement inside one division
GET /v1/cities/<CC>/<admin1>.json
<admin1> is a division id from the
regions file. This is the complete list — it is never ranked, never
truncated, and it is where you go when you need all of them rather than
the ones worth suggesting.
{"country":"AC","region":46197,"release":"2026-08-15T0426Z",
"s_version":"ed3ae9dd3630d619","settlements":[]}
The path says cities and the data says settlement. The
dataset's own word is the accurate one: these are populated places, not
only cities.
Autocomplete
Ask for the prefix you have
GET /v1/ac/<CC>/<prefix>.json
Autocomplete is a cached file and a client-side filter, not a search engine. Normalise what the user typed, ask for it, and go one character deeper only while the answer tells you it is holding back.
The rule
-
Lowercase the query, strip accents, and drop everything that is not a
letter or a digit.
San Josébecomessanjose. -
Fetch the bucket for the first character. If it comes back
"complete": true, it holds every place with that prefix — filter it locally and stop. -
If it comes back
"complete": false, it holds the most populousplacesonly, andtotaltells you how many exist. Fetch one character deeper. -
A
404means no place in that country carries the prefix, or the level above already answered in full. Either way, stop.
Worked example
/v1/ac/NP/k.json
complete · 216 places · Kathmandu is in it
/v1/ac/NP/ka.json
404 — the walk already ended at one character
/v1/ac/DE/ber.json
partial · 827 carry the prefix, the ranked head is returned
Nepal never needs a second character; Germany does. The depth is decided
per bucket when the tree is built, not per country, which is why you read
complete instead of guessing a prefix length.
{"complete":true,"country":"NP","places":[{"id":25104008,
"lat":26.666667,"lon":87.333333,"name":"Koshi Province",
"population":4961412,"type":"region"},{"id":25104017,"lat":29.27,
"lon":82.18,"name":"Karnali Prov…
In code
const key = (s) =>
s.toLowerCase().normalize('NFD')
.replace(/\p{Diacritic}/gu, '')
.replace(/[^a-z0-9]/g, '');
async function suggest(cc, query) {
const k = key(query);
let best = null;
for (let depth = 1; depth <= k.length; depth++) {
const res = await fetch(
`https://api.placedb.org/v1/ac/${cc}/${k.slice(0, depth)}.json`
);
if (!res.ok) break; // 404: nothing deeper to ask for
best = await res.json();
if (best.complete) break; // it is holding nothing back
}
return (best?.places ?? []).filter(
(p) => key(p.name).startsWith(k)
);
}
Conventions
Things that hold everywhere
- Absent means absent
-
A key with no value is omitted rather than sent as
null. Most settlements have no population figure, and across 1,674,947 rows that omission is most of the payload. - Coordinates are numbers
-
latandlon, not strings. Every settlement has both — that is a guarantee of the dataset, not a tendency. - Names are ASCII
-
Reykjavík is served as
Reykjavik. The transliteration comes from the dataset and this service does not alter it. - A place can be both
-
Berlin is a division and a settlement, under the same
id. Autocomplete returns both, tagged withtype. They are different answers; disambiguate rather than deduplicate. - Every file names its release
-
Responses carry
releaseands_version, so you can always tell which publication you were handed.
Verifying
Check what you were served
GET /v1/index.json
The index lists every file in the tree with its byte length and sha256, alongside the release it was built from. It is around 3.9 MB, so fetch it deliberately rather than from a page.
$ curl -s https://api.placedb.org/v1/regions/IS.json | sha256sum
$ curl -s https://api.placedb.org/v1/index.json \
| jq -r '.files["regions/IS.json"].sha256'
The tree is generated from a published release by a deterministic build, so the same release and the same code produce identical bytes. You can regenerate it yourself and get the same hashes.
Leaving
Take the data and go
This service sells not having to host it — nothing more. The dataset is CC0 and published in full, so if this endpoint becomes inconvenient, slow, or gone, you can pull the release and serve it yourself. Nothing about that is a violation of anything.
$ curl -s https://geo.mindstellar.com/releases/latest.json
$ # then fetch releases/<version>/manifest.json and the files it lists
The pipeline that builds it is public at
mindstellar/location-data. Current release 2026-08-15T0426Z, CC0-1.0.