{"openapi":"3.1.0","info":{"title":"AfriMap Engine API","version":"1.0.0","description":"Open-source mapping API for West Africa. Routing, geocoding, places, navigation, vector tiles and ETA — self-hosted from Abidjan, no third-party API fees."},"servers":[{"url":"https://api.afrimap.ci","description":"Production"},{"url":"https://sandbox.afrimap.ci","description":"Sandbox (afm_test_* keys, 10 000 req/day)"},{"url":"http://localhost:8090","description":"Local gateway"}],"tags":[{"name":"Routing","description":"Itineraries, matrices, isochrones, map matching and TSP.","x-portal-slug":"routing"},{"name":"Geocoding","description":"Forward, reverse and autocomplete geocoding.","x-portal-slug":"geocoding"},{"name":"Places","description":"Points of interest — search, nearby, by id.","x-portal-slug":"places"},{"name":"Navigation","description":"Turn-by-turn sessions, landmark enrichment and voice prompts.","x-portal-slug":"navigation"},{"name":"Tiles","description":"Vector tiles, styles, sprites and TileJSON.","x-portal-slug":"tiles"},{"name":"ETA","description":"Traffic-aware ETA predictions.","x-portal-slug":"eta"}],"paths":{"/v1/route":{"post":{"operationId":"routeCreate","tags":["Routing"],"summary":"Compute an itinerary (POST, JSON body)","description":"Returns one or more itineraries between two (or more) points with a precision-6 encoded polyline, three verbal instruction variants per maneuver (pre/alert/post), and — when traffic data is available — an ML-enhanced ETA alongside the raw Valhalla duration.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteRequest"},"example":{"origin":{"lat":5.3197,"lng":-4.0167},"destination":{"lat":5.348,"lng":-3.9904},"mode":"auto","language":"fr"}}}},"responses":{"200":{"description":"Route(s) computed.","content":{"application/json":{"schema":{"type":"object","properties":{"routes":{"type":"array","items":{"$ref":"#/components/schemas/Route"}}}}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key."},"429":{"description":"Rate limit exceeded."}},"x-portal-tryit":{"enabled":true,"bodyExample":{"origin":{"lat":5.3197,"lng":-4.0167},"destination":{"lat":5.348,"lng":-3.9904},"mode":"auto","language":"fr"}},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/route \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"origin\":      { \"lat\": 5.3197, \"lng\": -4.0167 },\n    \"destination\": { \"lat\": 5.3480, \"lng\": -3.9904 },\n    \"mode\":        \"auto\",\n    \"language\":    \"fr\"\n  }'"},{"lang":"typescript","label":"JavaScript","source":"import { AfriMapClient } from '@afrimap/sdk'\n\nconst client = new AfriMapClient({ apiKey: process.env.AFM_KEY! })\n\nconst { routes } = await client.routing.route({\n  origin:      { lat: 5.3197, lng: -4.0167 },\n  destination: { lat: 5.3480, lng: -3.9904 },\n  mode:        'auto',\n  language:    'fr',\n})\n\nconsole.log(routes[0].duration, routes[0].polyline)"},{"lang":"kotlin","label":"Kotlin","source":"import ci.afrimap.sdk.AfriMapClient\nimport ci.afrimap.sdk.core.LatLng\nimport ci.afrimap.sdk.routing.RouteRequest\n\nval client = AfriMapClient(apiKey = System.getenv(\"AFM_KEY\"))\n\nval response = client.routing.route(\n  RouteRequest(\n    origin      = LatLng(5.3197, -4.0167),\n    destination = LatLng(5.3480, -3.9904),\n    mode        = \"auto\",\n    language    = \"fr\",\n  )\n)\nprintln(response.routes.first().duration)"},{"lang":"swift","label":"Swift","source":"import AfriMapSDK\n\nlet client = AfriMapClient(apiKey: ProcessInfo.processInfo.environment[\"AFM_KEY\"]!)\n\nlet response = try await client.routing.route(\n  .init(\n    origin:      .init(lat: 5.3197, lng: -4.0167),\n    destination: .init(lat: 5.3480, lng: -3.9904),\n    mode:        \"auto\",\n    language:    \"fr\"\n  )\n)\nprint(response.routes.first?.duration ?? 0)"},{"lang":"go","label":"Go","source":"package main\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"fmt\"\n\tafrimap \"github.com/rellinxe/afri-map/sdks/go\"\n)\n\nfunc main() {\n\tclient := afrimap.New(os.Getenv(\"AFM_KEY\"))\n\tres, err := client.Routing.Route(context.Background(), &afrimap.RouteRequest{\n\t\tOrigin:      afrimap.LatLng{Lat: 5.3197, Lng: -4.0167},\n\t\tDestination: afrimap.LatLng{Lat: 5.3480, Lng: -3.9904},\n\t\tMode:        \"auto\",\n\t\tLanguage:    \"fr\",\n\t})\n\tif err != nil { panic(err) }\n\tfmt.Println(res.Routes[0].Duration)\n}"}]},"get":{"operationId":"routeGet","tags":["Routing"],"summary":"Compute an itinerary (GET, query string)","description":"Same semantics as POST /v1/route but with origin/destination passed as `lat,lng` pairs on the query string. Convenient for quick sanity checks from cURL; prefer POST for multi-stop or typed payloads.","parameters":[{"name":"origin","in":"query","required":true,"schema":{"type":"string","example":"5.3197,-4.0167"},"description":"`lat,lng` pair."},{"name":"destination","in":"query","required":true,"schema":{"type":"string","example":"5.3480,-3.9904"},"description":"`lat,lng` pair."},{"name":"mode","in":"query","schema":{"$ref":"#/components/schemas/TravelMode"}},{"name":"language","in":"query","schema":{"type":"string","default":"fr"}}],"responses":{"200":{"description":"Route computed.","content":{"application/json":{"schema":{"type":"object"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/route?origin=5.3197,-4.0167&destination=5.3480,-3.9904&mode=auto&language=fr\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const res = await fetch(\n  'https://api.afrimap.ci/v1/route?origin=5.3197,-4.0167&destination=5.3480,-3.9904&mode=auto&language=fr',\n  { headers: { 'X-AfriMap-Key': process.env.AFM_KEY! } },\n)\nconst { routes } = await res.json()"},{"lang":"kotlin","label":"Kotlin","source":"val url = \"https://api.afrimap.ci/v1/route\" +\n  \"?origin=5.3197,-4.0167\" +\n  \"&destination=5.3480,-3.9904\" +\n  \"&mode=auto&language=fr\"\n\nval client = OkHttpClient()\nval req = Request.Builder().url(url).addHeader(\"X-AfriMap-Key\", apiKey).build()\nclient.newCall(req).execute().use { println(it.body?.string()) }"},{"lang":"swift","label":"Swift","source":"var req = URLRequest(url: URL(string:\n  \"https://api.afrimap.ci/v1/route?origin=5.3197,-4.0167&destination=5.3480,-3.9904&mode=auto&language=fr\"\n)!)\nreq.setValue(apiKey, forHTTPHeaderField: \"X-AfriMap-Key\")\nlet (data, _) = try await URLSession.shared.data(for: req)"},{"lang":"go","label":"Go","source":"req, _ := http.NewRequest(\"GET\",\n\t\"https://api.afrimap.ci/v1/route?origin=5.3197,-4.0167&destination=5.3480,-3.9904&mode=auto&language=fr\",\n\tnil)\nreq.Header.Set(\"X-AfriMap-Key\", os.Getenv(\"AFM_KEY\"))\nres, err := http.DefaultClient.Do(req)"}]}},"/v1/matrix":{"post":{"operationId":"matrixCreate","tags":["Routing"],"summary":"Distance / duration matrix","description":"Computes a distance + duration matrix for up to 500 origin/destination pairs per call. Typical use: dispatching the nearest available driver across a fleet.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixRequest"},"example":{"sources":[{"lat":5.3197,"lng":-4.0167}],"targets":[{"lat":5.348,"lng":-3.9904},{"lat":5.3364,"lng":-4.0694}],"mode":"auto"}}}},"responses":{"200":{"description":"Matrix.","content":{"application/json":{"schema":{"type":"object"}}}}},"x-portal-tryit":{"enabled":true,"bodyExample":{"origins":[{"lat":5.3197,"lng":-4.0167}],"destinations":[{"lat":5.348,"lng":-3.9904},{"lat":5.3364,"lng":-4.0694}],"mode":"auto"}},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/matrix \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"origins\":      [{\"lat\":5.3197,\"lng\":-4.0167}],\n    \"destinations\":[{\"lat\":5.3480,\"lng\":-3.9904},{\"lat\":5.3364,\"lng\":-4.0694}],\n    \"mode\": \"auto\"\n  }'"},{"lang":"typescript","label":"JavaScript","source":"const { durations, distances } = await client.routing.matrix({\n  origins:      [{ lat: 5.3197, lng: -4.0167 }],\n  destinations: [\n    { lat: 5.3480, lng: -3.9904 },\n    { lat: 5.3364, lng: -4.0694 },\n  ],\n  mode: 'auto',\n})\n// durations[i][j] — driver i → rider j, in seconds"},{"lang":"kotlin","label":"Kotlin","source":"val result = client.routing.matrix(\n  MatrixRequest(\n    origins      = listOf(LatLng(5.3197, -4.0167)),\n    destinations = listOf(\n      LatLng(5.3480, -3.9904),\n      LatLng(5.3364, -4.0694),\n    ),\n    mode = \"auto\",\n  )\n)"},{"lang":"swift","label":"Swift","source":"let result = try await client.routing.matrix(.init(\n  origins:      [.init(lat: 5.3197, lng: -4.0167)],\n  destinations: [\n    .init(lat: 5.3480, lng: -3.9904),\n    .init(lat: 5.3364, lng: -4.0694),\n  ],\n  mode: \"auto\"\n))"},{"lang":"go","label":"Go","source":"res, err := client.Routing.Matrix(ctx, &afrimap.MatrixRequest{\n\tOrigins:      []afrimap.LatLng{{Lat: 5.3197, Lng: -4.0167}},\n\tDestinations: []afrimap.LatLng{\n\t\t{Lat: 5.3480, Lng: -3.9904},\n\t\t{Lat: 5.3364, Lng: -4.0694},\n\t},\n\tMode: \"auto\",\n})"}]}},"/v1/isochrone":{"get":{"operationId":"isochroneGet","tags":["Routing"],"summary":"Reachability polygon","description":"Returns a GeoJSON polygon of the area reachable within N minutes from a point. Supports driving, walking, and cycling costing profiles.","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","example":5.3197}},{"name":"lng","in":"query","required":true,"schema":{"type":"number","example":-4.0167}},{"name":"time","in":"query","required":true,"schema":{"type":"integer","example":15},"description":"Minutes."},{"name":"mode","in":"query","schema":{"$ref":"#/components/schemas/TravelMode"}}],"responses":{"200":{"description":"GeoJSON FeatureCollection with one Polygon feature."}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/isochrone?lat=5.3197&lng=-4.0167&time=15&mode=auto\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const geojson = await client.routing.isochrone({\n  lat: 5.3197, lng: -4.0167, time: 15, mode: 'auto',\n})\nmap.addSource('reach', { type: 'geojson', data: geojson })"},{"lang":"kotlin","label":"Kotlin","source":"val geojson = client.routing.isochrone(\n  IsochroneRequest(lat = 5.3197, lng = -4.0167, timeMinutes = 15, mode = \"auto\")\n)"},{"lang":"swift","label":"Swift","source":"let geojson = try await client.routing.isochrone(\n  .init(lat: 5.3197, lng: -4.0167, time: 15, mode: \"auto\")\n)"},{"lang":"go","label":"Go","source":"geojson, err := client.Routing.Isochrone(ctx, &afrimap.IsochroneRequest{\n\tLat:  5.3197,\n\tLng:  -4.0167,\n\tTime: 15,\n\tMode: \"auto\",\n})"}]}},"/v1/match":{"post":{"operationId":"mapMatch","tags":["Routing"],"summary":"Snap a GPS trace to the road graph","description":"Takes a raw GPS trace (noisy, possibly off-road) and returns the most likely sequence of road segments. Used post-trip for fare calculation and analytics.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"},"example":{"shape":[{"lat":5.3197,"lng":-4.0167,"time":1712000000},{"lat":5.3223,"lng":-4.0142,"time":1712000030},{"lat":5.326,"lng":-4.011,"time":1712000060}],"mode":"auto"}}}},"responses":{"200":{"description":"Matched trace."}},"x-portal-tryit":{"enabled":false},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/match \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"shape\": [\n      {\"lat\":5.3197,\"lng\":-4.0167,\"time\":1712000000},\n      {\"lat\":5.3223,\"lng\":-4.0142,\"time\":1712000030},\n      {\"lat\":5.3260,\"lng\":-4.0110,\"time\":1712000060}\n    ],\n    \"mode\": \"auto\"\n  }'"},{"lang":"typescript","label":"JavaScript","source":"const snapped = await client.routing.match({\n  shape: gpsTrace.map((p) => ({ lat: p.lat, lng: p.lng, time: p.ts })),\n  mode: 'auto',\n})"},{"lang":"kotlin","label":"Kotlin","source":"val snapped = client.routing.match(\n  MatchRequest(shape = gpsTrace, mode = \"auto\")\n)"},{"lang":"swift","label":"Swift","source":"let snapped = try await client.routing.match(\n  .init(shape: gpsTrace, mode: \"auto\")\n)"},{"lang":"go","label":"Go","source":"snapped, err := client.Routing.Match(ctx, &afrimap.MatchRequest{\n\tShape: trace,\n\tMode:  \"auto\",\n})"}]}},"/v1/optimize":{"post":{"operationId":"routeOptimize","tags":["Routing"],"summary":"Trip optimisation (TSP)","description":"Given an ordered list where the first point is origin, last is destination, and middle points are stops to visit in any order, returns the optimal stop sequence and the resulting route.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptimizeRequest"},"example":{"locations":[{"lat":5.3197,"lng":-4.0167},{"lat":5.33,"lng":-4.01},{"lat":5.348,"lng":-3.9904}],"mode":"auto"}}}},"responses":{"200":{"description":"Optimised trip."}},"x-portal-tryit":{"enabled":false},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/optimize \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"locations\": [\n      {\"lat\":5.3197,\"lng\":-4.0167},\n      {\"lat\":5.3480,\"lng\":-3.9904},\n      {\"lat\":5.3364,\"lng\":-4.0694},\n      {\"lat\":5.3197,\"lng\":-4.0167}\n    ],\n    \"mode\": \"auto\"\n  }'"},{"lang":"typescript","label":"JavaScript","source":"const { order, route } = await client.routing.optimize({\n  locations: [origin, ...stops, destination],\n  mode: 'auto',\n})\n// order[i] — index into your input.locations, in optimal visit order"},{"lang":"kotlin","label":"Kotlin","source":"val result = client.routing.optimize(\n  OptimizeRequest(locations = listOf(origin) + stops + destination, mode = \"auto\")\n)"},{"lang":"swift","label":"Swift","source":"let result = try await client.routing.optimize(\n  .init(locations: [origin] + stops + [destination], mode: \"auto\")\n)"},{"lang":"go","label":"Go","source":"res, err := client.Routing.Optimize(ctx, &afrimap.OptimizeRequest{\n\tLocations: append([]afrimap.LatLng{origin}, append(stops, destination)...),\n\tMode:      \"auto\",\n})"}]}},"/v1/geocode":{"get":{"operationId":"geocodeForward","tags":["Geocoding"],"summary":"Forward geocode","description":"Resolves a free-form text query (address, landmark, quartier, market name) to one or more GeoJSON Point features with a confidence score.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","example":"Cocody Abidjan"}},{"name":"lat","in":"query","schema":{"type":"number"},"description":"Proximity bias."},{"name":"lng","in":"query","schema":{"type":"number"},"description":"Proximity bias."},{"name":"size","in":"query","schema":{"type":"integer","default":10,"maximum":40}}],"responses":{"200":{"description":"GeoJSON FeatureCollection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/geocode?q=Cocody+Abidjan&size=5\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const fc = await client.geocoding.forward({ q: 'Cocody Abidjan', size: 5 })\nconst [lng, lat] = fc.features[0].geometry.coordinates\nconsole.log(fc.features[0].properties.label, lat, lng)"},{"lang":"kotlin","label":"Kotlin","source":"val fc = client.geocoding.forward(\n  GeocodeRequest(q = \"Cocody Abidjan\", size = 5)\n)\nval first = fc.features.first()\nprintln(\"${first.properties.label} ${first.geometry.coordinates}\")"},{"lang":"swift","label":"Swift","source":"let fc = try await client.geocoding.forward(\n  .init(q: \"Cocody Abidjan\", size: 5)\n)\nif let f = fc.features.first { print(f.properties.label) }"},{"lang":"go","label":"Go","source":"fc, err := client.Geocoding.Forward(ctx, &afrimap.GeocodeRequest{\n\tQ:    \"Cocody Abidjan\",\n\tSize: 5,\n})"}]}},"/v1/geocode/reverse":{"get":{"operationId":"geocodeReverse","tags":["Geocoding"],"summary":"Reverse geocode","description":"Returns the nearest addressable feature (street, landmark, quartier) for the given coordinates.","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","example":5.348}},{"name":"lng","in":"query","required":true,"schema":{"type":"number","example":-3.9904}}],"responses":{"200":{"description":"GeoJSON FeatureCollection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/geocode/reverse?lat=5.3480&lng=-3.9904\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const fc = await client.geocoding.reverse({ lat: 5.3480, lng: -3.9904 })\nconsole.log(fc.features[0]?.properties.label)"},{"lang":"kotlin","label":"Kotlin","source":"val fc = client.geocoding.reverse(\n  ReverseRequest(lat = 5.3480, lng = -3.9904)\n)"},{"lang":"swift","label":"Swift","source":"let fc = try await client.geocoding.reverse(\n  .init(lat: 5.3480, lng: -3.9904)\n)"},{"lang":"go","label":"Go","source":"fc, err := client.Geocoding.Reverse(ctx, &afrimap.ReverseRequest{\n\tLat: 5.3480,\n\tLng: -3.9904,\n})"}]}},"/v1/geocode/autocomplete":{"get":{"operationId":"geocodeAutocomplete","tags":["Geocoding"],"summary":"Autocomplete","description":"Low-latency prefix search biased toward the caller's location. Returns up to 5 GeoJSON Point candidates ranked by relevance.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","example":"coco"}},{"name":"lat","in":"query","schema":{"type":"number"}},{"name":"lng","in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"GeoJSON FeatureCollection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/geocode/autocomplete?q=coco&lat=5.32&lng=-4.02\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"// Throttle the input — the endpoint is tuned for <=3 QPS per user.\nconst suggest = debounce(async (q: string) => {\n  const fc = await client.geocoding.autocomplete({ q, lat: userLat, lng: userLng })\n  render(fc.features.slice(0, 5))\n}, 150)"},{"lang":"kotlin","label":"Kotlin","source":"val suggestions = client.geocoding.autocomplete(\n  AutocompleteRequest(q = \"coco\", lat = 5.32, lng = -4.02)\n)"},{"lang":"swift","label":"Swift","source":"let suggestions = try await client.geocoding.autocomplete(\n  .init(q: \"coco\", lat: 5.32, lng: -4.02)\n)"},{"lang":"go","label":"Go","source":"fc, err := client.Geocoding.Autocomplete(ctx, &afrimap.AutocompleteRequest{\n\tQ:   \"coco\",\n\tLat: 5.32,\n\tLng: -4.02,\n})"}]}},"/v1/places/search":{"get":{"operationId":"placesSearch","tags":["Places"],"summary":"Search POIs by text + optional location bias","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","example":"pharmacie"}},{"name":"lat","in":"query","schema":{"type":"number"}},{"name":"lng","in":"query","schema":{"type":"number"}},{"name":"radius","in":"query","schema":{"type":"integer","description":"Metres.","example":2000}}],"responses":{"200":{"description":"Matching places.","content":{"application/json":{"schema":{"type":"object","properties":{"places":{"type":"array","items":{"$ref":"#/components/schemas/Place"}}}}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/places/search?q=pharmacie&lat=5.3480&lng=-3.9904&radius=2000\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const { places } = await client.places.search({\n  q: 'pharmacie', lat: 5.3480, lng: -3.9904, radius: 2000,\n})\nplaces.forEach((p) => console.log(p.name, p.location))"},{"lang":"kotlin","label":"Kotlin","source":"val result = client.places.search(\n  PlaceSearchRequest(q = \"pharmacie\", lat = 5.3480, lng = -3.9904, radius = 2000)\n)\nresult.places.forEach { println(\"${it.name} @ ${it.location}\") }"},{"lang":"swift","label":"Swift","source":"let result = try await client.places.search(\n  .init(q: \"pharmacie\", lat: 5.3480, lng: -3.9904, radius: 2000)\n)"},{"lang":"go","label":"Go","source":"res, err := client.Places.Search(ctx, &afrimap.PlaceSearchRequest{\n\tQ: \"pharmacie\", Lat: 5.3480, Lng: -3.9904, Radius: 2000,\n})"}]}},"/v1/places/nearby":{"get":{"operationId":"placesNearby","tags":["Places"],"summary":"Places within a radius, optionally filtered by category","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","example":5.348}},{"name":"lng","in":"query","required":true,"schema":{"type":"number","example":-3.9904}},{"name":"radius","in":"query","schema":{"type":"integer","default":1000},"description":"Metres."},{"name":"category","in":"query","schema":{"type":"string","example":"restaurant"}}],"responses":{"200":{"description":"Matching places."}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/places/nearby?lat=5.3480&lng=-3.9904&radius=1000&category=restaurant\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const { places } = await client.places.nearby({\n  lat: 5.3480, lng: -3.9904, radius: 1000, category: 'restaurant',\n})"},{"lang":"kotlin","label":"Kotlin","source":"val nearby = client.places.nearby(\n  NearbyRequest(lat = 5.3480, lng = -3.9904, radius = 1000, category = \"restaurant\")\n)"},{"lang":"swift","label":"Swift","source":"let nearby = try await client.places.nearby(\n  .init(lat: 5.3480, lng: -3.9904, radius: 1000, category: \"restaurant\")\n)"},{"lang":"go","label":"Go","source":"res, err := client.Places.Nearby(ctx, &afrimap.NearbyRequest{\n\tLat: 5.3480, Lng: -3.9904, Radius: 1000, Category: \"restaurant\",\n})"}]}},"/v1/places/categories":{"get":{"operationId":"placesCategories","tags":["Places"],"summary":"List supported place categories","responses":{"200":{"description":"Flat array of category slugs."}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://api.afrimap.ci/v1/places/categories \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const categories = await client.places.categories()\n// ['restaurant', 'pharmacy', 'bank', 'school', ...]"},{"lang":"kotlin","label":"Kotlin","source":"val categories = client.places.categories()"},{"lang":"swift","label":"Swift","source":"let categories = try await client.places.categories()"},{"lang":"go","label":"Go","source":"categories, err := client.Places.Categories(ctx)"}]}},"/v1/places/{id}":{"get":{"operationId":"placesGet","tags":["Places"],"summary":"Fetch a single place by id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"e0e8a9e1-f7f5-45b4-afab-43f703fc4fd4"}}],"responses":{"200":{"description":"Place.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Place"}}}},"404":{"description":"Not found."}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://api.afrimap.ci/v1/places/place_01HZABC... \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const place = await client.places.get('place_01HZABC...')\nconsole.log(place.name, place.opening_hours)"},{"lang":"kotlin","label":"Kotlin","source":"val place = client.places.get(\"place_01HZABC...\")"},{"lang":"swift","label":"Swift","source":"let place = try await client.places.get(\"place_01HZABC...\")"},{"lang":"go","label":"Go","source":"place, err := client.Places.Get(ctx, \"place_01HZABC...\")"}]}},"/v1/navigate":{"post":{"operationId":"navigate","tags":["Navigation"],"summary":"Full turn-by-turn navigation session","description":"Bundles `/v1/route` + landmark enrichment + voice-ready instruction variants into a single call tuned for the client navigation state machine described in NAVIGATION.md §4.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NavigateRequest"},"example":{"origin":{"lat":5.3197,"lng":-4.0167},"destination":{"lat":5.348,"lng":-3.9904},"mode":"auto","language":"fr","enrich_landmarks":true}}}},"responses":{"200":{"description":"Navigation session payload."}},"x-portal-tryit":{"enabled":true,"bodyExample":{"origin":{"lat":5.3197,"lng":-4.0167},"destination":{"lat":5.348,"lng":-3.9904},"mode":"auto","language":"fr","enrich_landmarks":true}},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/navigate \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"origin\":      { \"lat\": 5.3197, \"lng\": -4.0167 },\n    \"destination\": { \"lat\": 5.3480, \"lng\": -3.9904 },\n    \"mode\": \"auto\", \"language\": \"fr\",\n    \"enrich_landmarks\": true\n  }'"},{"lang":"typescript","label":"JavaScript","source":"import { NavigationSession } from '@afrimap/sdk'\n\nconst session = new NavigationSession(client, { language: 'fr' })\nsession.addListener({ onVoicePrompt: (p) => speak(p.text) })\nconst route = await client.navigation.start({\n  origin, destination, mode: 'auto', language: 'fr', enrich_landmarks: true,\n})\nsession.start(route)\nnavigator.geolocation.watchPosition((pos) => session.onLocationUpdate(pos.coords))"},{"lang":"kotlin","label":"Kotlin","source":"val session = NavigationSession(client, language = \"fr\")\nval route = client.navigation.start(\n  NavigateRequest(origin, destination, mode = \"auto\", enrichLandmarks = true)\n)\nsession.start(route)\nlocationClient.onUpdate { fix -> session.onLocationUpdate(fix) }"},{"lang":"swift","label":"Swift","source":"let session = NavigationSession(client: client, language: \"fr\")\nlet route = try await client.navigation.start(\n  .init(origin: origin, destination: destination, mode: \"auto\", enrichLandmarks: true)\n)\nsession.start(route: route)"},{"lang":"go","label":"Go","source":"route, err := client.Navigation.Start(ctx, &afrimap.NavigateRequest{\n\tOrigin: origin, Destination: destination,\n\tMode: \"auto\", Language: \"fr\", EnrichLandmarks: true,\n})"}]}},"/v1/navigate/enrich":{"post":{"operationId":"navigateEnrich","tags":["Navigation"],"summary":"Attach landmark hints to existing maneuvers","description":"Takes a list of Valhalla maneuvers (each with `begin_shape_index`) and returns the same list enriched with nearby landmarks within 200 m of each turn point — e.g. \"Turn right at the orange mosque\".","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["maneuvers"],"properties":{"maneuvers":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Maneuver"}}}},"example":{"maneuvers":[{"instruction":"Tournez à droite sur le Boulevard Lagunaire","begin_shape_index":0,"lat":5.3197,"lng":-4.0167}]}}}},"responses":{"200":{"description":"Enriched maneuvers."}},"x-portal-tryit":{"enabled":false},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/navigate/enrich \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"maneuvers\": [...], \"polyline\": \"e|w_B}...\" }'"},{"lang":"typescript","label":"JavaScript","source":"const enriched = await client.navigation.enrich({\n  maneuvers: route.legs[0].maneuvers,\n  polyline: route.polyline,\n})"},{"lang":"kotlin","label":"Kotlin","source":"val enriched = client.navigation.enrich(\n  EnrichRequest(maneuvers = maneuvers, polyline = polyline)\n)"},{"lang":"swift","label":"Swift","source":"let enriched = try await client.navigation.enrich(\n  .init(maneuvers: maneuvers, polyline: polyline)\n)"},{"lang":"go","label":"Go","source":"enriched, err := client.Navigation.Enrich(ctx, &afrimap.EnrichRequest{\n\tManeuvers: maneuvers, Polyline: polyline,\n})"}]}},"/v1/navigate/voice":{"post":{"operationId":"navigateVoice","tags":["Navigation"],"summary":"Synthesize a single voice prompt","description":"Returns a pointer to a cached audio clip (MP3 or Opus) for the given text + language. French uses Edge TTS; Dioula/Baoulé use a fine-tuned Coqui XTTS model — see `/v1/navigate/languages` for production status.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceRequest"},"example":{"text":"Tournez à droite dans 200 mètres.","language":"fr"}}}},"responses":{"200":{"description":"Voice response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceResponse"}}}}},"x-portal-tryit":{"enabled":true,"bodyExample":{"text":"Tournez à droite dans 200 mètres.","language":"fr"}},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/navigate/voice \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"text\": \"Tournez à droite dans 200 mètres.\", \"language\": \"fr\" }'"},{"lang":"typescript","label":"JavaScript","source":"const { audio_url } = await client.navigation.voice({\n  text: 'Tournez à droite dans 200 mètres.',\n  language: 'fr',\n})\nnew Audio(audio_url).play()"},{"lang":"kotlin","label":"Kotlin","source":"val voice = client.navigation.voice(\n  VoiceRequest(text = \"Tournez à droite dans 200 mètres.\", language = \"fr\")\n)\nMediaPlayer().apply { setDataSource(voice.audioUrl); prepare(); start() }"},{"lang":"swift","label":"Swift","source":"let voice = try await client.navigation.voice(\n  .init(text: \"Tournez à droite dans 200 mètres.\", language: \"fr\")\n)\nlet player = AVPlayer(url: URL(string: voice.audioUrl)!)\nplayer.play()"},{"lang":"go","label":"Go","source":"voice, err := client.Navigation.Voice(ctx, &afrimap.VoiceRequest{\n\tText: \"Tournez à droite dans 200 mètres.\", Language: \"fr\",\n})"}]}},"/v1/navigate/voice/batch":{"post":{"operationId":"navigateVoiceBatch","tags":["Navigation"],"summary":"Pre-cache every voice prompt for a route","description":"Pre-warms the TTS cache for a whole navigation session in one call so offline navigation works through a network drop. Send every instruction the route will speak.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["instructions"],"properties":{"instructions":{"type":"array","minItems":1,"items":{"type":"string"}},"language":{"type":"string","enum":["fr"],"default":"fr"},"format":{"type":"string","enum":["mp3"],"default":"mp3"}}},"example":{"instructions":["Dans 200 mètres, tournez à droite","Tournez à droite maintenant","Continuez tout droit sur 1 kilomètre"],"language":"fr","format":"mp3"}}}},"responses":{"200":{"description":"Array of VoiceResponse in input order."}},"x-portal-tryit":{"enabled":false},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.afrimap.ci/v1/navigate/voice/batch \\\n  -H \"X-AfriMap-Key: $AFM_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"prompts\": [\n    { \"text\": \"Dans 200 mètres, tournez à droite.\", \"language\": \"fr\" },\n    { \"text\": \"Tournez à droite.\", \"language\": \"fr\" }\n  ]}'"},{"lang":"typescript","label":"JavaScript","source":"// Pre-cache every maneuver's prompt so offline navigation survives a drop.\nconst prompts = route.legs[0].maneuvers.flatMap((m) => [\n  { text: m.verbal_pre_transition_instruction,   language: 'fr' },\n  { text: m.verbal_transition_alert_instruction, language: 'fr' },\n])\nawait client.navigation.voiceBatch({ prompts })"},{"lang":"kotlin","label":"Kotlin","source":"val batch = route.legs.first().maneuvers.flatMap {\n  listOf(\n    VoiceRequest(it.verbalPreTransition,   \"fr\"),\n    VoiceRequest(it.verbalTransitionAlert, \"fr\"),\n  )\n}\nclient.navigation.voiceBatch(VoiceBatchRequest(batch))"},{"lang":"swift","label":"Swift","source":"let prompts = route.legs[0].maneuvers.flatMap { m in [\n  VoiceRequest(text: m.verbalPreTransition,   language: \"fr\"),\n  VoiceRequest(text: m.verbalTransitionAlert, language: \"fr\"),\n]}\ntry await client.navigation.voiceBatch(.init(prompts: prompts))"},{"lang":"go","label":"Go","source":"res, err := client.Navigation.VoiceBatch(ctx, &afrimap.VoiceBatchRequest{\n\tPrompts: prompts,\n})"}]}},"/v1/navigate/languages":{"get":{"operationId":"navigateLanguages","tags":["Navigation"],"summary":"Supported voice languages","responses":{"200":{"description":"Production + coming-soon language codes with backends.","content":{"application/json":{"schema":{"type":"object"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://api.afrimap.ci/v1/navigate/languages \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const { languages, details } = await client.navigation.languages()\n// details: [{ code, name, production, voice_backend, phrase_set? }]"},{"lang":"kotlin","label":"Kotlin","source":"val langs = client.navigation.languages()"},{"lang":"swift","label":"Swift","source":"let langs = try await client.navigation.languages()"},{"lang":"go","label":"Go","source":"langs, err := client.Navigation.Languages(ctx)"}]}},"/v1/navigate/audio/{hash}":{"get":{"operationId":"navigateAudio","tags":["Navigation"],"summary":"Stream a pre-synthesized audio clip","description":"Serves the cached audio blob referenced by `VoiceResponse.hash`. Supports HTTP `Range` for partial streaming.","parameters":[{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Audio blob (MP3 or Opus)."}},"x-portal-tryit":{"enabled":false}}},"/v1/tiles/{z}/{x}/{y}.pbf":{"get":{"operationId":"tileGet","tags":["Tiles"],"summary":"Vector tile (Mapbox Vector Tile, `application/x-protobuf`)","description":"Serves the AfriMap default tileset at the requested zoom/column/row. Compatible with MapLibre GL and any MVT consumer. See `tileStyle` for the matching style document.","parameters":[{"name":"z","in":"path","required":true,"schema":{"type":"integer","minimum":0,"maximum":16}},{"name":"x","in":"path","required":true,"schema":{"type":"integer"}},{"name":"y","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"MVT tile bytes."}},"x-portal-tryit":{"enabled":false},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -o tile.pbf \"https://api.afrimap.ci/v1/tiles/12/2048/1967.pbf\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"import maplibregl from 'maplibre-gl'\n\nnew maplibregl.Map({\n  container: 'map',\n  style: 'https://api.afrimap.ci/v1/tiles/style/day.json?key=' + encodeURIComponent(apiKey),\n  center: [-4.0167, 5.3197],\n  zoom: 12,\n})"},{"lang":"kotlin","label":"Kotlin","source":"MapView(context).apply {\n  getMapAsync { map ->\n    map.setStyle(\n      \"https://api.afrimap.ci/v1/tiles/style/day.json?key=$apiKey\"\n    )\n  }\n}"},{"lang":"swift","label":"Swift","source":"let url = URL(string:\n  \"https://api.afrimap.ci/v1/tiles/style/day.json?key=\\(apiKey)\"\n)!\nlet mapView = MLNMapView(frame: view.bounds, styleURL: url)"},{"lang":"go","label":"Go","source":"// Server-side pre-fetch for an MBTiles pack.\nres, err := http.Get(\"https://api.afrimap.ci/v1/tiles/12/2048/1967.pbf\")"}]}},"/v1/tiles/style/{name}":{"get":{"operationId":"tileStyle","tags":["Tiles"],"summary":"MapLibre style JSON","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","enum":["day.json","night.json","satellite.json"]}}],"responses":{"200":{"description":"MapLibre style document.","content":{"application/json":{"schema":{"type":"object"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://api.afrimap.ci/v1/tiles/style/day.json \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const style = await (await fetch(\n  'https://api.afrimap.ci/v1/tiles/style/day.json',\n  { headers: { 'X-AfriMap-Key': apiKey } },\n)).json()"},{"lang":"kotlin","label":"Kotlin","source":"// Pass the style URL directly to MapLibre — it'll fetch + parse for you.\nmap.setStyle(\"https://api.afrimap.ci/v1/tiles/style/day.json?key=$apiKey\")"},{"lang":"swift","label":"Swift","source":"let styleURL = URL(string:\n  \"https://api.afrimap.ci/v1/tiles/style/day.json?key=\\(apiKey)\"\n)!"},{"lang":"go","label":"Go","source":"res, err := http.Get(\"https://api.afrimap.ci/v1/tiles/style/day.json\")"}]}},"/v1/tiles/sprite/{name}":{"get":{"operationId":"tileSprite","tags":["Tiles"],"summary":"Sprite sheet (PNG or JSON index)","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","example":"afrimap@2x.json"}}],"responses":{"200":{"description":"Sprite PNG or index."}},"x-portal-tryit":{"enabled":false}}},"/v1/tiles/source/afrimap_tiles.json":{"get":{"operationId":"tileJson","tags":["Tiles"],"summary":"TileJSON descriptor","description":"TileJSON 3.0 document describing the default tileset — bounds, zoom range, attribution, vector_layers.","responses":{"200":{"description":"TileJSON document.","content":{"application/json":{"schema":{"type":"object"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl https://api.afrimap.ci/v1/tiles/source/afrimap_tiles.json \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const tilejson = await (await fetch(\n  'https://api.afrimap.ci/v1/tiles/source/afrimap_tiles.json',\n  { headers: { 'X-AfriMap-Key': apiKey } },\n)).json()\nconsole.log(tilejson.bounds, tilejson.vector_layers)"},{"lang":"kotlin","label":"Kotlin","source":"val res = client.newCall(\n  Request.Builder()\n    .url(\"https://api.afrimap.ci/v1/tiles/source/afrimap_tiles.json\")\n    .addHeader(\"X-AfriMap-Key\", apiKey)\n    .build()\n).execute()"},{"lang":"swift","label":"Swift","source":"let url = URL(string: \"https://api.afrimap.ci/v1/tiles/source/afrimap_tiles.json\")!\nvar req = URLRequest(url: url)\nreq.setValue(apiKey, forHTTPHeaderField: \"X-AfriMap-Key\")"},{"lang":"go","label":"Go","source":"req, _ := http.NewRequest(\"GET\",\n\t\"https://api.afrimap.ci/v1/tiles/source/afrimap_tiles.json\", nil)\nreq.Header.Set(\"X-AfriMap-Key\", apiKey)"}]}},"/v1/eta":{"get":{"operationId":"etaGet","tags":["ETA"],"summary":"Traffic-aware ETA for a single origin/destination pair","parameters":[{"name":"origin","in":"query","required":true,"schema":{"type":"string","example":"5.3197,-4.0167"}},{"name":"destination","in":"query","required":true,"schema":{"type":"string","example":"5.3480,-3.9904"}},{"name":"depart_at","in":"query","schema":{"type":"string","format":"date-time"},"description":"Optional future departure time (ISO 8601)."}],"responses":{"200":{"description":"Raw + ML-enhanced durations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EtaResponse"}}}}},"x-portal-tryit":{"enabled":true},"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl \"https://api.afrimap.ci/v1/eta?origin=5.3197,-4.0167&destination=5.3480,-3.9904\" \\\n  -H \"X-AfriMap-Key: $AFM_KEY\""},{"lang":"typescript","label":"JavaScript","source":"const eta = await client.eta.get({\n  origin:      { lat: 5.3197, lng: -4.0167 },\n  destination: { lat: 5.3480, lng: -3.9904 },\n})\nconsole.log(`ETA: ${Math.round(eta.duration_enhanced / 60)} min (confiance ${eta.confidence})`)"},{"lang":"kotlin","label":"Kotlin","source":"val eta = client.eta.get(\n  EtaRequest(origin = origin, destination = destination)\n)\nprintln(\"ETA: ${eta.durationEnhanced / 60} min\")"},{"lang":"swift","label":"Swift","source":"let eta = try await client.eta.get(\n  .init(origin: origin, destination: destination)\n)"},{"lang":"go","label":"Go","source":"eta, err := client.ETA.Get(ctx, &afrimap.EtaRequest{\n\tOrigin: origin, Destination: destination,\n})"}]}}},"components":{"schemas":{"LatLng":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number","format":"double","minimum":-90,"maximum":90,"example":5.3197},"lng":{"type":"number","format":"double","minimum":-180,"maximum":180,"example":-4.0167}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","example":400},"message":{"type":"string","example":"invalid lat/lng"},"details":{"type":"array","items":{"type":"object"}}}}}},"TravelMode":{"type":"string","enum":["auto","bicycle","pedestrian","motorcycle","bus","truck"],"default":"auto"},"Maneuver":{"type":"object","properties":{"instruction":{"type":"string","example":"Turn right onto Boulevard Lagunaire"},"verbal_pre_transition_instruction":{"type":"string"},"verbal_transition_alert_instruction":{"type":"string"},"verbal_post_transition_instruction":{"type":"string"},"distance":{"type":"number","description":"Kilometers until this maneuver"},"time":{"type":"number","description":"Seconds until this maneuver"},"begin_shape_index":{"type":"integer"},"end_shape_index":{"type":"integer"},"landmarks":{"type":"array","description":"Nearby landmarks (populated via /navigate/enrich).","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"distance_m":{"type":"number"}}}}}},"Route":{"type":"object","properties":{"distance":{"type":"number","description":"Kilometers"},"duration":{"type":"number","description":"Seconds (raw Valhalla)"},"eta_enhanced":{"type":"number","description":"Seconds after ML traffic adjustment"},"polyline":{"type":"string","description":"Encoded polyline (precision 6 — NOT precision 5). Decode with factor 1e-6."},"legs":{"type":"array","items":{"type":"object","properties":{"maneuvers":{"type":"array","items":{"$ref":"#/components/schemas/Maneuver"}}}}}}},"RouteRequest":{"type":"object","required":["origin","destination"],"properties":{"origin":{"$ref":"#/components/schemas/LatLng"},"destination":{"$ref":"#/components/schemas/LatLng"},"waypoints":{"type":"array","items":{"$ref":"#/components/schemas/LatLng"},"description":"Optional intermediate stops (multi-stop routing)."},"mode":{"$ref":"#/components/schemas/TravelMode"},"language":{"type":"string","default":"fr","example":"fr"},"alternatives":{"type":"integer","default":0,"minimum":0,"maximum":3}}},"MatrixRequest":{"type":"object","required":["sources","targets"],"properties":{"sources":{"type":"array","items":{"$ref":"#/components/schemas/LatLng"}},"targets":{"type":"array","items":{"$ref":"#/components/schemas/LatLng"}},"mode":{"$ref":"#/components/schemas/TravelMode"}}},"MatchRequest":{"type":"object","required":["shape"],"properties":{"shape":{"type":"array","description":"Raw GPS trace points to snap to the road graph.","items":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"},"time":{"type":"integer","description":"Unix epoch seconds"}}}},"mode":{"$ref":"#/components/schemas/TravelMode"}}},"OptimizeRequest":{"type":"object","required":["locations"],"properties":{"locations":{"type":"array","minItems":2,"items":{"$ref":"#/components/schemas/LatLng"},"description":"First point is origin; last is destination; middle points are re-ordered."},"mode":{"$ref":"#/components/schemas/TravelMode"}}},"GeocodeFeature":{"type":"object","properties":{"type":{"type":"string","enum":["Feature"]},"geometry":{"type":"object","properties":{"type":{"type":"string","enum":["Point"]},"coordinates":{"type":"array","items":{"type":"number"},"description":"[lng, lat]"}}},"properties":{"type":"object","properties":{"label":{"type":"string","example":"Cocody, Abidjan"},"locality":{"type":"string","example":"Abidjan"},"country":{"type":"string","example":"CI"},"confidence":{"type":"number","minimum":0,"maximum":1}}}}},"GeocodeResponse":{"type":"object","properties":{"type":{"type":"string","enum":["FeatureCollection"]},"features":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeFeature"}}}},"Place":{"type":"object","properties":{"id":{"type":"string","example":"place_01HZ..."},"name":{"type":"string","example":"Pharmacie du Plateau"},"category":{"type":"string","example":"pharmacy"},"location":{"$ref":"#/components/schemas/LatLng"},"address":{"type":"string"},"opening_hours":{"type":"string"},"phone":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}}},"NavigateRequest":{"type":"object","required":["origin","destination"],"properties":{"origin":{"$ref":"#/components/schemas/LatLng"},"destination":{"$ref":"#/components/schemas/LatLng"},"mode":{"$ref":"#/components/schemas/TravelMode"},"language":{"type":"string","default":"fr"},"enrich_landmarks":{"type":"boolean","default":true}}},"VoiceRequest":{"type":"object","required":["text","language"],"properties":{"text":{"type":"string","example":"Tournez à droite dans 200 mètres."},"language":{"type":"string","enum":["fr","fr-CI","en","dioula","baoule"],"description":"See GET /v1/navigate/languages for production status per code."},"voice":{"type":"string","description":"Optional voice identifier override."}}},"VoiceResponse":{"type":"object","properties":{"audio_url":{"type":"string","description":"Relative path under /v1/navigate/audio/{hash}"},"hash":{"type":"string"},"bytes":{"type":"integer"},"duration_ms":{"type":"integer"},"backend":{"type":"string","enum":["edge_tts","coqui_xtts_v2"]}}},"EtaResponse":{"type":"object","properties":{"duration_raw":{"type":"number","description":"Valhalla-reported seconds"},"duration_enhanced":{"type":"number","description":"ML-adjusted seconds"},"confidence":{"type":"number","minimum":0,"maximum":1},"model_version":{"type":"string","example":"eta-abidjan-v3"}}}},"securitySchemes":{"AfriMapKey":{"type":"apiKey","name":"X-AfriMap-Key","in":"header","description":"Per-account API key minted via `/v1/admin/keys`. `afm_live_*` for production, `afm_test_*` for sandbox (10 000 req/day, no billing)."}}},"security":[{"AfriMapKey":[]}]}