Route planner
Assigns jobs and pickup-delivery shipments to agents and returns an optimized, routed plan for each agent.
Signed in? Open this endpoint in the dashboard playground, which uses your organization automatically. Signed out, use Try in Playground below with your API key.
apiKey<token>Your API key from the dashboard, sent as ?apiKey=<key>.
Route optimization task. The request body must be JSON.
application/json- body
mode?stringTravel profile used for all agent routes. drive represents passenger cars. transit and approximated_transit are not supported (no transit data).
"walk""hike""scooter""motorcycle""drive""light_truck""bicycle""mountain_bike""road_bike""bus""medium_truck""truck""truck_dangerous_goods""heavy_truck""long_truck"agents?array<|||>Resources, such as vehicles or workers, to which jobs and shipments can be assigned.
Deviation (approximated): an agent with several time_windows is solved against their hull (earliest start to latest end); gaps between windows are not enforced. An agent without delivery_capacity/pickup_capacity has effectively unlimited capacity.
1 <= items <= 100jobs*unknown1 <= itemsshipments?array<>Paired pickup-delivery tasks. Pickup always precedes delivery and both are assigned to the same agent.
items <= 1000locations?array<>Reusable coordinates referenced by zero-based location_index fields. Regular calls may contain at most 300 unique coordinates across the complete task. The gateway rejects a task with more than 300 unique coordinates (agents, jobs, shipments and locations together) with 400; 300 x 300 = 90,000 matrix pairs is the Valhalla limit.
items <= 1000avoid?array<>Road features, locations, polygons, or countries the calculated agent routes should avoid.
Deviation (approximated): tolls, highways (motorized modes only) and ferries are applied as soft Valhalla preferences scaled by importance; locations and polygons are hard exclusions; green_zones, transfers and country_codes are accepted but have no effect. Rule values are {lat, lon} objects (locations) and arrays of {lat, lon} objects (polygons).
items <= 100traffic?stringTraffic model for motorized modes. free_flow assumes uncongested roads; approximated accounts for likely congestion.
Deviation (ignored): Accepted and echoed in properties.params.traffic, but routes are always computed free-flow; there is no congestion model.
"free_flow""free_flow""approximated"type?stringRoute preference used for agent travel. balanced balances time and distance; short prioritizes distance; less_maneuvers penalizes turns.
less_maneuvers is only supported for motorized modes; other modes return 400.
"balanced""balanced""short""less_maneuvers"max_speed?numberMaximum speed for driving, truck, and bus modes. Use 10 to 252 km/h with units=metric, or 6.5 to 155 mph with units=imperial.
Deviation (approximated): Applied only to motorized modes (drive, motorcycle, scooter, bus, truck modes); ignored for walk, hike and bicycle modes. The gateway enforces 10-252 (metric) or 6.5-155 (imperial) and returns 400 outside that range.
6.5 <= value <= 252units?stringUnit system for returned route distances. metric returns meters; imperial returns miles.
"metric""metric""imperial"Optimized plan. Each GeoJSON feature is one assigned agent route; collection properties contain the normalized task parameters and any assignment issues. Distances are meters (units=metric) or miles (units=imperial); times are seconds.
application/json- response
type*unknownfeatures*array<>bbox?array<number>4 <= items <= 6properties*curl -X POST 'https://api.data.graphitelab.ai/v1/routeplanner' \ -H 'Content-Type: application/json' \ -d '{ "mode": "drive", "agents": [ { "id": "courier-1", "start_location": [ -86.1581, 39.7684 ] } ], "jobs": [ { "id": "job-a", "location": [ -86.1478, 39.7653 ], "duration": 300 }, { "id": "job-b", "location": [ -86.1805, 39.7791 ], "duration": 300 }, { "id": "job-c", "location": [ -86.1349, 39.7882 ], "duration": 300 } ]}'{ "type": "FeatureCollection", "properties": { "mode": "drive", "params": { "mode": "drive", "type": "balanced", "traffic": "free_flow", "units": "metric", "agents": [ { "id": "courier-1", "start_location": [ -86.1581, 39.7684 ] } ], "jobs": [ { "id": "job-a", "location": [ -86.1478, 39.7653 ], "duration": 300 }, { "id": "job-b", "location": [ -86.1805, 39.7791 ], "duration": 300 }, { "id": "job-c", "location": [ -86.1349, 39.7882 ], "duration": 300 } ] } }, "features": [ { "type": "Feature", "geometry": { "type": "MultiLineString", "coordinates": [ [ [ -86.158241, 39.768017 ], [ -86.158201, 39.768008 ], [ -86.15813, 39.767998 ], [ -86.1478, 39.765127 ] ], [ [ -86.1478, 39.765127 ], [ -86.148043, 39.765127 ], [ -86.148252, 39.765115 ], [ -86.134927, 39.788128 ] ], [ [ -86.134927, 39.788128 ], [ -86.134927, 39.788128 ], [ -86.13534, 39.788126 ], [ -86.180569, 39.7791 ] ], [ [ -86.180569, 39.7791 ], [ -86.180571, 39.778877 ], [ -86.180326, 39.778879 ], [ -86.158241, 39.768017 ] ] ] }, "bbox": [ -86.181421, 39.764552, -86.134927, 39.788521 ], "properties": { "agent_index": 0, "agent_id": "courier-1", "time": 1960, "start_time": 0, "end_time": 1960, "distance": 13437, "legs": [ { "time": 213, "distance": 1547, "from_waypoint_index": 0, "to_waypoint_index": 1, "steps": [ { "from_index": 0, "to_index": 1, "time": 213, "distance": 1547 } ] }, { "time": 429, "distance": 4047, "from_waypoint_index": 1, "to_waypoint_index": 2, "steps": [ { "from_index": 1, "to_index": 2, "time": 429, "distance": 4047 } ] }, { "time": 418, "distance": 4996, "from_waypoint_index": 2, "to_waypoint_index": 3, "steps": [ { "from_index": 2, "to_index": 3, "time": 418, "distance": 4996 } ] }, { "time": 297, "distance": 2847, "from_waypoint_index": 3, "to_waypoint_index": 4, "steps": [ { "from_index": 3, "to_index": 4, "time": 297, "distance": 2847 } ] } ], "mode": "drive", "actions": [ { "index": 0, "type": "start", "start_time": 0, "duration": 0, "waypoint_index": 0 }, { "index": 1, "type": "job", "start_time": 213, "duration": 300, "waypoint_index": 1, "job_index": 0, "job_id": "job-a" }, { "index": 2, "type": "job", "start_time": 942, "duration": 300, "waypoint_index": 2, "job_index": 2, "job_id": "job-c" }, { "index": 3, "type": "job", "start_time": 1660, "duration": 300, "waypoint_index": 3, "job_index": 1, "job_id": "job-b" }, { "index": 4, "type": "end", "start_time": 1960, "duration": 0, "waypoint_index": 4 } ], "waypoints": [ { "next_leg_index": 0, "original_location": [ -86.1581, 39.7684 ], "location": [ -86.1581, 39.7684 ], "start_time": 0, "duration": 0, "actions": [ { "index": 0, "type": "start", "start_time": 0, "duration": 0, "waypoint_index": 0 } ] }, { "next_leg_index": 1, "prev_leg_index": 0, "original_location": [ -86.1478, 39.7653 ], "location": [ -86.1478, 39.7653 ], "start_time": 213, "duration": 300, "actions": [ { "index": 1, "type": "job", "start_time": 213, "duration": 300, "waypoint_index": 1, "job_index": 0, "job_id": "job-a" } ] }, { "next_leg_index": 2, "prev_leg_index": 1, "original_location": [ -86.1349, 39.7882 ], "location": [ -86.1349, 39.7882 ], "start_time": 942, "duration": 300, "actions": [ { "index": 2, "type": "job", "start_time": 942, "duration": 300, "waypoint_index": 2, "job_index": 2, "job_id": "job-c" } ] }, { "next_leg_index": 3, "prev_leg_index": 2, "original_location": [ -86.1805, 39.7791 ], "location": [ -86.1805, 39.7791 ], "start_time": 1660, "duration": 300, "actions": [ { "index": 3, "type": "job", "start_time": 1660, "duration": 300, "waypoint_index": 3, "job_index": 1, "job_id": "job-b" } ] }, { "prev_leg_index": 3, "original_location": [ -86.1581, 39.7684 ], "location": [ -86.1581, 39.7684 ], "start_time": 1960, "duration": 0, "actions": [ { "index": 4, "type": "end", "start_time": 1960, "duration": 0, "waypoint_index": 4 } ] } ] } } ]}Full schema: API reference
POST /v1/routeplanner takes a set of agents (vehicles or workers) and a set of jobs or shipments, decides who does what and in which order, and returns one GeoJSON feature per working agent with actions, timing, distance and route geometry. Use it for courier runs, service rounds and pickup-delivery planning.
How it works
- The gateway validates the JSON body against the spec, then collects every unique coordinate (locations, agents, jobs, shipments) into one list.
- It asks Valhalla for a time and distance matrix over those coordinates, honoring
mode,type,avoidandmax_speed. - It hands that matrix to the VROOM optimizer as custom matrices. VROOM never calls Valhalla itself, because it cannot pass
avoid,typeormax_speed. - For each agent that got work, the gateway asks Valhalla for the legs and geometry of the optimized stop order.
Road data comes from OpenStreetMap through Valhalla tiles. No geocoding or address data is involved: coordinates go in, so there is no place_id or datasource in the response.
Quick start
curl -s -X POST -H "Content-Type: application/json" "https://api.data.graphitelab.ai/v1/routeplanner?apiKey=$GRAPHITE_API_KEY" -d '{"mode":"drive","agents":[{"id":"courier-1","start_location":[-86.1581,39.7684]}],"jobs":[{"id":"job-a","location":[-86.1478,39.7653],"duration":300},{"id":"job-b","location":[-86.1805,39.7791],"duration":300},{"id":"job-c","location":[-86.1349,39.7882],"duration":300}]}'Response (geometry, params and waypoints trimmed):
{
"type": "FeatureCollection",
"properties": { "mode": "drive" },
"features": [
{
"type": "Feature",
"geometry": { "type": "MultiLineString", "coordinates": [ "..." ] },
"properties": {
"agent_index": 0,
"agent_id": "courier-1",
"time": 1960,
"start_time": 0,
"end_time": 1960,
"distance": 13437,
"mode": "drive",
"actions": [
{ "index": 0, "type": "start", "start_time": 0, "duration": 0, "waypoint_index": 0 },
{ "index": 1, "type": "job", "start_time": 213, "duration": 300, "waypoint_index": 1, "job_index": 0, "job_id": "job-a" },
{ "index": 2, "type": "job", "start_time": 942, "duration": 300, "waypoint_index": 2, "job_index": 2, "job_id": "job-c" },
{ "index": 3, "type": "job", "start_time": 1660, "duration": 300, "waypoint_index": 3, "job_index": 1, "job_id": "job-b" },
{ "index": 4, "type": "end", "start_time": 1960, "duration": 0, "waypoint_index": 4 }
],
"legs": [
{ "time": 213, "distance": 1547, "from_waypoint_index": 0, "to_waypoint_index": 1 }
]
}
}
]
}The optimizer reordered the jobs: job-c is visited before job-b.
Parameters
JSON body. Coordinates are [longitude, latitude].
| Name | Type | Required | What it does |
|---|---|---|---|
mode | string | yes | Travel profile for all agents, for example drive, truck, bicycle, walk. |
agents | array | yes | 1 to 100 vehicles or workers. Each needs a start_location or start_location_index (or an end location). Optional end_location, capacities, capabilities, time_windows, breaks. |
jobs | array | one of jobs or shipments | Up to 1000 independent tasks with location or location_index, duration (seconds), priority (0 to 100), amounts, requirements, time_windows. |
shipments | array | one of jobs or shipments | Up to 1000 paired pickup and delivery tasks. Pickup always comes first, both go to the same agent. |
locations | array | no | Up to 1000 reusable coordinates, referenced by zero-based *_location_index fields. |
type | string | no | balanced (default), short or less_maneuvers (motorized modes only). |
units | string | no | metric (default, meters) or imperial (miles). |
avoid | array | no | Up to 100 rules: tolls, ferries, highways, locations, polygons, and others (see differences). |
max_speed | number | no | Speed cap for motorized modes: 10 to 252 km/h, or 6.5 to 155 mph with imperial. |
traffic | string | no | free_flow (default) or approximated. Accepted but ignored. |
Response
A GeoJSON FeatureCollection. Each feature is one agent plan. Agents with no work do not get a feature.
| Field | Meaning |
|---|---|
properties.params (collection) | The normalized task parameters as the gateway understood them. |
properties.issues (collection) | Unassigned work: unassigned_agents, unassigned_jobs, unassigned_shipments (indexes). |
geometry | MultiLineString, one line per leg. |
properties.agent_index, agent_id | Which agent this plan belongs to. |
properties.time, start_time, end_time | Seconds, relative to the start of the plan. |
properties.distance | Meters with units=metric, miles with units=imperial. |
properties.actions | Ordered start, job / pickup / delivery, end steps with start_time and duration in seconds. |
properties.waypoints | Stops with the original and snapped location and the actions done there. |
properties.legs | Time (seconds) and distance between consecutive waypoints, with steps. |
Differences from Geoapify
-
ignored:
trafficis accepted and echoed inproperties.params.traffic, but routes are always computed free-flow. -
approximated: an agent with several
time_windowsis solved against their hull (earliest start to latest end); gaps between windows are not enforced. -
approximated:
avoidrulesgreen_zones,transfersandcountry_codesare accepted but have no effect.tolls,highways(motorized modes only) andferriesare soft preferences scaled byimportance;locationsandpolygonsare hard exclusions. -
approximated:
max_speedapplies only to motorized modes and is ignored for walk, hike and bicycle modes. -
unsupported: modes
transitandapproximated_transit(no transit data). The request schema does not list them.
US and Canada notes
Routing runs on OpenStreetMap-derived Valhalla tiles for the US and Canada. Coordinates are plain WGS84, so there are no ZIP, FSA or address-source concerns here. An agent without delivery_capacity or pickup_capacity is treated as having effectively unlimited capacity.
More examples
Depot referenced by index, distances in miles.
curl -s -X POST -H "Content-Type: application/json" "https://api.data.graphitelab.ai/v1/routeplanner?apiKey=$GRAPHITE_API_KEY" -d '{"mode":"drive","units":"imperial","locations":[{"id":"depot","location":[-86.1581,39.7684]}],"agents":[{"id":"courier-1","start_location_index":0,"end_location_index":0}],"jobs":[{"id":"job-a","location":[-86.1478,39.7653],"duration":120},{"id":"job-b","location":[-86.1805,39.7791],"duration":120}]}'The agent starts and ends at the depot. Distance is 5.57 (miles), and start and end actions carry location_index and location_id.
Errors and limits
| Status | Meaning |
|---|---|
| 400 | Invalid or missing parameters, or a limit exceeded. |
| 401 | Missing or invalid API key. |
| 500 | Unexpected gateway error. |
| 502 | VROOM or Valhalla returned an error or an invalid response. |
| 503 | VROOM or Valhalla is down or timed out. |
Limits:
-
At most 300 unique coordinates per task across locations, agents, jobs and shipments (300 x 300 = 90,000 matrix pairs). More returns 400.
-
At most 100 agents, 1000 jobs, 1000 shipments and 1000 locations.
-
At least one non-empty
jobsorshipmentsarray. -
An agent cannot set both
pickup_capacityanddelivery_capacity.
Try in Playground (signed in to your Graphite Lab organization). Signed out, use the paste-your-key widget on the reference page.