Geocoding
Host: https://geocode.topoexport.com. Every request carries a tpk_ key, see Authentication. The geocoder runs Pelias on OpenStreetMap, OpenAddresses, Who's On First and GeoNames data: parameter names and the response format are Pelias's.
Endpoints
| Endpoint | Use | Required parameters |
|---|---|---|
GET /v1/geocode/autocomplete | Suggestions while the user types | text |
GET /v1/geocode/search | Full-text search of an address or a place | text |
GET /v1/geocode/structured | Search from separate address fields | at least one of the structured fields |
GET /v1/geocode/reverse | Places and addresses around a point | point.lat, point.lon |
GET /v1/geocode/place | Records by identifier | ids |
GET /v1/geocode/nearby | Places around a point | point.lat, point.lon |
curl "https://geocode.topoexport.com/v1/geocode/autocomplete?text=10%20rue%20de%20riv&focus.point.lat=48.86&focus.point.lon=2.35&size=5&key=$TOPOEXPORT_API_KEY"
curl "https://geocode.topoexport.com/v1/geocode/search?text=Tour%20Eiffel&boundary.country=FR&key=$TOPOEXPORT_API_KEY"
curl "https://geocode.topoexport.com/v1/geocode/structured?address=5%20avenue%20Anatole%20France&postalcode=75007&locality=Paris&country=FR&key=$TOPOEXPORT_API_KEY"
curl "https://geocode.topoexport.com/v1/geocode/reverse?point.lat=48.8584&point.lon=2.2945&size=1&key=$TOPOEXPORT_API_KEY"
curl "https://geocode.topoexport.com/v1/geocode/place?ids=openstreetmap:venue:way/5013364&key=$TOPOEXPORT_API_KEY"
curl "https://geocode.topoexport.com/v1/geocode/nearby?point.lat=48.8584&point.lon=2.2945&layers=venue&size=10&key=$TOPOEXPORT_API_KEY"
More complete examples, one per use case, are in Examples.
Parameters
Parameters outside these lists are ignored. Their support per endpoint follows the Pelias API.
Common
| Parameter | Meaning |
|---|---|
size | Number of results, from 1 to 40. Another value falls back to 10 |
lang | Preferred language of the names, as a language code (fr, de…) |
layers | Comma-separated place types to return, see Layers |
sources | Comma-separated data sources: osm (OpenStreetMap), oa (OpenAddresses), wof (Who's On First), gn (GeoNames) |
boundary.country | Filter: ISO 3166-1 country codes, comma-separated (FR or FRA) |
boundary.rect.min_lat, boundary.rect.min_lon, boundary.rect.max_lat, boundary.rect.max_lon | Filter: results inside this rectangle only |
boundary.circle.lat, boundary.circle.lon, boundary.circle.radius | Filter: results inside this circle, radius in km |
boundary.gid | Filter: results inside the area with this gid |
Autocomplete and search
| Parameter | Meaning |
|---|---|
text | Text typed by the user. Required |
focus.point.lat, focus.point.lon | Optional bias: results near this point rank higher, the others still come back. Rounded to 2 decimals (about 1 km). Send it when you know where the user is or which area the map shows; otherwise leave it out, or narrow the search with boundary.country |
Reverse and nearby
| Parameter | Meaning |
|---|---|
point.lat, point.lon | The point to look around. Required |
Structured search
| Parameter | Meaning |
|---|---|
address, neighbourhood, borough, locality, county, region, postalcode, country, venue | Address fields, as separate values |
Place
| Parameter | Meaning |
|---|---|
ids | Comma-separated gid values. Required |
Layers
| Layer | Contents |
|---|---|
venue | Points of interest, businesses, landmarks |
address | Addresses with a house number |
street | Streets |
neighbourhood, borough | Parts of a city |
locality, localadmin | Cities, towns, villages, municipalities |
county, macrocounty, region, macroregion | Administrative areas |
postalcode | Postal codes |
country, dependency | Countries and dependent territories |
coarse | Shortcut for every administrative layer above street |
Address guard on autocomplete
On autocomplete, the address layer is searched only when text has at least 6 characters, or contains a digit. Shorter texts still return streets, places and venues. A search for 10 r therefore includes addresses, Rivoli too, but Riv does not.
The guard applies whether or not you send layers:
- without
layers, a short text searches every layer exceptaddress; - with
layers,addressis removed from the list for a short text; - with
layers=addressalone, a short text returns an emptyFeatureCollectionwith a message ingeocoding.warnings, and costs no credits.
The X-Geo-Address-Guard header tells which case applied. Searching addresses from the first keystrokes returns mostly noise: house numbers matching a few letters.
Response
Every endpoint answers a GeoJSON FeatureCollection in the Pelias format. Coordinates are [longitude, latitude]. An abridged example:
{
"geocoding": {
"attribution": "© TopoExport, © OpenStreetMap contributors, © OpenAddresses, Who's On First, GeoNames",
"query": { "text": "Tour Eiffel", "size": 10 }
},
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [2.2945, 48.8584] },
"properties": {
"gid": "openstreetmap:venue:way/5013364",
"layer": "venue",
"source": "openstreetmap",
"name": "Tour Eiffel",
"label": "Tour Eiffel, Paris, France",
"locality": "Paris",
"region": "Paris",
"country": "France",
"country_a": "FRA",
"confidence": 1
}
}
],
"bbox": [2.2945, 48.8584, 2.2945, 48.8584]
}
| Field | Meaning |
|---|---|
geometry.coordinates | Position, [lon, lat] |
properties.gid | Stable identifier, to pass to /v1/geocode/place?ids= |
properties.label | Full text to display |
properties.name | Name of the place or the address line |
properties.layer, properties.source | Place type and data source |
properties.housenumber, street, postalcode, locality, region, country, country_a | Address parts, when known |
properties.confidence | Match score of search results, from 0 to 1 |
properties.distance | Distance in km from the requested point (reverse, nearby) or the focus point |
Autocomplete suggestions include their coordinates: no second request is needed to place a suggestion on a map.
Response headers
| Header | Meaning |
|---|---|
X-Geo-Credits | Credits consumed by this request |
X-Geo-Plan | Plan of the key |
X-Geo-Cache | HIT when the response came from cache, MISS otherwise |
X-Geo-Region | Region of the geocoder that answered |
X-Geo-Address-Guard | applied when the address layer was left out, empty when nothing was left to search, off otherwise. See Address guard |
Examples
The browser examples read the key from a build-time variable (import.meta.env); use a key restricted to your site's origins. Server examples use a key without origin restriction, from an environment variable.
Address of a point clicked on a map (reverse)
const KEY = import.meta.env.VITE_TOPOEXPORT_API_KEY;
map.on("click", async (event) => {
const params = new URLSearchParams({
"point.lat": String(event.lngLat.lat),
"point.lon": String(event.lngLat.lng),
layers: "address,street,venue",
size: "1",
key: KEY,
});
const response = await fetch(`https://geocode.topoexport.com/v1/geocode/reverse?${params}`);
if (!response.ok) return;
const [nearest] = (await response.json()).features;
if (nearest) {
// properties.distance: km between the clicked point and the result
showAddress(nearest.properties.label, nearest.properties.distance);
}
});
Search from separate form fields (structured)
When the address is already split into fields, structured avoids parsing a single line of text:
const params = new URLSearchParams({
address: form.street.value, // house number and street, e.g. "5 avenue Anatole France"
postalcode: form.postcode.value,
locality: form.city.value,
country: "FR",
size: "1",
key: KEY,
});
const response = await fetch(`https://geocode.topoexport.com/v1/geocode/structured?${params}`);
const [match] = (await response.json()).features;
// match.properties.confidence: from 0 to 1, low values mean only part of the address matched
Load saved places again (place)
Store the gid of the results your users pick, then fetch up to date records later, several at once:
// gids saved earlier, e.g. "openstreetmap:venue:way/5013364"
const ids = savedPlaces.map((place) => place.gid);
const params = new URLSearchParams({ ids: ids.join(","), key: KEY });
const { features } = await (await fetch(`https://geocode.topoexport.com/v1/geocode/place?${params}`)).json();
A gid that no longer exists is missing from features.
Points of interest around a point (nearby)
const params = new URLSearchParams({
"point.lat": "48.8584",
"point.lon": "2.2945",
layers: "venue",
size: "20",
key: KEY,
});
const { features } = await (await fetch(`https://geocode.topoexport.com/v1/geocode/nearby?${params}`)).json();
for (const place of features) {
console.log(place.properties.name, `${place.properties.distance} km`);
}
Search from a server
A server-side search with a timeout, which waits for Retry-After once when the API answers 429 or 503:
import os
import time
import requests
API = "https://geocode.topoexport.com/v1/geocode/search"
def geocode(text: str, country: str | None = None) -> dict | None:
params = {"text": text, "size": 1, "key": os.environ["TOPOEXPORT_API_KEY"]}
if country:
params["boundary.country"] = country
for attempt in range(2):
response = requests.get(API, params=params, timeout=10)
if response.status_code in (429, 503) and attempt == 0:
time.sleep(int(response.headers.get("Retry-After", "5")))
continue
response.raise_for_status()
features = response.json()["features"]
return features[0] if features else None
return None
place = geocode("10 rue de Rivoli, Paris", country="FR")
if place:
lon, lat = place["geometry"]["coordinates"]
print(place["properties"]["label"], lat, lon)
const API = "https://geocode.topoexport.com/v1/geocode/search";
export async function geocode(text, country) {
const params = new URLSearchParams({ text, size: "1", key: process.env.TOPOEXPORT_API_KEY });
if (country) params.set("boundary.country", country);
for (let attempt = 0; attempt < 2; attempt++) {
const response = await fetch(`${API}?${params}`, { signal: AbortSignal.timeout(10_000) });
if ((response.status === 429 || response.status === 503) && attempt === 0) {
const seconds = Number(response.headers.get("Retry-After") ?? 5);
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
continue;
}
if (!response.ok) throw new Error(`Geocoding failed: HTTP ${response.status}`);
const { features } = await response.json();
return features[0] ?? null;
}
return null;
}
const place = await geocode("10 rue de Rivoli, Paris", "FR");
if (place) {
const [lon, lat] = place.geometry.coordinates;
console.log(place.properties.label, lat, lon);
}
Autocomplete in a web page
Send a request once the user pauses typing, and cancel the previous one:
const KEY = import.meta.env.VITE_TOPOEXPORT_API_KEY;
let controller;
let timer;
input.addEventListener("input", () => {
clearTimeout(timer);
timer = setTimeout(async () => {
controller?.abort();
controller = new AbortController();
const params = new URLSearchParams({ text: input.value, size: "5", key: KEY });
try {
const response = await fetch(`https://geocode.topoexport.com/v1/geocode/autocomplete?${params}`, {
signal: controller.signal,
});
if (!response.ok) return;
const { features } = await response.json();
render(features.map((f) => ({ label: f.properties.label, lonLat: f.geometry.coordinates })));
} catch (error) {
if (error.name !== "AbortError") throw error;
}
}, 250);
});
Credits
| Request | Credits |
|---|---|
| Autocomplete | 5 |
| Search, structured, nearby | 10 |
| Reverse geocoding, place by ID | 5 |
Responses are cached for up to 24 hours (autocomplete and place) or 1 hour (the other endpoints). A cached response costs the same credits as a fresh one. A response in error, or an autocomplete emptied by the address guard, costs nothing.
Rate limits
Geocoding follows the common rate limits and fair use rules. When the geocoder is momentarily saturated, it answers 503 with a Retry-After of 5 seconds, without billing it. For autocomplete, wait for a pause in typing (about 250 ms) and cancel the previous request, as in the example above.
Attribution
Show the geocoding.attribution string of the response near the results. On the API Trial and API plans it already contains © TopoExport.
Map tiles
Worldwide OpenStreetMap basemap styles, cadastral overlays, 3D Tiles and terrain, served as vector tiles for MapLibre GL JS, Mapbox GL JS, QGIS, ArcGIS and Cesium.
Export API
Find the best official dataset for a location, then download its features over a bounding box as GeoJSON, glTF, OBJ, GeoTIFF or PNG.