API Endpoints
All endpoints use POST with a JSON body unless noted. Responses follow the Valhalla JSON format.
POST /route
Compute a turn-by-turn route between two or more locations.
Request
{
"locations": [
{"lat": 48.8566, "lon": 2.3522},
{"lat": 48.8606, "lon": 2.3376}
],
"costing": "auto",
"costing_options": {
"auto": {
"use_highways": 0.8
}
},
"units": "kilometers",
"language": "en-US",
"generalize": 0,
"verbose": false,
"alternates": 0,
"id": "my-request"
}
| Field | Type | Default | Description |
|---|---|---|---|
locations |
array | required | Two or more {lat, lon} points |
costing |
string | "auto" |
Cost model: auto, bicycle, pedestrian |
costing_options |
object | — | Per-mode tuning (see Configuration) |
units |
string | "kilometers" |
kilometers or miles |
language |
string | "en-US" |
Narrative language (36+ supported) |
generalize |
number | — | Shape simplification. 0 = full detail |
verbose |
bool | false |
Include per-edge cost breakdowns |
alternates |
int | 0 |
Alternate routes to return alongside the best one, up to 3 |
id |
string | — | Echoed back in the response |
Response
{
"trip": {
"status": 0,
"status_message": "Found route between points",
"units": "kilometers",
"language": "en-US",
"locations": [
{"lat": 48.8566, "lon": 2.3522, "type": "break"},
{"lat": 48.8606, "lon": 2.3376, "type": "break"}
],
"legs": [
{
"summary": {"time": 180.5, "length": 1.42},
"shape": "_encoded_polyline6_",
"maneuvers": [
{
"type": 1,
"instruction": "Drive east on Rue de Rivoli.",
"time": 120.0,
"length": 0.95,
"begin_shape_index": 0,
"end_shape_index": 15,
"street_names": ["Rue de Rivoli"]
}
]
}
],
"summary": {"time": 180.5, "length": 1.42}
}
}
Alternate routes
With "alternates": n the response carries an alternates array beside trip,
each entry holding a trip of the same shape. They come back in increasing
order of cost, so an alternate is never cheaper than the main route - though it
may well be shorter or quicker, since cost weighs turns, gates and road class
alongside time.
{
"trip": { "summary": {"time": 810.0, "length": 8.198} },
"alternates": [
{"trip": { "summary": {"time": 751.7, "length": 5.847} }},
{"trip": { "summary": {"time": 794.6, "length": 7.329} }}
]
}
Fewer than n alternates come back when the graph offers nothing different
enough: a candidate is dropped if it retraces more than 60% of an accepted route
(75% for origin and destination 100 km or more apart), or if the stretch where
it diverges costs more than twice the stretch it replaces. A motorway run with
no parallel road returns no alternates at all. The field is omitted entirely
when alternates is absent or 0.
Verbose mode
When "verbose": true, each leg includes a debug_edges array with per-edge cost breakdowns:
{
"edge_id": "2/546123/1234",
"names": ["Avenue de la Liberté"],
"length_meters": 245,
"speed": 50,
"density": 8,
"road_class": "Primary",
"surface": "PavedSmooth",
"toll": false,
"dest_only": false,
"edge_cost": 12.4,
"edge_time": 11.2,
"transition_cost": 3.5,
"transition_time": 2.0,
"elapsed_cost": 45.8,
"elapsed_time": 38.1,
"path_distance": 520.0
}
POST /sources_to_targets
Compute a many-to-many distance/time matrix.
Request
{
"sources": [
{"lat": 48.856, "lon": 2.352},
{"lat": 48.870, "lon": 2.330}
],
"targets": [
{"lat": 48.860, "lon": 2.337},
{"lat": 48.845, "lon": 2.370}
],
"costing": "auto",
"units": "kilometers",
"verbose": false
}
| Field | Type | Default | Description |
|---|---|---|---|
sources |
array | required | Origin {lat, lon} points |
targets |
array | required | Destination {lat, lon} points |
costing |
string | "auto" |
Cost model |
costing_options |
object | — | Per-mode tuning |
units |
string | "kilometers" |
kilometers or miles |
verbose |
bool | false |
Include encoded polyline shapes per cell |
id |
string | — | Echoed back in the response |
Response
{
"sources_to_targets": [
[
{"from_index": 0, "to_index": 0, "distance": 1.42, "time": 180},
{"from_index": 0, "to_index": 1, "distance": 3.15, "time": 420}
],
[
{"from_index": 1, "to_index": 0, "distance": 2.10, "time": 250},
{"from_index": 1, "to_index": 1, "distance": 4.50, "time": 560}
]
],
"units": "kilometers"
}
When "verbose": true, each cell with a found route includes a "shape" field containing an encoded polyline6 of the path.
POST /debug/inspect
Inspect graph connectivity at a junction. Useful for debugging edge attributes, access flags, and connectivity.
Request
{"lat": 43.611, "lon": 3.862}
Or by edge ID:
{"edge_id": "2/751073/456"}
Response
{
"node_lat": 43.611034,
"node_lon": 3.861898,
"traffic_signal": false,
"drive_on_right": true,
"edges": [
{
"edge_id": "2/751073/456",
"names": ["Rue Gustave"],
"road_class": "Residential",
"speed": 30,
"length_meters": 85,
"forward_access": true,
"reverse_access": false,
"dest_only": false,
"not_thru": false,
"roundabout": false,
"restrictions": 0,
"endnode_lat": 43.6115,
"endnode_lon": 3.8625,
"shape": [[3.861898, 43.611034], [3.8625, 43.6115]]
}
]
}
POST /debug/cost
Show cost breakdown for all edges at a junction. Given an incoming edge, computes the transition cost to every outgoing edge — useful for understanding why the router prefers one turn over another.
Request
{
"from_edge_id": "2/751073/456",
"costing": "auto"
}
| Field | Type | Default | Description |
|---|---|---|---|
from_edge_id |
string | required | Edge ID in level/tileid/id format |
costing |
string | "auto" |
Cost model to use for evaluation |
Response
{
"from_edge": {
"edge_id": "2/751073/456",
"names": ["Rue Gustave"],
"opp_local_idx": 2,
"opp_index": 5,
"name_consistency_byte": "00110100",
"raw_name_consistency_byte": "01010010"
},
"node": {
"lat": 43.611034,
"lon": 3.861898,
"traffic_signal": false,
"drive_on_right": true,
"density": 8,
"local_edge_count": 4,
"transition_count": 0
},
"alternatives": [
{
"edge_id": "2/751073/460",
"names": ["Rue Gustave"],
"road_class": "Residential",
"length_meters": 120,
"speed": 30,
"forward_access": true,
"is_link": false,
"is_shortcut": false,
"internal": false,
"dest_only": false,
"not_thru": false,
"local_edge_idx": 1,
"allowed": true,
"edge_cost": 8.2,
"edge_secs": 7.5,
"transition_cost": 1.5,
"transition_secs": 0.5,
"total_cost": 9.7,
"from_heading": 180,
"to_heading": 175,
"turn_degree": 355,
"turn_type": "Straight",
"name_consistent": true,
"restriction_blocked": false,
"stopimpact": 2
}
]
}
Alternatives are sorted by total_cost ascending (cheapest first). Key fields for debugging:
turn_type— raw tile turn classification (Straight, SlightRight, Right, SharpRight, SlightLeft, Left, SharpLeft, Reverse)stopimpact— 0 (free flow) to 7 (full stop), approximates intersection controlname_consistent— whether the road name continues across the junctionname_consistency_byte— 8-bit mask showing name consistency for all edge pairs at this node
GET /status
Server health check. No request body.
Response
{
"version": "0.1.0",
"build_date": "2025-01-15",
"has_tiles": true,
"available_actions": ["route", "sources_to_targets", "inspect", "cost"],
"bbox": {
"min_lat": 43.0,
"min_lon": 3.0,
"max_lat": 44.0,
"max_lon": 4.0
}
}
Error handling
All errors return a Valhalla-compatible JSON error with an HTTP status code:
{
"error": "No path could be found for input",
"error_code": 442,
"status": "Bad Request",
"status_code": 400
}
| HTTP Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (missing fields, invalid edge_id, no path found) |
| 500 | Internal error (tile loading failure, task join error) |
Common error scenarios:
- No path found — origin and destination are not connected (e.g., on different islands)
- Could not load tile — tile directory doesn't contain tiles for the requested region
- No edge found near location — coordinates are too far from any road in the graph
Shape encoding
Route shapes use polyline6 encoding (6 decimal digits of precision). This is the same encoding used by Valhalla and Google Maps.
To decode a shape string, use any polyline6 decoder:
// Using @mapbox/polyline
const coords = polyline.decode(shape, 6);
import polyline
coords = polyline.decode(shape, 6)
curl -s http://localhost:8002/route -d '{
"locations": [{"lat": 43.7, "lon": 7.4}, {"lat": 43.73, "lon": 7.42}]
}' | jq '.trip.legs[0].shape'