Skip to content

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 control
  • name_consistent — whether the road name continues across the junction
  • name_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'