Search box autocomplete
GET /v1/places/autocomplete?q= is the endpoint behind a search box: one call per keystroke,
answered from a prefix index built into the data version. It needs an API key with the route
scope; from a browser, use a publishable key.
curl -H "Authorization: Bearer $RAIL_API_KEY" \ "https://api.routing.gleisklar.de/v1/places/autocomplete?q=kiel%20h&focus=54.32,10.13&limit=5"{ "results": [ { "id": "node/4711", "name": "Kiel Hauptbahnhof", "kind": "station", "ds100": "AK", "uopid": "DE000AK", "uic": "8000199", "country": "DE", "lat": 54.3135, "lon": 10.131, "distance_m": 750 }, { "id": "node/4712", "name": "Kiel-Hassee CAU", "kind": "halt", "country": "DE", "lat": 54.3301, "lon": 10.1102, "distance_m": 2211 } ], "next_cursor": null}(Illustrative values.) id is the place’s ID as in /v1/places; kind is
station, halt or operational_point.
What matches
Section titled “What matches”- Every word of
qmust start a word of the name, in any order:kiel h,frankf main,main frankandst peterall work. Each keystroke narrows the list. - Codes:
qmay also be the start of a DS100 code, a RINF operational point ID or a UIC code (AK,DE000A,80001). Written in capitals, an exact code ranks first. - Umlauts and diacritics:
lüb,luebandluball find Lübeck. Diacritics you type are meant:Fürthdoesn’t suggest Furth im Wald. - Abbreviations work both ways:
kiel hbfandkiel hauptbfind “Kiel Hauptbahnhof” and “Kiel Hbf” alike (alsoBf,Rbf,Gbf,St.). - No typos, no substrings. That keeps suggestions predictable while typing. When nothing
starts with the text, fall back to
GET /v1/places?q=, which corrects typos.
Ranking and bias
Section titled “Ranking and bias”Suggestions are ordered by how well they match (the whole name, then names starting with the query, then names with the words further on), then stations before halts before operational points, more railway lines nearby and shorter names.
Two optional parameters move nearby places up without filtering anything out:
| Parameter | Effect |
|---|---|
focus=lat,lon | Closer places rank higher, e.g. the map centre. Each result then has distance_m. |
focus_scale_km | Distance from focus at which its bonus halves (km, default 50). Smaller keeps suggestions local. |
bbox=minLon,minLat,maxLon,maxLat | Places inside rank higher, e.g. the visible map. |
This is the difference from GET /v1/places, where bbox is a filter: in autocomplete a good
match far away still shows up, so typing “Berlin” on a map of Munich still suggests Berlin. To
restrict results, use kinds (station, halt, operational_point, comma-separated) and
country (ISO 3166-1 alpha-2; places of unknown country are left out), which filter as in
/v1/places. limit is 1 to 20, default 8; more pages come with cursor (see
pagination).
After the pick
Section titled “After the pick”Suggestions are deliberately lean: ID, name, kind, codes, country and coordinates. Use the code
in a route ({"station": "AK"}), or fetch the whole place with GET /v1/places/{id} (its codes,
other names, lines nearby, its OSM stations and RINF operational points, and its
platforms). GET /v1/places/lookup?code= finds places by an exact code.
Pricing
Section titled “Pricing”Autocomplete is billed at 0.2 requests per call: 5 calls = 1 request, so a user typing a station name letter by letter costs a request or two. Usage shows the calls, and the billable requests next to them; the included requests, the monthly cap and statements count billable requests (the month’s total rounded once). Rate limits count every call.
In the browser with the SDK
Section titled “In the browser with the SDK”import { createClient } from "@quebi/rail-api"
const api = createClient({ apiKey: "rr_pk_…" })let pending: AbortController | undefinedlet timer: ReturnType<typeof setTimeout>
input.addEventListener("input", () => { clearTimeout(timer) timer = setTimeout(async () => { pending?.abort() // drop the previous keystroke's request const ctrl = new AbortController() pending = ctrl try { const { results } = await api.autocomplete(input.value, { focus: map.getCenter(), // MapLibre's { lat, lng } works kinds: ["station", "halt"], signal: ctrl.signal, }) render(results) } catch (err) { if (!ctrl.signal.aborted) throw err // an aborted call rejects with NetworkError } }, 100)})A short debounce (100–150 ms) skips most intermediate keystrokes without feeling slow.