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,
"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 |
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}
}
}
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'