Routing

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.

POST
/routeplanner

Authorization

queryapiKey<token>

Your API key from the dashboard, sent as ?apiKey=<key>.

Request Body

Route optimization task. The request body must be JSON.

application/json
  1. body
mode?string

Travel profile used for all agent routes. drive represents passenger cars. transit and approximated_transit are not supported (no transit data).

Value in"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.

Items1 <= items <= 100
jobs*unknown
Items1 <= items
shipments?array<>

Paired pickup-delivery tasks. Pickup always precedes delivery and both are assigned to the same agent.

Itemsitems <= 1000
locations?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.

Itemsitems <= 1000
avoid?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).

Itemsitems <= 100
traffic?string

Traffic 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.

Default"free_flow"
Value in"free_flow""approximated"
type?string

Route 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.

Default"balanced"
Value in"balanced""short""less_maneuvers"
max_speed?number

Maximum 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.

Range6.5 <= value <= 252
units?string

Unit system for returned route distances. metric returns meters; imperial returns miles.

Default"metric"
Value in"metric""imperial"

Response Body

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
  1. response
type*unknown
features*array<>
bbox?array<number>
Items4 <= items <= 6
properties*
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    }  ]}'
Example Response
{  "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              }            ]          }        ]      }    }  ]}

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

  1. The gateway validates the JSON body against the spec, then collects every unique coordinate (locations, agents, jobs, shipments) into one list.
  2. It asks Valhalla for a time and distance matrix over those coordinates, honoring mode, type, avoid and max_speed.
  3. It hands that matrix to the VROOM optimizer as custom matrices. VROOM never calls Valhalla itself, because it cannot pass avoid, type or max_speed.
  4. 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].

NameTypeRequiredWhat it does
modestringyesTravel profile for all agents, for example drive, truck, bicycle, walk.
agentsarrayyes1 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.
jobsarrayone of jobs or shipmentsUp to 1000 independent tasks with location or location_index, duration (seconds), priority (0 to 100), amounts, requirements, time_windows.
shipmentsarrayone of jobs or shipmentsUp to 1000 paired pickup and delivery tasks. Pickup always comes first, both go to the same agent.
locationsarraynoUp to 1000 reusable coordinates, referenced by zero-based *_location_index fields.
typestringnobalanced (default), short or less_maneuvers (motorized modes only).
unitsstringnometric (default, meters) or imperial (miles).
avoidarraynoUp to 100 rules: tolls, ferries, highways, locations, polygons, and others (see differences).
max_speednumbernoSpeed cap for motorized modes: 10 to 252 km/h, or 6.5 to 155 mph with imperial.
trafficstringnofree_flow (default) or approximated. Accepted but ignored.

Full parameter reference

Response

A GeoJSON FeatureCollection. Each feature is one agent plan. Agents with no work do not get a feature.

FieldMeaning
properties.params (collection)The normalized task parameters as the gateway understood them.
properties.issues (collection)Unassigned work: unassigned_agents, unassigned_jobs, unassigned_shipments (indexes).
geometryMultiLineString, one line per leg.
properties.agent_index, agent_idWhich agent this plan belongs to.
properties.time, start_time, end_timeSeconds, relative to the start of the plan.
properties.distanceMeters with units=metric, miles with units=imperial.
properties.actionsOrdered start, job / pickup / delivery, end steps with start_time and duration in seconds.
properties.waypointsStops with the original and snapped location and the actions done there.
properties.legsTime (seconds) and distance between consecutive waypoints, with steps.

Differences from Geoapify

  • ignored: traffic is accepted and echoed in properties.params.traffic, but routes are always computed free-flow.

  • approximated: an agent with several time_windows is solved against their hull (earliest start to latest end); gaps between windows are not enforced.

  • approximated: avoid rules green_zones, transfers and country_codes are accepted but have no effect. tolls, highways (motorized modes only) and ferries are soft preferences scaled by importance; locations and polygons are hard exclusions.

  • approximated: max_speed applies only to motorized modes and is ignored for walk, hike and bicycle modes.

  • unsupported: modes transit and approximated_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

StatusMeaning
400Invalid or missing parameters, or a limit exceeded.
401Missing or invalid API key.
500Unexpected gateway error.
502VROOM or Valhalla returned an error or an invalid response.
503VROOM 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 jobs or shipments array.

  • An agent cannot set both pickup_capacity and delivery_capacity.

Try in Playground (signed in to your Graphite Lab organization). Signed out, use the paste-your-key widget on the reference page.