TopoExport logo
Developer docs

Geocoding

Autocomplete, search, structured search, reverse geocoding and place lookup worldwide, with Pelias-compatible parameters and GeoJSON responses.

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

EndpointUseRequired parameters
GET /v1/geocode/autocompleteSuggestions while the user typestext
GET /v1/geocode/searchFull-text search of an address or a placetext
GET /v1/geocode/structuredSearch from separate address fieldsat least one of the structured fields
GET /v1/geocode/reversePlaces and addresses around a pointpoint.lat, point.lon
GET /v1/geocode/placeRecords by identifierids
GET /v1/geocode/nearbyPlaces around a pointpoint.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"

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

ParameterMeaning
sizeNumber of results, from 1 to 40. Another value falls back to 10
langPreferred language of the names, as a language code (fr, de…)
layersComma-separated place types to return, see Layers
sourcesComma-separated data sources: osm (OpenStreetMap), oa (OpenAddresses), wof (Who's On First), gn (GeoNames)
boundary.countryFilter: 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_lonFilter: results inside this rectangle only
boundary.circle.lat, boundary.circle.lon, boundary.circle.radiusFilter: results inside this circle, radius in km
boundary.gidFilter: results inside the area with this gid
ParameterMeaning
textText typed by the user. Required
focus.point.lat, focus.point.lonOptional 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

ParameterMeaning
point.lat, point.lonThe point to look around. Required
ParameterMeaning
address, neighbourhood, borough, locality, county, region, postalcode, country, venueAddress fields, as separate values

Place

ParameterMeaning
idsComma-separated gid values. Required

Layers

LayerContents
venuePoints of interest, businesses, landmarks
addressAddresses with a house number
streetStreets
neighbourhood, boroughParts of a city
locality, localadminCities, towns, villages, municipalities
county, macrocounty, region, macroregionAdministrative areas
postalcodePostal codes
country, dependencyCountries and dependent territories
coarseShortcut 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 except address;
  • with layers, address is removed from the list for a short text;
  • with layers=address alone, a short text returns an empty FeatureCollection with a message in geocoding.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]
}
FieldMeaning
geometry.coordinatesPosition, [lon, lat]
properties.gidStable identifier, to pass to /v1/geocode/place?ids=
properties.labelFull text to display
properties.nameName of the place or the address line
properties.layer, properties.sourcePlace type and data source
properties.housenumber, street, postalcode, locality, region, country, country_aAddress parts, when known
properties.confidenceMatch score of search results, from 0 to 1
properties.distanceDistance 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

HeaderMeaning
X-Geo-CreditsCredits consumed by this request
X-Geo-PlanPlan of the key
X-Geo-CacheHIT when the response came from cache, MISS otherwise
X-Geo-RegionRegion of the geocoder that answered
X-Geo-Address-Guardapplied 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)

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

RequestCredits
Autocomplete5
Search, structured, nearby10
Reverse geocoding, place by ID5

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.