Cette API est dépréciée. Elle ne reçoit plus que des correctifs de sécurité et de justesse, et sera retirée le 1er avril 2027. Ne l'utilisez pas pour une nouvelle intégration.
Le guide de migration donne, route par route, l'équivalent dans l'API v2.
Les routes servies
Mêmes chemins qu'auparavant, sans clé d'API. Chaque route répond avec ou sans barre oblique finale. Seuls les paramètres listés sont lus ; les autres sont ignorés, sauf sur ban/*, qui les transmet tous au moteur.
| Route | Paramètres lus | En v2 |
|---|---|---|
GET /services/ban/search | q (requis), limit, type, postcode, citycode, lat, lon, autocomplete… | Géocodage BAN |
GET /services/ban/reverse | lat, lon (requis), limit, type… | Géocodage BAN |
GET /services/geocode | q (requis), lang, limit | Géocodage OSM |
GET /services/reverse | lat, lon (requis), lang | Géocodage OSM |
GET /services/directions/route | point=lat,lon (au moins deux), vehicule ou vehicle, locale, points_encoded | Itinéraire |
GET /services/directions/isochrone | point=lat,lon (requis), vehicule ou vehicle, time_limit, distance_limit, buckets | Isochrone |
POST /services/directions/matrix | Corps JSON : from_points et to_points, ou points ; vehicle, out_arrays, fail_fast | Matrice |
Profils : car (par défaut), foot et bike. Tout autre chemin sous /services, dont l'ancien format /services/v1/*, répond 404.
Exemples d'appels
Un appel par route, avec sa réponse réelle, abrégée quand elle est longue (…). Ils servent à reconnaître et à vérifier les appels existants : pour une nouvelle intégration, partez de l'équivalent v2, en lien sur chaque route.
Rechercher une adresse BAN
Format natif d'addok, identique à celui de /v2/geocoding/ban/search.
curl -G "https://geoservice.example.com/services/ban/search" \
--data-urlencode "q=87 avenue d'italie, 75013" \
-d limit=1
const url = new URL("https://geoservice.example.com/services/ban/search");
url.search = new URLSearchParams({ q: "87 avenue d'italie, 75013", limit: 1 });
const { features } = await (await fetch(url)).json();
const [lon, lat] = features[0].geometry.coordinates;
import httpx
r = httpx.get(
"https://geoservice.example.com/services/ban/search",
params={"q": "87 avenue d'italie, 75013", "limit": 1},
)
lon, lat = r.json()["features"][0]["geometry"]["coordinates"]
{
"type": "FeatureCollection",
"version": "draft",
"features": [{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [2.358434, 48.824031]},
"properties": {
"label": "87 Avenue d'Italie 75013 Paris",
"score": 0.89274,
"housenumber": "87",
"id": "75113_4754_00087",
"banId": "aee01bd3-241d-4939-9981-2432d129343f",
"name": "87 Avenue d'Italie",
"postcode": "75013",
"citycode": "75113",
"x": 652897,
"y": 6858410.19,
"city": "Paris",
"district": "Paris 13e Arrondissement",
"context": "75, Paris, Île-de-France",
"type": "housenumber",
"importance": 0.82014,
"street": "Avenue d'Italie"
}
}],
"attribution": "BAN",
"licence": "ETALAB-2.0",
"query": "87 avenue d'italie, 75013",
"limit": 1
}
Adresse BAN d'un point
Ajoute distance, en mètres, entre le point demandé et l'adresse trouvée.
curl "https://geoservice.example.com/services/ban/reverse?lat=48.8240267&lon=2.3584248&limit=1"
const url = new URL("https://geoservice.example.com/services/ban/reverse");
url.search = new URLSearchParams({ lat: 48.8240267, lon: 2.3584248, limit: 1 });
const { features } = await (await fetch(url)).json();
const { label, distance } = features[0].properties; // distance en mètres
import httpx
r = httpx.get(
"https://geoservice.example.com/services/ban/reverse",
params={"lat": 48.8240267, "lon": 2.3584248, "limit": 1},
)
props = r.json()["features"][0]["properties"]
print(props["label"], props["distance"]) # distance en mètres
{
"type": "FeatureCollection",
"version": "draft",
"features": [{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [2.358434, 48.824031]},
"properties": {
"label": "87 Avenue d'Italie 75013 Paris",
"score": 0.9999999997274487,
"housenumber": "87",
"id": "75113_4754_00087",
"banId": "aee01bd3-241d-4939-9981-2432d129343f",
"name": "87 Avenue d'Italie",
"postcode": "75013",
"citycode": "75113",
"x": 652897,
"y": 6858410.19,
"city": "Paris",
"district": "Paris 13e Arrondissement",
"context": "75, Paris, Île-de-France",
"type": "housenumber",
"importance": 0.82014,
"street": "Avenue d'Italie",
"distance": 0
}
}],
"attribution": "BAN",
"licence": "ETALAB-2.0",
"limit": 1
}
Rechercher un lieu OSM
Format de photon : propriétés à plat, extent sur les objets étendus.
curl -G "https://geoservice.example.com/services/geocode" \
--data-urlencode "q=Avenue d'Italie 75013 Paris France" \
-d lang=fr -d limit=1
const url = new URL("https://geoservice.example.com/services/geocode");
url.search = new URLSearchParams({
q: "Avenue d'Italie 75013 Paris France",
lang: "fr",
limit: 1,
});
const { features } = await (await fetch(url)).json();
const { name, city, countrycode } = features[0].properties;
import httpx
r = httpx.get(
"https://geoservice.example.com/services/geocode",
params={"q": "Avenue d'Italie 75013 Paris France", "lang": "fr", "limit": 1},
)
props = r.json()["features"][0]["properties"]
print(props["name"], props["city"], props["countrycode"])
{
"features": [{
"geometry": {"type": "Point", "coordinates": [2.3574814, 48.8254891]},
"type": "Feature",
"properties": {
"osm_id": 685719842,
"osm_type": "W",
"osm_key": "highway",
"osm_value": "primary",
"name": "Avenue d'Italie",
"street": "Avenue d'Italie",
"postcode": "75013",
"country": "France",
"city": "Paris",
"countrycode": "FR",
"extent": [2.3574144, 48.825715, 2.3575502, 48.8252637]
}
}],
"type": "FeatureCollection"
}
Lieu OSM d'un point
Un seul résultat, quel que soit limit.
curl "https://geoservice.example.com/services/reverse?lat=48.8240267&lon=2.3584248&lang=fr"
const url = new URL("https://geoservice.example.com/services/reverse");
url.search = new URLSearchParams({ lat: 48.8240267, lon: 2.3584248, lang: "fr" });
const { features } = await (await fetch(url)).json();
const { housenumber, street, city } = features[0].properties;
import httpx
r = httpx.get(
"https://geoservice.example.com/services/reverse",
params={"lat": 48.8240267, "lon": 2.3584248, "lang": "fr"},
)
props = r.json()["features"][0]["properties"]
print(props["housenumber"], props["street"], props["city"])
{
"features": [{
"geometry": {"type": "Point", "coordinates": [2.3584248, 48.8240267]},
"type": "Feature",
"properties": {
"osm_id": 2540682965,
"osm_type": "N",
"osm_key": "place",
"osm_value": "house",
"housenumber": "87",
"street": "Avenue d'Italie",
"postcode": "75013",
"country": "France",
"city": "Paris",
"countrycode": "FR"
}
}],
"type": "FeatureCollection"
}
Itinéraire
Format de GraphHopper : distances en mètres, durées en millisecondes.
curl -G "https://geoservice.example.com/services/directions/route" \
-d point=48.855199,2.354035 \
-d point=48.853138,2.349185 \
-d vehicule=car -d locale=fr \
-d points_encoded=false
const url = new URL("https://geoservice.example.com/services/directions/route");
url.search = new URLSearchParams([
["point", "48.855199,2.354035"],
["point", "48.853138,2.349185"],
["vehicule", "car"],
["locale", "fr"],
["points_encoded", "false"],
]);
const { paths: [path] } = await (await fetch(url)).json();
console.log(`${path.distance} m, ${path.time / 1000} s`); // mètres, millisecondes
import httpx
r = httpx.get(
"https://geoservice.example.com/services/directions/route",
params=[
("point", "48.855199,2.354035"),
("point", "48.853138,2.349185"),
("vehicule", "car"),
("locale", "fr"),
("points_encoded", "false"),
],
)
path = r.json()["paths"][0]
print(f"{path['distance']} m, {path['time'] / 1000} s") # mètres, millisecondes
{
"hints": {},
"paths": [{
"instructions": [
{"distance": 15.0, "sign": 0, "interval": [0, 2], "text": "Conduisez vers le nord-ouest sur Rue de l'Hôtel de Ville.", "time": 5663, "street_name": "Rue de l'Hôtel de Ville"},
{"distance": 79.0, "sign": 2, "interval": [2, 8], "text": "Tournez à droite dans Rue de Brosse.", "time": 34211, "street_name": "Rue de Brosse"},
…
{"distance": 0.0, "sign": 4, "interval": [75, 75], "text": "Vous êtes arrivé à votre destination.", "time": 0, "street_name": ""}
],
"distance": 1185.0,
"time": 321450,
"bbox": [2.349207, 48.853633, 2.356614, 48.856957],
"points_encoded": false,
"points": {"type": "LineString", "coordinates": [[2.353976, 48.855143], [2.353882, 48.855185], …]},
"snapped_waypoints": {"type": "LineString", "coordinates": [[2.353976, 48.855143], [2.349207, 48.853633]]},
…
}],
"info": {"took": 96, "copyrights": ["Valhalla", "OpenStreetMap contributors"]}
}
Isochrone
Deux minutes à pied (time_limit en secondes) ; un polygone par bucket, du plus petit au plus grand.
curl -G "https://geoservice.example.com/services/directions/isochrone" \
-d point=48.853401,2.348792 \
-d time_limit=120 -d vehicule=foot
const url = new URL("https://geoservice.example.com/services/directions/isochrone");
url.search = new URLSearchParams({
point: "48.853401,2.348792",
time_limit: 120, // secondes
vehicule: "foot",
});
const { polygons } = await (await fetch(url)).json();
const zone = polygons[0].geometry; // bucket 0 : le plus petit
import httpx
r = httpx.get(
"https://geoservice.example.com/services/directions/isochrone",
params={"point": "48.853401,2.348792", "time_limit": 120, "vehicule": "foot"},
)
zone = r.json()["polygons"][0]["geometry"] # bucket 0 : le plus petit
{
"polygons": [{
"geometry": {
"coordinates": [[
[2.349792, 48.855144], [2.347792, 48.854954], [2.346638, 48.853554],
[2.348792, 48.851993], [2.350097, 48.852401], [2.351211, 48.853401],
[2.349792, 48.855144]
]],
"type": "Polygon"
},
"type": "Feature",
"properties": {"bucket": 0}
}],
"copyrights": ["Valhalla", "OpenStreetMap contributors"]
}
Matrice
Points en [lon, lat] ; distances en mètres, durées en secondes, indexées [origine][destination].
curl "https://geoservice.example.com/services/directions/matrix" \
-H "Content-Type: application/json" \
-d '{
"from_points": [[2.358052, 48.824101]],
"to_points": [
[2.364601, 48.833535],
[2.374128, 48.815678],
[2.342628, 48.845737]
],
"vehicle": "car",
"out_arrays": ["distances", "times"]
}'
const res = await fetch("https://geoservice.example.com/services/directions/matrix", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
from_points: [[2.358052, 48.824101]], // [lon, lat]
to_points: [
[2.364601, 48.833535],
[2.374128, 48.815678],
[2.342628, 48.845737],
],
vehicle: "car",
out_arrays: ["distances", "times"],
}),
});
const { distances, times } = await res.json(); // mètres, secondes
import httpx
r = httpx.post(
"https://geoservice.example.com/services/directions/matrix",
json={
"from_points": [[2.358052, 48.824101]], # [lon, lat]
"to_points": [
[2.364601, 48.833535],
[2.374128, 48.815678],
[2.342628, 48.845737],
],
"vehicle": "car",
"out_arrays": ["distances", "times"],
},
)
corps = r.json()
distances, times = corps["distances"], corps["times"] # mètres, secondes
{
"distances": [[1484, 2300, 3028]],
"times": [[216, 293, 485]],
"info": {"copyrights": ["Valhalla", "OpenStreetMap contributors"]}
}
Ce qui a changé depuis l'ancien serveur
Les chemins, les paramètres et la structure des réponses sont conservés. Les moteurs, eux, ont changé : les valeurs peuvent différer, et quelques comportements ont été corrigés.
Toutes les routes
- Une erreur répond avec le code HTTP attendu et un corps JSON au format du GeoService (ci-dessous), au lieu d'une page HTML d'erreur ou d'une connexion coupée.
- Une requête qu'un moteur refuserait est refusée d'emblée, en
400, avec undetailqui nomme le paramètre en cause.
Géocodage BAN (ban/search, ban/reverse)
- Chaque résultat porte en plus
properties.banId, l'identifiant de l'adresse dans les données récentes de la BAN. - Une longitude aberrante, très loin au-delà de ±180, est refusée (
400).
Géocodage OSM (geocode, reverse)
- Le premier résultat peut changer, au même endroit : par exemple l'avenue elle-même plutôt qu'un point d'intérêt situé sur l'avenue. L'ordre et le nombre des résultats diffèrent aussi.
- Les paramètres
osm_tag,layer,bbox,countrycode,zoom,location_bias_scaleetdedupesont ignorés : les résultats ne sont plus filtrés.latetlonne favorisent plus les résultats proches. reverserenvoie un seul résultat, quel que soitlimit;radiusest ignoré.
Itinéraire (directions/route)
vehiculeest désormais pris en compte. L'ancien serveur l'ignorait et calculait tout en voiture :vehicule=footdonne maintenant un itinéraire à pied.bikeest servi ; une valeur inconnue répond400.- Les durées en voiture sont nettement plus longues en ville (près de deux fois sur un trajet court dans Paris) : le nouveau moteur compte les virages, les feux et les intersections. À pied, elles sont quasiment identiques.
- Un
pointmal formé, y comprislat,lon,altitude, est refusé (400) au lieu d'être ignoré en silence. Au-delà d'un nombre maximal de points, la requête est refusée (400). - Les autres paramètres de l'ancien moteur sont ignorés :
instructions,calc_points,elevation,type=gpx… Les instructions sont toujours renvoyées, en JSON. hintsest vide,weightn'est qu'une approximation etinfo.copyrightsnomme Valhalla.- Instructions : une arrivée à chaque étape intermédiaire, un rond-point en deux instructions (entrée, puis sortie), des textes reformulés. Douze langues ne sont plus traduites (
ast,eo,fa,fil,gl,he,hr,hsb,lt,ne,sr,zh) et passent à l'anglais. - Un point peut être recalé sur une autre voie proche qu'auparavant.
Isochrone (directions/isochrone)
vehiculeest désormais pris en compte, comme pour l'itinéraire :vehicule=footdonne une isochrone à pied, et non plus en voiture.bikeest servi.- Les zones sont plus larges et plus anguleuses : le nouveau moteur trace ses contours sur une grille.
- Le nombre de
buckets, la durée et la distance sont limités : au-delà, la requête est refusée (400). Le nombre maximal debucketsest plus bas qu'auparavant. - Un
time_limitdécimal (120.5) est respecté, au lieu de valoir 600 secondes. result=pointlistetreverse_flowsont ignorés : la réponse contient toujours des polygones.- Un point hors des données couvertes reçoit une petite zone autour de lui, en
200, au lieu d'une erreur.
Matrice (directions/matrix)
- Les durées en voiture sont plus longues, pour la même raison que l'itinéraire ; les distances diffèrent case par case, le chemin emprunté n'étant pas toujours le même. À pied, les valeurs sont quasiment identiques.
weightsn'est qu'une approximation : la durée en secondes.- Sont désormais servis :
out_arraysabsent (qui donneweights),vehicleentouré d'espaces, et le profilbike. Un champ inconnu est ignoré au lieu d'être refusé. fail_fast: falseest accepté : une case sans itinéraire vaut alorsnull, au lieu de faire échouer toute la matrice (422).- Le nombre de couples origine × destination et la distance à vol d'oiseau entre deux points sont limités : au-delà, la matrice est refusée (
400). Un corps de requête trop volumineux est refusé (413), et une longitude hors de [−180, 180] aussi (400).
Les erreurs de l'ancienne API
Toutes les erreurs de /services sont au format du GeoService, application/problem+json (description du format), y compris celles des moteurs, traduites. Testez le membre code.
| HTTP | code | Cause | Que faire |
|---|---|---|---|
| 400 | invalid_parameter | Paramètre absent ou mal formé, profil inconnu, requête au-delà d'une limite | Corriger la requête : detail nomme le paramètre. |
| 404 | unknown_endpoint | Route inexistante sous /services | Corriger l'URL. |
| 405 | method_not_allowed | Méthode non acceptée, par exemple POST sur geocode | Utiliser la méthode de l'en-tête Allow. |
| 413 | payload_too_large | Corps de la matrice trop volumineux | Découper la matrice. |
| 422 | route_impossible | Point trop loin de toute voie ou hors des données, points non reliés ou trop éloignés, case de matrice sans itinéraire | Changer de point ou de profil. |
| 429 | — | Trop de requêtes en peu de temps. Réponse produite en amont du GeoService : ni corps Problem Details, ni en-têtes de dépréciation. | Ralentir, puis réessayer. |
| 500 | internal_error | Erreur inattendue du GeoService | Prévenir Galigeo. |
| 502 | upstream_invalid_response | Réponse du moteur inexploitable | Réessayer après un délai ; prévenir Galigeo si l'erreur persiste. |
| 503 | upstream_unavailable | Moteur momentanément injoignable | Réessayer après un délai. |
| 504 | upstream_timeout | Le moteur a mis trop de temps à répondre | Réessayer, ou alléger la requête. |
{
"type": "tag:galigeo.com,2026:geoservice:route_impossible",
"title": "Itinéraire impossible",
"status": 422,
"detail": "Un des points est trop loin de toute voie praticable.",
"instance": "/services/directions/route",
"code": "route_impossible"
}
Détecter la dépréciation dans vos réponses
Les réponses de /services, erreurs comprises, portent des en-têtes standard : Deprecation et Link (RFC 9745) pour la dépréciation, Sunset (RFC 8594) pour la date de retrait. Seule exception : le 429 de limitation du débit, produit en amont du GeoService, ne les porte pas.
| En-tête | Contenu |
|---|---|
Deprecation | La date de dépréciation, en secondes Unix précédées de @ : @1767225600 désignerait le 1er janvier 2026 à 0 h UTC. |
Link | Le lien vers le guide de migration : <https://…/migration>; rel="deprecation"; type="text/html". |
Sunset | La date de retrait, au format date HTTP : Thu, 01 Apr 2027 00:00:00 GMT. À partir de cet instant, /services ne répond plus. |
Faites-les remonter dans vos journaux ou votre supervision : vous saurez ainsi quelles parties de votre code appellent encore l'ancienne API. Ces en-têtes sont lisibles depuis un navigateur.
const res = await fetch(url);
if (res.headers.has("Deprecation") || res.headers.has("Sunset")) {
// Route dépréciée : à migrer vers /v2 avant la date de retrait
console.warn(`Route dépréciée : ${url}`, res.headers.get("Sunset"), res.headers.get("Link"));
}
import logging
import httpx
r = httpx.get(url)
if "Deprecation" in r.headers or "Sunset" in r.headers:
# Route dépréciée : à migrer vers /v2 avant la date de retrait
logging.warning(
"Route dépréciée : %s, retrait %s (%s)", url, r.headers.get("Sunset"), r.headers.get("Link")
)