GET /v1/autocomplete
Overview
Prefix search designed for search boxes: partial words and typos are welcome. Feed it what the user has typed so far (18 rue de la pai) and get ranked suggestions back, optionally biased around the user's position. The response has the same shape as /v1/geocode, so a search box can switch between the two without changing its parsing.
Cost: 1 credit per call, whatever limit you ask for (see Credit cost). Availability: every plan, including Free.
Two behaviours are worth reading before you integrate: how suggestions from our several regional indexes are merged and ordered, and the fact that the cost does not depend on the number of results.
Request
GET https://api.geocodeme.net/v1/autocomplete
Required header: X-Api-Key: gm_YOUR_API_KEY
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q | string | Yes | n/a | Partial query (max 256 characters), e.g. 18 rue de la pai |
limit | integer | No | 5 | Maximum number of suggestions (1 to 20). Does not change the cost: the call debits 1 credit at limit=1 as at limit=20 |
lat | float | No | n/a | Bias latitude: puts the suggestions of the region closest to this point first, and sharpens ranking inside that region. Must be sent together with lon. See Result ordering |
lon | float | No | n/a | Bias longitude. Must be sent together with lat |
lang | string | No | n/a | Result language: fr or en. Default: local place names |
countrycodes | string | No | n/a | Region selector, not a country filter: it picks which regional indexes are queried, never what they return. The aliases eu (continental Europe), gp (Guadeloupe), mq (Martinique), gf (French Guiana), yt (Mayotte) and re (Réunion) each select one index, and listing several selects several (gp,mq). fr means the whole covered area, exactly as if the parameter were absent (it does not restrict results to French data), and it designates no index of its own, so next to any other code it is that other code that decides (gp,fr returns exactly what gp returns). A code we do not serve as a region designates nothing either: de on its own returns no suggestions, and eu,de returns all of continental Europe. A code can only ever bring in the index it designates: fr and any uncovered code designate none, so putting either next to an alias can never widen the answer; a second alias does widen, as a list should (gp,mq). See Which regions get searched |
format | string | No | json | Response format: json or geojson |
The aliases gp, mq, gf, yt and re are resolved by our regional router to target the matching overseas region. In the results, however, address.country_code remains fr for these territories, the OpenStreetMap convention (they are part of France). See Coverage for the covered areas.
Which regions get searched
Here countrycodes carries a single decision: where to look. The suggestion engine behind autocomplete has no forwardable country filter, so the parameter can only select regional indexes; it can never restrict what a selected index gives back.
- The six aliases select one index each.
eupicks the continental index,gp/mq/gf/yt/repick their island. Listing several is read as a logical OR, and it is the only way to ask for two regions at once (gp,mq). frmeans "the whole covered area", exactly like sending nothing at all. It is not a narrowing filter and it designates no index of its own, so it only has an effect when it is alone.- Any other code designates nothing. It cannot filter (there is nothing to filter with), so it selects no index. When nothing in the list selects an index, the call returns an empty list and no regional request is issued at all.
countrycodes | Indexes queried | What you get |
|---|---|---|
| (absent) | all six | suggestions from the whole covered area |
fr | all six, or the same subset the bias would have picked without the parameter | the same thing as sending nothing; fr is not a filter, and it does not disable the bias pruning either |
eu | continental only | anything in continental Europe |
gp | Guadeloupe only | anything in Guadeloupe |
gp,mq | Guadeloupe + Martinique | anything in those two islands |
gp,fr | Guadeloupe only | exactly what gp returns: one request, not six |
fr,eu | continental only | exactly what eu returns |
eu,de | continental only | all of continental Europe; de filters nothing here |
gp,de | Guadeloupe only | anything in Guadeloupe; de filters nothing here either |
de | none | no suggestions, and no regional request is issued |
fr,de | none | no suggestions, same as de on its own |
zz | none | no suggestions; an uncovered code selects nothing |
Codes are case-insensitive, surrounding spaces are ignored and duplicates are removed, so gp,fr, FR,GP and GP , FR are one and the same request.
The rule is the same as on /v1/geocode: an alias chooses the index that is searched, and a code can only ever bring in what it designates, so mixing fr, or any code we do not serve as a region, with an alias never widens the answer (a second alias does, exactly as gp,mq says it should). gp,fr is gp on both endpoints; fr,eu searches the continental index on both.
What still differs is what a country code can do. On /v1/geocode a code that is not an alias is forwarded to the search engine as a real country filter, applied inside the selected indexes. Here it has nowhere to go, and that shows up in three places if you share countrycodes values between the two endpoints:
eu,dereturns all of continental Europe here, and German results only on/v1/geocode.deon its own returns nothing at all here, where/v1/geocodereturns German results. An empty list is the honest answer: we will not silently serve the whole continent for a request we cannot honour.fron its own means the whole covered area here (Germany included), where on/v1/geocodeit means all French data, mainland and overseas.
If a suggestion list must hold a single country, filter client-side on address.country_code. Asking for limit=20, filtering, and displaying five costs the same single credit as asking for five (see Credit cost).
Result ordering: how regions are merged
Autocomplete is not backed by a single index. Continental Europe and each of the five overseas regions run their own suggestion engine (Coverage). Every call queries the regions selected by countrycodes (all six when the parameter is absent or fr and no bias is given, see Which regions get searched), asks each of them for limit results, and merges what comes back into one list truncated to limit.
When you pass lat/lon and no countrycodes (or countrycodes=fr, which asks for no restriction and is treated identically), the service first queries only the region containing that point (plus continental Europe if the point is in an overseas region). The remaining regions are queried right after, except those whose coverage area is entirely farther from your bias point than the last suggestion already retained: those could only rank after the cut and be dropped. Whenever a region of the first pass returned nothing at all, all the remaining ones are queried. Both passes feed the same merge, so the list you normally get is the one the full fan-out would have produced; the saved work is work whose results the merge would have dropped anyway.
The one exception is time. The call is bounded end to end at 2.5 s, both passes included. If the first pass has eaten nearly all of that, the second one is cut short (or not started at all), and the list comes back shorter than the full fan-out would have been. Truncating is the deliberate choice: on a search box a slightly short list is repaired by the very next keystroke, whereas a late list arrives after the user has typed three more characters. Such a response is served normally (200, same schema, no extra header) and is never cached, so the next keystroke starts from a fresh fan-out. It is not reported in X-Coverage-Degraded either; nothing failed.
The merge orders region blocks, never individual results. Inside a block, suggestions keep the exact order the region returned them: that in-region ranking is the only relevance signal available, and it already accounts for the lat/lon bias. The merge decides only which region goes first; blocks are then concatenated and the list cut to limit.
With a lat/lon bias
Regions are ordered by the great-circle distance from the bias point to that region's closest result: nearest region first.
The same query with two different bias points therefore returns two different lists. Distances below are measured from the bias point to the nearest result of each region (for a query matching a place near Villeurbanne for Europe, and near each overseas region's main city):
| Bias point | Region order (distance to that region's nearest result) |
|---|---|
Lyon, lat=45.7640&lon=4.8357 | Europe (2 km) → Guadeloupe (6,898 km) → Martinique (6,987 km) → French Guiana (7,116 km) → Mayotte (7,667 km) → Réunion (8,995 km) |
Pointe-à-Pitre, lat=16.2410&lon=-61.5330 | Guadeloupe (0 km) → Martinique (188 km) → French Guiana (1,610 km) → Europe (6,899 km) → Mayotte (12,168 km) → Réunion (13,393 km) |
With limit=5 and a bias in Lyon, all five suggestions come from the continental region; you do not get one suggestion per region. A region that returns nothing sinks to the last position and creates no hole in the list; a region that is down during the fan-out is simply absent (see Degraded coverage).
Without a lat/lon bias
Ordering falls back to an explicit, fixed priority: Europe, then Guadeloupe, Martinique, French Guiana, Mayotte, Réunion (blocks concatenated in that order).
This is a documented prior, not a relevance ranking: the continental index holds orders of magnitude more objects than the five overseas regions combined, so with no other information it is the likeliest source of the expected result. To put an overseas region first without a bias point, use the explicit lever: countrycodes=gp, mq, gf, yt or re.
Consequences for limit
There is no round-robin and no fixed cycle across regions. With limit=12, all twelve suggestions may come from a single region; that is the intended behaviour, not a coverage bug. If your search box must show results from several regions at once, issue one call per region with the matching countrycodes (each call costs its own credit) rather than expecting the merge to interleave them.
Sending lat/lon is the single most effective thing a search box can do: without it, a user in Fort-de-France typing a local street gets the continental block first.
Credit cost: per call, not per result
One call to /v1/autocomplete debits 1 credit, whatever limit you asked for. limit=1 and limit=20 cost exactly the same: X-RateLimit-Monthly-Remaining goes down by 1 in both cases. The fan-out across the regional engines is not billed either; however many regions are queried, still one credit.
Sizing consequence: over-sampling is free. If you want to filter suggestions client-side (drop results outside a bounding box, deduplicate, keep a single country, re-rank by your own criteria), ask for limit=20, filter, and display the five you need. It costs the same single credit as asking for limit=5; the only price is a slightly larger response body.
What does drive consumption is the number of calls: a search box that fires on every keystroke debits one credit per keystroke. Debounce the input (250 to 300 ms is a common value) and skip queries below three characters. The same rule applies to /v1/geocode and /v1/reverse, which also debit 1 credit per call regardless of limit; see Credits & Quotas for the endpoints whose cost does depend on request size (match, matrix, batch).
Code Examples
- cURL
- Python
- JavaScript
curl -H "X-Api-Key: gm_YOUR_API_KEY" \
"https://api.geocodeme.net/v1/autocomplete?q=18%20rue%20de%20la%20pai&limit=3&lat=48.8566&lon=2.3522"
Filter on an overseas territory (here Guadeloupe):
curl -H "X-Api-Key: gm_YOUR_API_KEY" \
"https://api.geocodeme.net/v1/autocomplete?q=pointe-a-pitre&countrycodes=gp"
import os
import requests
api_key = os.environ["GEOCODEME_API_KEY"]
response = requests.get(
"https://api.geocodeme.net/v1/autocomplete",
params={
"q": "18 rue de la pai",
"limit": 3,
"lat": 48.8566,
"lon": 2.3522,
},
headers={"X-Api-Key": api_key},
)
response.raise_for_status()
if response.headers.get("X-Coverage-Degraded"):
print(f"Partial results, regions down: {response.headers['X-Coverage-Degraded']}")
for result in response.json()["results"]:
print(f"{result['display_name']} ({result['lat']}, {result['lon']})")
const apiKey = process.env.GEOCODEME_API_KEY;
const params = new URLSearchParams({
q: "18 rue de la pai",
limit: "3",
lat: "48.8566",
lon: "2.3522",
});
const response = await fetch(
`https://api.geocodeme.net/v1/autocomplete?${params}`,
{ headers: { "X-Api-Key": apiKey } }
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const degraded = response.headers.get("X-Coverage-Degraded");
if (degraded) {
console.warn(`Partial results, regions down: ${degraded}`);
}
const data = await response.json();
for (const result of data.results) {
console.log(`${result.display_name} (${result.lat}, ${result.lon})`);
}
Response
All responses include rate limiting headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Monthly-Remaining, etc.).
Same schema as /v1/geocode, including the top-level name field. Two points are specific to this endpoint:
importanceisnullon every suggestion with the engine version we run: its GeoJSON output exposes no comparable relevance score. Never sort on it; the order results arrive in is the ranking (see Result ordering).display_nameis composed from the suggestion's own components, so its punctuation differs slightly from/v1/geocode(which returns Nominatim's own string):18 Rue de la Paix, 75002 Paris, Île-de-France, France. One more reason to displaynameanddisplay_namerather than parsing either.
{
"results": [
{
"lat": 48.8687,
"lon": 2.3315,
"name": null,
"display_name": "18 Rue de la Paix, 75002 Paris, Île-de-France, France",
"type": "house",
"importance": null,
"address": {
"house_number": "18",
"road": "Rue de la Paix",
"city": "Paris",
"state": "Île-de-France",
"postcode": "75002",
"country": "France",
"country_code": "fr"
}
},
{
"lat": 48.8698,
"lon": 2.3305,
"name": "Boulangerie Chez Momo",
"display_name": "Boulangerie Chez Momo, 22 Rue de la Paix, 75002 Paris, Île-de-France, France",
"type": "bakery",
"importance": null,
"address": {
"house_number": "22",
"road": "Rue de la Paix",
"city": "Paris",
"state": "Île-de-France",
"postcode": "75002",
"country": "France",
"country_code": "fr"
}
}
],
"attribution": "© OpenStreetMap contributors",
"meta": {
"took_ms": 23,
"cache_hit": false
}
}
The name field
name is the OpenStreetMap name tag of the suggested object, exposed at the top level of each result: the same field, with the same contract, as on /v1/geocode and /v1/reverse.
| Property | Value |
|---|---|
| Type | string or null |
| Example values | "École élémentaire Jules Ferry", "Boulangerie Chez Momo", "Musée du Louvre" |
null when | the OSM object carries no name tag, the usual case for a plain street or a numbered address. Always JSON null, never an empty string |
| Not to be confused with | type (the OSM category: school, bakery, house) and display_name (the full address line) |
Recommended rendering for a suggestion list: name as the title, display_name as the address line underneath, and fall back to display_name alone when name is null. Do not split display_name on its first comma to get a title: on 18 Rue de la Paix, 75002 Paris, … that yields 18 Rue de la Paix, and on a Nominatim-style string such as 4, Rue Victor Hugo, … it yields 4.
name is never an address component: address keeps holding address parts only (house_number, road, city, town, village, county, state, postcode, country, country_code).
With format=geojson, the response is a GeoJSON FeatureCollection, exactly as on /v1/geocode; name is present in each feature's properties, before display_name, and is null under the same conditions.
Degraded coverage: X-Coverage-Degraded
Autocomplete fans out across all covered regions (Europe + the five French overseas regions). If a region fails, the API still answers 200 with the results of the healthy regions and sets the response header:
X-Coverage-Degraded: re,yt
The header has exactly one meaning: every region listed really failed. It is a fault signal, and only that. A region gets listed when its instance refused the connection, returned a 5xx, returned a body we could not read, or returned nothing at all while it had its full per-region time budget.
Two things that can also shorten your list are deliberately not reported there, because nothing is broken:
- a region we never queried. With a
lat/lonbias, regions whose entire coverage area lies farther from you than the last suggestion already retained are skipped; they could only have been truncated away (see Result ordering). Not queried is not down. - a region cut short by the request budget. The call is bounded end to end at 2.5 s. Under load, a region still answering when the budget runs out is dropped from the merge rather than waited for. It never proved anything about its own health, so we do not denounce it.
When the list is merely shorter, the header is simply absent. Its presence therefore always means something is genuinely down on our side, which is what makes it worth logging.
Caching follows the same line. A response is stored only when it is both failure-free and complete. A list shortened by a failure or by budget pressure is served normally but never cached, so the next call starts from a fresh fan-out and the list becomes whole again as soon as the pressure passes; a two-second saturation can never freeze a short list for a day.
Integrating this in a search box
/v1/autocomplete is built for type-ahead, where the user's next keystroke is the cheapest repair mechanism you have. Four recommendations follow from that:
- Do not show a "partial coverage" warning while the user is typing. The header is operational telemetry for your dashboards, not a message for the person filling in the field. Surfacing it turns a two-second incident on one instance into a visible product defect.
- Do not retry on a short list. A list of 3 suggestions instead of 5 is not an error, and re-issuing the request doubles the load precisely when the service is under pressure. The next keystroke fires a fresh request ~200 ms later and repairs the list for free.
- Treat as an error only a list that is empty and stays empty. A single empty response mid-typing is ordinary; a four-letter prefix that matches nothing looks exactly like that. Wait until the user has stopped typing and the result is still empty before showing "no address found".
- Log the header, don't display it. Recording
X-Coverage-Degradedalongside your request logs is what lets you correlate a bad user session with an outage on our side, and it is the one place where the header pays for itself.
The same reasoning applies to the 2.5 s bound: if a call is slow, let it be truncated rather than compensating with a client-side retry or a longer timeout; a suggestion list that lands after the query has changed is worse than a short one.
Error Responses
Missing q parameter
{
"error": {
"code": "MISSING_PARAMETER",
"message": "Parameter 'q' is required",
"field": "q"
}
}
lat without lon (or the reverse)
{
"error": {
"code": "INVALID_PARAMETER",
"message": "'lat' and 'lon' must be provided together",
"field": "lon"
}
}
All endpoints are also subject to INVALID_API_KEY (401) and RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED (429) errors. See Error Codes for the full reference.