API · v1

Documentation de l'API

Générez des parcours de course et de trail depuis votre application : un point de départ, un objectif de distance ou de dénivelé, et vous recevez cinq parcours différents, prêts à afficher.

API · v1

API documentation

Generate running and trail routes from your app: a starting point, a distance or elevation-gain goal, and you get five different routes, ready to display.

Démarrage rapide

Toutes les requêtes se font en HTTPS vers {{BASE}}/v1, avec un corps JSON et votre clé dans l'en-tête X-API-Key. Exemple : cinq boucles de trail de 10 km au départ d'Angers.

curl -X POST {{BASE}}/v1/routes \
  -H "X-API-Key: dplus_votre_cle" \
  -H "Content-Type: application/json" \
  -d '{
    "lat": 47.4739,
    "lon": -0.5516,
    "radius_km": 5,
    "terrain": "trail",
    "mode": "distance",
    "target_distance_km": 10
  }'

Quick start

All requests go over HTTPS to {{BASE}}/v1, with a JSON body and your key in the X-API-Key header. Example: five 10 km trail loops starting from Angers.

curl -X POST {{BASE}}/v1/routes \
  -H "X-API-Key: dplus_votre_cle" \
  -H "Content-Type: application/json" \
  -d '{
    "lat": 47.4739,
    "lon": -0.5516,
    "radius_km": 5,
    "terrain": "trail",
    "mode": "distance",
    "target_distance_km": 10
  }'

Authentification

Chaque requête porte votre clé dans l'en-tête X-API-Key. Les clés commencent par dplus_ et vous sont transmises une seule fois, à la création. Demandez la vôtre en décrivant votre projet.

La clé reste sur votre serveur. Ne l'intégrez jamais dans une application mobile, une page web ou un dépôt de code public : toute personne qui la récupère consommerait votre quota. Votre application appelle votre serveur, qui appelle D+.

Authentication

Every request carries your key in the X-API-Key header. Keys start with dplus_ and are sent to you once, when created. Request yours by describing your project.

The key stays on your server. Never embed it in a mobile app, a web page or a public code repository: anyone who gets hold of it would use up your quota. Your app calls your server, which calls D+.

Quotas et débit

  • Deux quotas : les générations réussies de POST /v1/routes, et les conversations avec l'assistant (POST /v1/assistant), comptés séparément. Les compteurs repartent à zéro le 1er de chaque mois (UTC) ; pour un accès de démonstration, ils valent pour toute sa durée. Les fichiers GPX et la consultation de la consommation sont hors quota.
  • Débit : un nombre limité d'appels par minute et par clé. Au-delà, l'API répond 429 avec l'en-tête Retry-After.

Chaque réponse réussie indique où vous en êtes :

X-Quota-Limit: 1000
X-Quota-Remaining: 987

Quotas and rate limits

  • Two quotas: successful POST /v1/routes generations, and assistant conversations (POST /v1/assistant), counted separately. Counters reset on the 1st of each month (UTC); for a demo access, they cover its whole duration. GPX files and usage queries do not count.
  • Rate limit: a limited number of calls per minute and per key. Beyond that, the API returns 429 with a Retry-After header.

Every successful response tells you where you stand:

X-Quota-Limit: 1000
X-Quota-Remaining: 987
POST

/v1/routes

Calcule jusqu'à cinq parcours nettement différents, classés du meilleur au moins bon. Seuls lat, lon et radius_km sont obligatoires ; l'objectif dépend de mode. Le calcul prend en général de 1 à 20 secondes, jusqu'à 40 secondes pour plus de 100 km : prévoyez un délai d'attente d'au moins 120 secondes.

Paramètres principaux

ChampTypeDescription
lat, lonrequisnombrePoint de départ, ou centre de la zone de recherche si within_radius est vrai.
radius_kmrequis1 – 30Rayon de la zone dans laquelle le parcours reste.
terraintexteroad (bitume, par défaut), trail (chemins et sentiers) ou mixed (indifférent). C'est une préférence : le parcours en suit le plus possible.
modetexteObjectif du calcul, voir ci-dessous. Par défaut distance.
target_distance_km1 – 200Distance visée. Les parcours font entre cette distance et 500 m de plus.
target_elevation_m50 – 10 000Dénivelé positif visé, en mètres.
within_radiusbooléenfalse (défaut) : départ exactement à lat/lon. true : D+ choisit les meilleurs départs dans le rayon (lacs, parcs, reliefs).
preferencesobjetavoid_roads (limiter le bitume), near_water (bords de l'eau), marked_trails (sentiers balisés) : booléens.

Modes

modeChamps à fournirRésultat
distancetarget_distance_kmParcours à la distance visée.
elevationtarget_elevation_mLe parcours le plus court atteignant le dénivelé visé.
comboles deuxLa distance visée avec un dénivelé aussi proche que possible de la cible.
elevation_maxtarget_distance_kmLe plus de dénivelé possible sur la distance.

Si l'objectif est hors de portée dans la zone (par exemple 1 000 m de D+ en terrain plat), les parcours les plus proches sont tout de même renvoyés et le champ message commence par « ⚠ » avec l'explication.

Parcours avancés

ChampTypeDescription
shapetexteloop (boucle, défaut) ou one_way (aller simple).
end_lat, end_lonnombreAller simple : point d'arrivée.
direction_deg0 – 359Aller simple sans arrivée : cap à suivre (0 = nord, 90 = est).
stepsliste (8 max.)Étapes imposées, dans l'ordre. Chacune : action = point (passer par ce point), pass (passer à proximité), through (le traverser), inside (y rester, la distance se faisant à l'intérieur), tour (en faire le tour) ou laps (plusieurs tours, avec laps de 1 à 10), et lat/lon. Pour le tour d'un hameau ou d'un village, ajoutez radius_m (50 – 5 000).
variant0 – 100« Autres itinéraires » : numéro de la série demandée (0 = première).
exclude_routeslisteTracés déjà proposés ([[lon, lat], …]) : les nouveaux parcours s'en écartent.

Exemple : 15 km avec le plus de D+ possible et un passage imposé

{
  "lat": 47.4739, "lon": -0.5516, "radius_km": 8,
  "terrain": "trail",
  "mode": "elevation_max",
  "target_distance_km": 15,
  "steps": [
    { "action": "point", "lat": 47.4806, "lon": -0.5925, "name": "Étang Saint-Nicolas" }
  ],
  "preferences": { "near_water": true }
}
POST

/v1/routes

Computes up to five clearly different routes, ranked from best to worst. Only lat, lon and radius_km are required; the goal depends on mode. Computing usually takes 1 to 20 seconds, up to 40 seconds beyond 100 km: set a timeout of at least 120 seconds.

Main parameters

FieldTypeDescription
lat, lonrequirednumberStarting point, or centre of the search area when within_radius is true.
radius_kmrequired1 – 30Radius of the area the route stays within.
terrainstringroad (tarmac, default), trail (tracks and trails) or mixed (either). It is a preference: the route follows it as much as possible.
modestringGoal of the computation, see below. Defaults to distance.
target_distance_km1 – 200Target distance. Routes are between this distance and 500 m more.
target_elevation_m50 – 10 000Target elevation gain, in metres.
within_radiusbooleanfalse (default): start exactly at lat/lon. true: D+ picks the best starts within the radius (lakes, parks, hills).
preferencesobjectavoid_roads (less tarmac), near_water (along water), marked_trails (waymarked trails): booleans.

Modes

modeFields to provideResult
distancetarget_distance_kmRoutes at the target distance.
elevationtarget_elevation_mThe shortest route reaching the target elevation gain.
combobothThe target distance with an elevation gain as close as possible to the target.
elevation_maxtarget_distance_kmAs much elevation gain as possible over the distance.

If the goal is out of reach in the area (for example 1,000 m of elevation gain on flat ground), the closest routes are still returned and the message field starts with “⚠” and the explanation.

Advanced routes

FieldTypeDescription
shapestringloop (default) or one_way.
end_lat, end_lonnumberOne way: finish point.
direction_deg0 – 359One way without a finish: heading to follow (0 = north, 90 = east).
stepslist (8 max.)Required stops, in order. Each one: action = point (go through this point), pass (go nearby), through (go across it), inside (stay in it, covering the distance inside), tour (go around it) or laps (several laps, with laps from 1 to 10), and lat/lon. To go around a hamlet or village, add radius_m (50 – 5,000).
variant0 – 100“More routes”: number of the requested set (0 = first).
exclude_routeslistRoutes already shown ([[lon, lat], …]): new routes keep away from them.

Example: 15 km with as much climbing as possible and a required stop

{
  "lat": 47.4739, "lon": -0.5516, "radius_km": 8,
  "terrain": "trail",
  "mode": "elevation_max",
  "target_distance_km": 15,
  "steps": [
    { "action": "point", "lat": 47.4806, "lon": -0.5925, "name": "Étang Saint-Nicolas" }
  ],
  "preferences": { "near_water": true }
}

Format de la réponse

La réponse contient la liste routes (cinq au plus, parfois moins si la zone n'offre pas assez de parcours vraiment différents) et un message de synthèse.

{
  "routes": [
    {
      "rank": 1,
      "label": "Itinéraire 1 — Meilleur",
      "color": "#dc2626",
      "geojson": { "type": "FeatureCollection", "features": [
        { "type": "Feature", "geometry": { "type": "LineString",
          "coordinates": [[-0.551629, 47.473894, 22.1], …] } } ] },
      "stats": { "distance_km": 10.44, "elevation_gain_m": 81.9, "elevation_loss_m": 80.4 },
      "elevation_profile": [{ "distance_m": 0.0, "elevation_m": 22.1 }, …],
      "message": "Direction NE · 42 % chemins / sentiers · 36 % au bord de l'eau",
      "route_start_lat": 47.473894, "route_start_lon": -0.551629,
      "laps": [],
      "directions": {
        "forward": { "elevation_gain_m": 81.9, "max_grade_pct": 5.7, "longest_climb_m": 3240, "biggest_climb_m": 32.7, … },
        "reverse": { "elevation_gain_m": 75.4, "max_grade_pct": 6.8, … }
      },
      "one_way": false
    }
  ],
  "mode": "distance",
  "message": "…"
}
ChampDescription
geojsonTracé en GeoJSON. Coordonnées [longitude, latitude, altitude], dans le sens de course.
statsDistance (km), dénivelés positif et négatif (m), calculés sur le relief IGN.
elevation_profileProfil d'altitude : distance depuis le départ et altitude, en mètres.
lapsSites parcourus plusieurs fois : name, count, label (« 2 tours ») et position pour l'afficher sur la carte.
directionsMontées dans le sens proposé (forward) et en sens inverse (reverse) : pente maximale sur 200 m, plus longue montée, plus grosse montée.
messageRésumé lisible : direction, sites traversés, part de chemins, de bords de l'eau, de sentiers balisés.

Response format

The response contains the routes list (five at most, sometimes fewer when the area does not offer enough truly different routes) and a summary message.

{
  "routes": [
    {
      "rank": 1,
      "label": "Route 1 — Best",
      "color": "#dc2626",
      "geojson": { "type": "FeatureCollection", "features": [
        { "type": "Feature", "geometry": { "type": "LineString",
          "coordinates": [[-0.551629, 47.473894, 22.1], …] } } ] },
      "stats": { "distance_km": 10.44, "elevation_gain_m": 81.9, "elevation_loss_m": 80.4 },
      "elevation_profile": [{ "distance_m": 0.0, "elevation_m": 22.1 }, …],
      "message": "Heading NE · 42% paths and trails · 36% along water",
      "route_start_lat": 47.473894, "route_start_lon": -0.551629,
      "laps": [],
      "directions": {
        "forward": { "elevation_gain_m": 81.9, "max_grade_pct": 5.7, "longest_climb_m": 3240, "biggest_climb_m": 32.7, … },
        "reverse": { "elevation_gain_m": 75.4, "max_grade_pct": 6.8, … }
      },
      "one_way": false
    }
  ],
  "mode": "distance",
  "message": "…"
}
FieldDescription
geojsonRoute as GeoJSON. Coordinates [longitude, latitude, elevation], in running order.
statsDistance (km), elevation gain and loss (m), computed on IGN terrain data.
elevation_profileElevation profile: distance from the start and elevation, in metres.
lapsPlaces covered several times: name, count, label (“2 laps”) and a position to show it on the map.
directionsClimbs in the suggested direction (forward) and the other way (reverse): steepest grade over 200 m, longest climb, biggest climb.
messageReadable summary: heading, places crossed, share of paths, water edges and waymarked trails.
POST

/v1/gpx

Transforme un tracé en fichier GPX 1.1, à importer dans une montre ou une application de course. Hors quota.

curl -X POST {{BASE}}/v1/gpx \
  -H "X-API-Key: dplus_votre_cle" -H "Content-Type: application/json" \
  -d '{"name": "Boucle du lac", "coordinates": [[-0.5516, 47.4739, 22.1], [-0.5525, 47.4737, 20.7]]}' \
  -o parcours.gpx

coordinates reprend directement geojson.features[0].geometry.coordinates (2 à 50 000 points). name (120 caractères) et description (500 caractères) sont facultatifs.

POST

/v1/gpx

Turns a route into a GPX 1.1 file, to import into a watch or running app. Does not count towards the quota.

curl -X POST {{BASE}}/v1/gpx \
  -H "X-API-Key: dplus_votre_cle" -H "Content-Type: application/json" \
  -d '{"name": "Lake loop", "coordinates": [[-0.5516, 47.4739, 22.1], [-0.5525, 47.4737, 20.7]]}' \
  -o route.gpx

coordinates is simply geojson.features[0].geometry.coordinates (2 to 50,000 points). name (120 characters) and description (500 characters) are optional.

POST

/v1/assistant

Un message en langage naturel à l'assistant IA, qui le traduit en parcours calculés sur la carte. Disponible si l'assistant est inclus dans votre accès ; chaque conversation compte une fois dans votre quota, à son premier message abouti ; les messages suivants de la même conversation (session_id) sont inclus, dans la limite de 12.

  • Premier message : message, context (mêmes champs que /v1/routes : départ, rayon, terrain, objectif éventuel) et place (nom du départ).
  • Messages suivants : message et le session_id de la réponse précédente (« plus court », « moins de route »). Une conversation expire après 2 heures sans message.
  • Réponse : soit des parcours dans routes (même format que /v1/routes) avec un texte reply, soit une question à poser à votre utilisateur, avec des options de réponse.
curl -X POST {{BASE}}/v1/assistant \
  -H "X-API-Key: dplus_votre_cle" -H "Content-Type: application/json" \
  -d '{
    "message": "Deux fois le tour du lac de Maine",
    "place": "Angers",
    "context": { "lat": 47.4739, "lon": -0.5516, "radius_km": 8, "terrain": "trail",
                 "mode": "distance", "target_distance_km": 12 }
  }'
{
  "session_id": "9f2c…",
  "reply": "Voici deux tours du lac de Maine…",
  "question": null,
  "options": [],
  "routes": { "routes": [ … ] }
}

Les en-têtes X-Assistant-Quota-Limit et X-Assistant-Quota-Remaining indiquent où vous en êtes. Prévoyez un délai d'attente de 180 secondes : l'assistant enchaîne plusieurs calculs.

POST

/v1/assistant

A plain-language message to the AI assistant, which turns it into routes computed on the map. Available when the assistant is included in your access; each conversation counts once towards your quota, on its first successful message; follow-up messages in the same conversation (session_id) are included, up to 12.

  • First message: message, context (same fields as /v1/routes: start, radius, terrain, optional goal) and place (name of the start).
  • Follow-up messages: message and the session_id from the previous response (“shorter”, “less road”). A conversation expires after 2 hours without a message.
  • Response: either routes in routes (same format as /v1/routes) with a reply text, or a question to ask your user, with answer options.
curl -X POST {{BASE}}/v1/assistant \
  -H "X-API-Key: dplus_votre_cle" -H "Content-Type: application/json" \
  -d '{
    "message": "Twice around Lac de Maine",
    "place": "Angers",
    "context": { "lat": 47.4739, "lon": -0.5516, "radius_km": 8, "terrain": "trail",
                 "mode": "distance", "target_distance_km": 12 }
  }'
{
  "session_id": "9f2c…",
  "reply": "Here are two laps of Lac de Maine…",
  "question": null,
  "options": [],
  "routes": { "routes": [ … ] }
}

The X-Assistant-Quota-Limit and X-Assistant-Quota-Remaining headers tell you where you stand. Set a timeout of 180 seconds: the assistant chains several computations. Send Accept-Language: en to get answers in English.

GET

/v1/usage

Vos deux quotas, ce qu'il en reste (routes et assistant), la date de fin d'un accès de démonstration (expires_on) et le détail par jour sur les 31 derniers jours.

{
  "client": "Mon application",
  "monthly_quota": 1000,
  "used_this_month": 13,
  "remaining": 987,
  "daily": [ … ]
}
GET

/v1/usage

Your two quotas, what is left of them (routes and assistant), the end date of a demo access (expires_on) and a daily breakdown over the last 31 days.

{
  "client": "My app",
  "monthly_quota": 1000,
  "used_this_month": 13,
  "remaining": 987,
  "daily": [ … ]
}

Erreurs

En cas d'erreur, le corps JSON contient un champ detail qui l'explique (en anglais avec l'en-tête Accept-Language: en) ; pour les codes 422, il peut être affiché tel quel à vos utilisateurs.

CodeSignificationQue faire
401Clé absente ou inconnue.Vérifiez l'en-tête X-API-Key.
403Clé désactivée ou arrivée à sa date de fin, ou assistant non inclus.Contactez-nous.
422Paramètres invalides, lieu hors couverture ou objectif irréalisable.Lisez detail ; élargissez le rayon ou changez d'objectif.
429Débit par minute dépassé, ou quota mensuel atteint.Respectez Retry-After ; pour le quota, contactez-nous.
500Erreur interne.Réessayez plus tard ; si elle persiste, signalez-la.
503Moteur de calcul momentanément indisponible.Réessayez après quelques secondes.

Les générations en erreur ne sont pas décomptées du quota.

Errors

On error, the JSON body contains a detail field explaining it (in English with the Accept-Language: en header); for 422 codes you can show it to your users as is.

CodeMeaningWhat to do
401Missing or unknown key.Check the X-API-Key header.
403Key disabled or past its end date, or assistant not included.Contact us.
422Invalid parameters, place outside coverage or unreachable goal.Read detail; widen the radius or change the goal.
429Per-minute rate exceeded, or monthly quota reached.Honour Retry-After; for the quota, contact us.
500Internal error.Try again later; if it persists, report it.
503Routing engine temporarily unavailable.Try again after a few seconds.

Failed generations do not count towards the quota.

Couverture et limites

  • Zone couverte : France métropolitaine. Un point hors de la zone renvoie une erreur 422 explicite.
  • Distance : de 1 à 200 km ; dénivelé visé : de 50 à 10 000 m ; rayon : de 1 à 30 km.
  • Terrain : une préférence, pas un filtre. Un parcours « route » en pleine forêt ou « trail » en centre-ville emprunte le meilleur réseau disponible ; le message indique la part de chemins obtenue.
  • Dénivelé : calculé sur le modèle de relief de l'IGN. Il peut différer de 10 à 20 % de celui mesuré par une montre à altimètre barométrique.
  • Langue : messages et erreurs en français par défaut, en anglais avec l'en-tête Accept-Language: en.

Coverage and limits

  • Coverage: mainland France. A point outside the area returns an explicit 422 error.
  • Distance: 1 to 200 km; target elevation gain: 50 to 10,000 m; radius: 1 to 30 km.
  • Terrain: a preference, not a filter. A “road” route deep in a forest or a “trail” route in a city centre uses the best network available; message gives the resulting share of paths.
  • Elevation gain: computed on the IGN terrain model. It may differ by 10 to 20% from what a watch with a barometric altimeter measures.
  • Language: messages and errors are in French by default, in English with the Accept-Language: en header.

Bonnes pratiques

  • Appelez l'API depuis votre serveur, jamais directement depuis l'application de vos utilisateurs.
  • Gardez en cache les parcours déjà calculés pour un même départ et un même objectif : ils ne changent pas d'un appel à l'autre.
  • Pour proposer « d'autres parcours », renvoyez la même requête avec variant incrémenté et les tracés déjà affichés dans exclude_routes.
  • Affichez l'attribution « © contributeurs OpenStreetMap » à côté des parcours (licence ODbL des données cartographiques).
  • Le schéma complet est disponible au format OpenAPI : documentation interactive.

Prêt à intégrer D+ ?

Décrivez votre projet, nous vous envoyons une clé de test.

Demander une clé

Best practices

  • Call the API from your server, never directly from your users' app.
  • Cache routes already computed for the same start and goal: they do not change from one call to the next.
  • To offer “more routes”, send the same request again with variant incremented and the routes already shown in exclude_routes.
  • Show the “© OpenStreetMap contributors” attribution next to the routes (ODbL licence of the map data).
  • The full schema is available in OpenAPI format: interactive documentation.

Ready to integrate D+?

Describe your project and we will send you a test key.

Request a key