The request that contains the vehicle routing problem to be solved.
GraphHopper Directions API (1.0.0)
Integrate A-to-B route planning, turn-by-turn navigation, route optimization, isochrone calculations, location clustering and other tools into your application.
Authenticate to the API by passing your key as a query parameter in every request.
You can also try all API parts without registration in the API explorer.
To speed up development and make coding easier, we offer a JavaScript client and a Java client.
Credit costs vary by endpoint and request parameters. Read more about it here for more details.
- Reuse SSL/TLS sessions
You should utilize the SSL session to speed up responses after the initial response or use a library that does this. E.g. for Java the OkHttp library automatically reuses SSL/TLS sessions and also the browser takes care of this automatically. For python you can use the requests library: first you create a session (session = requests.Session()) and then do requests only with this session instead of directly using "requests".
- Bandwidth reduction
If you create your own client, make sure it supports http/2 and gzipped responses for best speed. If you use Route Optimization or Matrices and want to solve large problems, we recommend you to reduce bandwidth by compressing your POST request and specifying the header as follows: Content-Encoding: gzip. This will also avoid the HTTP 413 error "Request Entity Too Large".
Map Data and Routing Profiles
The default data source is OpenStreetMap. As an alternative, we also offer TomTom MultiNet.
Please read in our attribution page on how to attribute these different data sources.
Route Optimization
The Route Optimization API solves traveling salesman and vehicle routing problems: given a set of stops to visit and a set of vehicles to visit them with, it returns an assignment of stops to vehicles and an order to visit them in, minimizing an objective you choose (total time, number of vehicles used, longest single route, etc.) while respecting constraints you specify (time windows, vehicle capacities, driver skills, and many more).
This page describes the conceptual model. The endpoint reference pages linked at the bottom describe the request and response schema in detail. If you prefer to learn by example, jump to the API Explorer or the Getting Started tutorial.
A request describes a fleet, a workload, and an objective. The API returns a solution.
The fleet is the set of vehicles that will execute the routes. Vehicles are described individually (start and end location, optional shift schedule); shared characteristics (routing profile, capacity, speed, cost per hour and per kilometer) are factored out into vehicle types.
The workload is the set of orders to be served. Each order is either a service (a single stop) or a shipment (a paired pickup and delivery that must end up on the same route, in order).
The objective is what the optimizer minimizes. Total time, number of vehicles used, longest single route, fairness across drivers. You choose, and you can combine multiple objectives in priority order.
A solution is a list of routes (one per used vehicle), each an ordered list of activities with arrival and departure times, plus an unassigned list for orders that could not be placed.
Two further mechanisms refine the model:
Constraints restrict which solutions are valid. Hard constraints (time windows, capacity, skills, allowed vehicles) leave non-conforming orders unassigned; soft constraints (priority, preferred vehicles) influence the optimizer without forbidding violations.
Relations link orders to each other beyond what an objective and constraints can express. They state that certain orders must be served on the same route, in a given sequence, or within a geographic flow, independently of what the optimizer would otherwise choose.
These five elements — fleet, workload, objective, constraints, relations — are everything you ever describe in a request. The rest of this page explains how to use each of them well.
A request with one vehicle, one vehicle type, four services, a relation, and an objective to minimize total time. One service exceeds the vehicle's capacity and will be left unassigned. The in_direct_sequence relation forces order-3 to be served immediately after the vehicle's start, followed immediately by order-2:
{
"vehicles": [
{
"vehicle_id": "driver-1",
"type_id": "van",
"start_address": { "location_id": "depot", "lon": 13.3888, "lat": 52.5170 },
"earliest_start": 1746612000
}
],
"vehicle_types": [
{
"type_id": "van",
"profile": "car",
"capacity": [10]
}
],
"services": [
{
"id": "order-1",
"address": { "location_id": "order-1", "lon": 13.3762, "lat": 52.5206 },
"size": [2],
"duration": 300,
"time_windows": [{ "earliest": 1746612000, "latest": 1746615600 }]
},
{
"id": "order-2",
"address": { "location_id": "order-2", "lon": 13.4050, "lat": 52.5200 },
"size": [3],
"duration": 300
},
{
"id": "order-3",
"address": { "location_id": "order-3", "lon": 13.4270, "lat": 52.5230 },
"size": [1],
"duration": 300
},
{
"id": "order-4",
"address": { "location_id": "order-4", "lon": 13.395163, "lat": 52.526215 },
"size": [20],
"duration": 300
}
],
"objectives": [
{ "type": "min", "value": "completion_time" }
],
"relations": [ { "type": "in_direct_sequence", "ids": ["start","order-3","order-2"] } ]
}The response contains a solution with a routes array (one per used vehicle) and an unassigned object for orders that could not be placed:
{
"solution": {
"distance": 10424,
"transport_time": 1279,
"completion_time": 2179,
"no_vehicles": 1,
"no_unassigned": 1,
"routes": [
{
"vehicle_id": "driver-1",
"activities": [
{ "type": "start", "location_id": "depot", "arr_time": 1746612000, "end_time": 1746612000, "load_after": [0] },
{ "type": "service", "id": "order-3", "location_id": "order-3", "arr_time": 1746612352, "end_time": 1746612652, "load_before": [0], "load_after": [1] },
{ "type": "service", "id": "order-2", "location_id": "order-2", "arr_time": 1746612966, "end_time": 1746613266, "load_before": [1], "load_after": [4] },
{ "type": "service", "id": "order-1", "location_id": "order-1", "arr_time": 1746613640, "end_time": 1746613940, "load_before": [4], "load_after": [6], "time_window": { "earliest": 1746612000, "latest": 1746615600 } },
{ "type": "end", "location_id": "depot", "arr_time": 1746614179, "end_time": 1746614179, "load_before": [6] }
]
}
],
"unassigned": {
"services": ["order-4"],
"details": [{ "id": "order-4", "code": 3, "reason": "does not fit into any vehicle due to capacity" }]
}
}
}Note that time_windows use Unix timestamps (recommended). The in_direct_sequence relation forced the solver to visit order-3 then order-2 immediately after the start, even though a different sequence might have been shorter. The order-4 was left unassigned with reason code 3 (capacity exceeded: size 20 vs. vehicle capacity 10).
The objectives plus hard and soft constraints can express most of what a router needs, but not everything. Real-world dispatchers carry knowledge that resists formalization: a driver prefers to clear the north side of town before lunch, two stops at the same office should be done back-to-back so the driver only signs in once, a particular customer expects to be the first visit of the day, the morning route should sweep west-to-east before circling back. These aren't gaps in the API — they're the gap between any abstract objective and the messy reality of operations. Drivers have preferences, customers have habits, and dispatchers have judgment that earned its keep over years.
Relations are the escape hatch for that gap. Instead of trying to encode every preference as an objective or constraint, you let the optimizer do its job within boundaries you define. A relation says "whatever else you decide, this constraint must hold."
The available relation types:
in_same_route— the related items must end up on the same vehicle, regardless of order.not_in_same_route— the related items must end up on different vehicles. This is the only exclusion relation; all others pull items together.in_sequence— the related items must appear in the listed order on a single route, but other stops may be inserted between them.in_direct_sequence— the related items must appear in the listed order on a single route, with no stops in between.neighbor— the related items must be served by the same vehicle and appear next to each other, but in any order.in_area_sequence— each area is defined by an anchor stop and a radius; all stops within that radius belong to the area. The areas must be visited in the given order, while the optimizer remains free to sequence stops within each area.
in_area_sequence is unique among the six in that the grouping is defined spatially, by an anchor stop and a radius, rather than by labels you assign to individual orders. A dispatcher picks a few anchor stops, sets a radius for each, orders the resulting areas, and the optimizer respects the geographic flow while still finding the best sequence within each area. The dispatcher's tacit knowledge about traffic, neighborhood characteristics, or driver preference becomes a few clicks; the optimizer handles the rest.
Relations operate at two granularities, depending on which field you populate.
Order-level relations use the ids field, listing individual service or shipment ids. in_sequence over ["service-A", "service-B"] means service-A must be served before service-B on the same route.
Group-level relations use the group field, listing group labels instead of order ids. in_sequence over groups ["downtown", "uptown"] is a higher-order constraint: every stop tagged group: "downtown" must be served before every stop tagged group: "uptown". The relation operates on the clusters as units, not on the individual stops inside them.
Group-level relations are what make interactive UIs practical. A dispatcher assigns a group label to a set of stops in a neighborhood, and a single relation entry sequences that group against another, regardless of how many stops are inside either one. The dispatcher works with a handful of meaningful clusters; the request stays compact regardless of how many orders the underlying problem contains.
Groups are not only labels you reference from relations. Assigning the same group label to two or more orders is itself a constraint.
Tagging orders with the same group does two things at once. First, it treats them as a contiguous cluster: wherever they are served, they appear as a block, with no other stops inserted between them. Second, it leaves the order within that block to the optimizer. A group is in effect a "weak in_sequence": the stops stay together, but the algorithm picks the best internal sequence.
The cluster constraint is about contiguity, not route assignment. In a multi-vehicle problem, the orders in group A and the orders in group B can land on different routes; the optimizer is free to make that choice. On each route a group appears on, its members are contiguous. If you want all members of a group on the same route, add an in_same_route relation over the group, or pin the group to a specific vehicle by adding a vehicle_id to the relation.
Ungrouped orders are by default not allowed to be inserted inside a group; if you need that flexibility, set configuration.optimization.free_insertion to true. You can then exempt specific groups (typically conventional ones like "asap" or "last") from free insertion via free_insertion_exempt_groups, so those groups remain compact while other ungrouped orders move freely.
Request
Start by reading the introduction to the Route Optimization API.
To solve a new vehicle routing problem, make a HTTP POST to this URL
https://graphhopper.com/api/1/vrp?key=<your_key>It returns the solution to this problem in the JSON response.
Please note that this endpoint is well suited for solving smaller problems. Larger vehicle routing problems that take longer than 10 seconds to solve cannot be processed using this endpoint. To solve them, please use the batch mode URL instead.
Specifies the available vehicle types. These types can be assigned to vehicles.
Specifies the orders of the type "service". These are, for example, pickups, deliveries, or other stops to be visited by the specified vehicles. Each of these orders contains only one location.
Specifies the available shipments. Each shipment consists of a pickup and a delivery. For a single shipment, the pickup must always occur before the delivery. However, pickups and deliveries from multiple shipments can be sequenced independently.
Defines additional relationships between orders.
Specifies an objective function. The vehicle routing problem is solved in such a way that this objective function is minimized.
- https://graphhopper.com/api/1/vrp
- curl
- Java
- Payload
curl -X POST -H "Content-Type: application/json" "https://graphhopper.com/api/1/vrp?key=api_key" -d '{
"vehicles": [
{
"vehicle_id": "my_vehicle",
"start_address": {
"location_id": "berlin",
"lon": 13.406,
"lat": 52.537
}
}
],
"services": [
{
"id": "hamburg",
"name": "visit_hamburg",
"address": {
"location_id": "hamburg",
"lon": 9.999,
"lat": 53.552
}
},
{
"id": "munich",
"name": "visit_munich",
"address": {
"location_id": "munich",
"lon": 11.57,
"lat": 48.145
}
}
]}'A response containing the solution
The number of seconds that you have to wait before a reset of the credit count is done.
Indicates the current status of the job
Processing time in ms. If job is still waiting in queue, processing_time is 0
{ "copyrights": [ "GraphHopper", "OpenStreetMap contributors" ], "job_id": "d62fcadd-c84a-4298-90b5-28550125bec5", "status": "finished", "waiting_time_in_queue": 0, "processing_time": 459, "solution": { "costs": 438, "distance": 17994, "time": 4094, "transport_time": 4094, "completion_time": 4172, "max_operation_time": 2465, "waiting_time": 78, "service_duration": 0, "preparation_time": 0, "no_vehicles": 2, "no_unassigned": 0, "routes": [ { "vehicle_id": "vehicle-2", "distance": 10618, "transport_time": 2465, "completion_time": 2465, "waiting_time": 0, "service_duration": 0, "preparation_time": 0, "points": [ { "coordinates": [ [ 13.40608, 52.53701 ], [ 13.40643, 52.53631 ], [ 13.40554, 52.53616 ], [ 13.4054, 52.53608 ], [ 13.40445, 52.53513 ], [ 13.40436, 52.53509 ], [ 13.40428, 52.53508 ], [ 13.40463, 52.53419 ], [ 13.40451, 52.53419 ], [ 13.4034, 52.53401 ], [ 13.403, 52.53359 ], [ 13.40291, 52.53354 ], [ 13.40268, 52.53347 ], [ 13.39888, 52.53259 ], [ 13.39839, 52.53253 ], [ 13.39812, 52.53251 ], [ 13.39616, 52.53243 ], [ 13.39579, 52.5324 ], [ 13.38973, 52.53173 ], [ 13.39163, 52.53025 ], [ 13.38797, 52.52935 ], [ 13.38763, 52.52996 ] ], "type": "LineString" }, { "coordinates": [ [ 13.38763, 52.52996 ], [ 13.38739, 52.53039 ], [ 13.38724, 52.53036 ], [ 13.38464, 52.52929 ], [ 13.38538, 52.52871 ], [ 13.38634, 52.52792 ], [ 13.38638, 52.52779 ], [ 13.38657, 52.52763 ], [ 13.38676, 52.52741 ], [ 13.38698, 52.52713 ], [ 13.38704, 52.52701 ], [ 13.38753, 52.524 ], [ 13.3877, 52.52307 ], [ 13.3878, 52.52282 ], [ 13.38788, 52.52252 ], [ 13.38802, 52.52174 ], [ 13.38519, 52.52009 ], [ 13.38539, 52.5191 ], [ 13.38548, 52.51852 ], [ 13.38042, 52.51819 ], [ 13.38071, 52.5167 ], [ 13.38076, 52.51652 ], [ 13.38084, 52.51634 ], [ 13.3821, 52.51396 ], [ 13.38055, 52.51365 ] ], "type": "LineString" }, { "coordinates": [ [ 13.38055, 52.51365 ], [ 13.38229, 52.514 ], [ 13.38363, 52.51429 ], [ 13.3848, 52.51445 ], [ 13.38504, 52.51358 ], [ 13.39124, 52.51397 ], [ 13.3911, 52.51488 ], [ 13.39303, 52.51499 ], [ 13.39317, 52.5141 ], [ 13.39548, 52.51419 ], [ 13.39571, 52.51421 ] ], "type": "LineString" }, { "coordinates": [ [ 13.39571, 52.51421 ], [ 13.39695, 52.51434 ], [ 13.39674, 52.51523 ], [ 13.39742, 52.51531 ], [ 13.39873, 52.51558 ], [ 13.39846, 52.51599 ], [ 13.39825, 52.51729 ], [ 13.39805, 52.51755 ], [ 13.39892, 52.51761 ], [ 13.39917, 52.51764 ], [ 13.39964, 52.51775 ], [ 13.40009, 52.51791 ], [ 13.40034, 52.51797 ], [ 13.4021, 52.51864 ], [ 13.40288, 52.51896 ], [ 13.40375, 52.51936 ], [ 13.40498, 52.52001 ], [ 13.40463, 52.5203 ], [ 13.40311, 52.52144 ], [ 13.40442, 52.52189 ], [ 13.40448, 52.52192 ], [ 13.40451, 52.52195 ], [ 13.40473, 52.52199 ], [ 13.40504, 52.52208 ], [ 13.40572, 52.52235 ], [ 13.40687, 52.52294 ], [ 13.40693, 52.52299 ], [ 13.40706, 52.52319 ], [ 13.40738, 52.52378 ], [ 13.40787, 52.52443 ], [ 13.4079, 52.52453 ], [ 13.40938, 52.52401 ], [ 13.40962, 52.52398 ], [ 13.41001, 52.52395 ], [ 13.41072, 52.52391 ], [ 13.41215, 52.52389 ], [ 13.41233, 52.52386 ], [ 13.4131, 52.5235 ], [ 13.41288, 52.52333 ], [ 13.41475, 52.52247 ], [ 13.41496, 52.52264 ], [ 13.41523, 52.52251 ], [ 13.41633, 52.52338 ], [ 13.41631, 52.52346 ], [ 13.41654, 52.52364 ], [ 13.41684, 52.52351 ] ], "type": "LineString" }, { "coordinates": [ [ 13.41684, 52.52351 ], [ 13.41654, 52.52364 ], [ 13.41631, 52.52346 ], [ 13.4163, 52.52344 ], [ 13.41587, 52.52363 ], [ 13.41572, 52.5235 ], [ 13.41409, 52.5242 ], [ 13.41454, 52.52461 ], [ 13.41454, 52.52466 ], [ 13.41358, 52.52508 ], [ 13.41366, 52.52514 ], [ 13.41344, 52.52525 ], [ 13.4133, 52.52514 ], [ 13.41316, 52.5252 ], [ 13.41107, 52.52585 ], [ 13.41118, 52.52606 ], [ 13.41118, 52.52616 ], [ 13.41095, 52.52664 ], [ 13.41097, 52.52678 ], [ 13.41084, 52.52706 ], [ 13.41057, 52.52747 ], [ 13.41028, 52.52809 ], [ 13.41032, 52.52821 ], [ 13.4102, 52.52847 ], [ 13.40999, 52.52875 ], [ 13.40984, 52.52905 ], [ 13.40982, 52.52914 ], [ 13.40984, 52.52926 ], [ 13.4104, 52.52998 ], [ 13.4105, 52.53001 ], [ 13.41064, 52.53016 ], [ 13.41082, 52.5303 ], [ 13.41198, 52.53107 ], [ 13.4122, 52.53128 ], [ 13.41232, 52.53143 ], [ 13.41247, 52.53192 ], [ 13.41267, 52.53245 ], [ 13.41275, 52.53259 ], [ 13.41215, 52.5327 ], [ 13.40731, 52.53463 ], [ 13.40608, 52.53701 ] ], "type": "LineString" } ], "activities": [ { "type": "start", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "end_time": 1554804329, "end_date_time": null, "distance": 0, "driving_time": 0, "preparation_time": 0, "waiting_time": 0, "load_after": [ 0 ] }, { "type": "pickupShipment", "id": "7fe77504-7df8-4497-843c-02d70b6490ce", "location_id": "13.387613_52.529961", "address": { "location_id": "13.387613_52.529961", "lat": 52.529961, "lon": 13.387613 }, "arr_time": 1554804789, "arr_date_time": null, "end_time": 1554804789, "end_date_time": null, "waiting_time": 0, "distance": 2012, "driving_time": 460, "preparation_time": 0, "load_before": [ 0 ], "load_after": [ 1 ] }, { "type": "deliverShipment", "id": "7fe77504-7df8-4497-843c-02d70b6490ce", "location_id": "13.380575_52.513614", "address": { "location_id": "13.380575_52.513614", "lat": 52.513614, "lon": 13.380575 }, "arr_time": 1554805344, "arr_date_time": null, "end_time": 1554805344, "end_date_time": null, "waiting_time": 0, "distance": 4560, "driving_time": 1015, "preparation_time": 0, "load_before": [ 1 ], "load_after": [ 0 ] }, { "type": "service", "id": "s-4", "location_id": "13.395767_52.514038", "address": { "location_id": "13.395767_52.514038", "lat": 52.514038, "lon": 13.395767 }, "arr_time": 1554805632, "arr_date_time": null, "end_time": 1554805632, "end_date_time": null, "waiting_time": 0, "distance": 5887, "driving_time": 1303, "preparation_time": 0, "load_before": [ 0 ], "load_after": [ 1 ] }, { "type": "service", "id": "s-3", "location_id": "13.416882_52.523543", "address": { "location_id": "13.416882_52.523543", "lat": 52.523543, "lon": 13.416882 }, "arr_time": 1554806253, "arr_date_time": null, "end_time": 1554806253, "end_date_time": null, "waiting_time": 0, "distance": 8486, "driving_time": 1924, "preparation_time": 0, "load_before": [ 1 ], "load_after": [ 2 ] }, { "type": "end", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "arr_time": 1554806794, "arr_date_time": null, "distance": 10618, "driving_time": 2465, "preparation_time": 0, "waiting_time": 0, "load_before": [ 2 ] } ] }, { "vehicle_id": "vehicle-1", "distance": 7376, "transport_time": 1629, "completion_time": 1707, "waiting_time": 78, "service_duration": 0, "preparation_time": 0, "points": [ { "coordinates": [ [ 13.40608, 52.53701 ], [ 13.40674, 52.53571 ], [ 13.40433, 52.53313 ], [ 13.40271, 52.53149 ], [ 13.40246, 52.53121 ], [ 13.40148, 52.52999 ], [ 13.40128, 52.52993 ], [ 13.40118, 52.52988 ], [ 13.40133, 52.5296 ], [ 13.40138, 52.52951 ], [ 13.40167, 52.52914 ], [ 13.40188, 52.52895 ], [ 13.398, 52.52885 ], [ 13.39289, 52.52748 ], [ 13.39354, 52.5264 ], [ 13.39358, 52.52628 ], [ 13.39324, 52.52575 ], [ 13.39334, 52.52573 ], [ 13.39339, 52.52584 ] ], "type": "LineString" }, { "coordinates": [ [ 13.39339, 52.52584 ], [ 13.3934, 52.52599 ], [ 13.39358, 52.52628 ], [ 13.39354, 52.5264 ], [ 13.39242, 52.52823 ], [ 13.39381, 52.52852 ], [ 13.38973, 52.53173 ], [ 13.38717, 52.5315 ], [ 13.38678, 52.5315 ], [ 13.38641, 52.53147 ], [ 13.38617, 52.53143 ], [ 13.38607, 52.53155 ], [ 13.38526, 52.53225 ], [ 13.38501, 52.53252 ], [ 13.38316, 52.53418 ], [ 13.38179, 52.5355 ], [ 13.38084, 52.53523 ], [ 13.38081, 52.53531 ], [ 13.3795, 52.53677 ], [ 13.37941, 52.53682 ], [ 13.37935, 52.53683 ], [ 13.37919, 52.53682 ], [ 13.37617, 52.5361 ], [ 13.37502, 52.53698 ], [ 13.37584, 52.53734 ] ], "type": "LineString" }, { "coordinates": [ [ 13.37584, 52.53734 ], [ 13.37566, 52.53726 ], [ 13.37515, 52.53763 ], [ 13.37644, 52.53841 ], [ 13.37807, 52.53935 ], [ 13.37946, 52.5402 ], [ 13.3796, 52.54019 ], [ 13.37984, 52.54021 ], [ 13.37988, 52.54012 ], [ 13.38062, 52.53936 ], [ 13.38169, 52.53832 ], [ 13.38236, 52.5377 ], [ 13.38363, 52.53661 ], [ 13.38492, 52.53555 ], [ 13.38613, 52.53447 ], [ 13.38757, 52.53338 ], [ 13.38791, 52.53354 ], [ 13.38812, 52.53368 ], [ 13.38833, 52.53392 ], [ 13.38977, 52.53518 ], [ 13.39003, 52.53539 ], [ 13.39256, 52.53701 ], [ 13.39316, 52.53739 ], [ 13.39327, 52.53744 ], [ 13.3936, 52.53757 ], [ 13.40155, 52.53982 ], [ 13.40357, 52.53715 ], [ 13.40372, 52.53719 ], [ 13.40465, 52.53727 ], [ 13.4048, 52.53726 ], [ 13.4059, 52.53736 ], [ 13.40608, 52.53701 ] ], "type": "LineString" } ], "activities": [ { "type": "start", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "end_time": 1554804329, "end_date_time": null, "distance": 0, "driving_time": 0, "preparation_time": 0, "waiting_time": 0, "load_after": [ 0 ] }, { "type": "service", "id": "s-2", "location_id": "13.393364_52.525851", "address": { "location_id": "13.393364_52.525851", "lat": 52.525851, "lon": 13.393364 }, "arr_time": 1554804743, "arr_date_time": null, "end_time": 1554804743, "end_date_time": null, "waiting_time": 0, "distance": 1884, "driving_time": 414, "preparation_time": 0, "load_before": [ 0 ], "load_after": [ 1 ] }, { "type": "service", "id": "s-1", "location_id": "13.375854_52.537338", "address": { "location_id": "13.375854_52.537338", "lat": 52.537338, "lon": 13.375854 }, "arr_time": 1554805251, "arr_date_time": null, "end_time": 1554805329, "end_date_time": null, "waiting_time": 78, "distance": 4205, "driving_time": 922, "preparation_time": 0, "load_before": [ 1 ], "load_after": [ 2 ] }, { "type": "end", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "arr_time": 1554806036, "arr_date_time": null, "distance": 7376, "driving_time": 1629, "preparation_time": 0, "waiting_time": 0, "load_before": [ 2 ] } ] } ], "unassigned": { "services": [], "shipments": [], "breaks": [], "details": [] } } }
Request
To solve a vehicle routing problem, perform the following steps:
1.) Make a HTTP POST to this URL
https://graphhopper.com/api/1/vrp/optimize?key=<your_key>It returns a job id (job_id).
2.) Take the job id and fetch the solution for the vehicle routing problem from this URL:
https://graphhopper.com/api/1/vrp/solution/<job_id>?key=<your_key>We recommend querying the solution every 500ms until it returns 'status=finished'.
Note: Since the workflow is more complex and you lose some time fetching the solution, you should prefer the synchronous endpoint when possible. Use the batch mode only for long-running problems.
The request that contains the problem to be solved.
Specifies the available vehicle types. These types can be assigned to vehicles.
Specifies the orders of the type "service". These are, for example, pickups, deliveries, or other stops to be visited by the specified vehicles. Each of these orders contains only one location.
Specifies the available shipments. Each shipment consists of a pickup and a delivery. For a single shipment, the pickup must always occur before the delivery. However, pickups and deliveries from multiple shipments can be sequenced independently.
Defines additional relationships between orders.
Specifies an objective function. The vehicle routing problem is solved in such a way that this objective function is minimized.
- https://graphhopper.com/api/1/vrp/optimize
- curl
- Java
- Payload
curl -X POST -H "Content-Type: application/json" "https://graphhopper.com/api/1/vrp/optimize?key=api_key" -d '{
"vehicles": [
{
"vehicle_id": "my_vehicle",
"start_address": {
"location_id": "berlin",
"lon": 13.406,
"lat": 52.537
}
}
],
"services": [
{
"id": "hamburg",
"name": "visit_hamburg",
"address": {
"location_id": "hamburg",
"lon": 9.999,
"lat": 53.552
}
},
{
"id": "munich",
"name": "visit_munich",
"address": {
"location_id": "munich",
"lon": 11.57,
"lat": 48.145
}
}
]}'{ "job_id": "44886560-b584-4da5-b245-768151dacd8f" }
Request
Take the job id and fetch the solution for the vehicle routing problem from this URL:
https://graphhopper.com/api/1/vrp/solution/<job_id>?key=<your_key>You get the job id by sending a vehicle routing problem to the batch mode URL.
- https://graphhopper.com/api/1/vrp/solution/{jobId}
- curl
- Java
- Payload
curl -X GET "https://graphhopper.com/api/1/vrp/solution/job_id?key=api_key"A response containing the solution
The number of seconds that you have to wait before a reset of the credit count is done.
Indicates the current status of the job
Processing time in ms. If job is still waiting in queue, processing_time is 0
{ "copyrights": [ "GraphHopper", "OpenStreetMap contributors" ], "job_id": "d62fcadd-c84a-4298-90b5-28550125bec5", "status": "finished", "waiting_time_in_queue": 0, "processing_time": 459, "solution": { "costs": 438, "distance": 17994, "time": 4094, "transport_time": 4094, "completion_time": 4172, "max_operation_time": 2465, "waiting_time": 78, "service_duration": 0, "preparation_time": 0, "no_vehicles": 2, "no_unassigned": 0, "routes": [ { "vehicle_id": "vehicle-2", "distance": 10618, "transport_time": 2465, "completion_time": 2465, "waiting_time": 0, "service_duration": 0, "preparation_time": 0, "points": [ { "coordinates": [ [ 13.40608, 52.53701 ], [ 13.40643, 52.53631 ], [ 13.40554, 52.53616 ], [ 13.4054, 52.53608 ], [ 13.40445, 52.53513 ], [ 13.40436, 52.53509 ], [ 13.40428, 52.53508 ], [ 13.40463, 52.53419 ], [ 13.40451, 52.53419 ], [ 13.4034, 52.53401 ], [ 13.403, 52.53359 ], [ 13.40291, 52.53354 ], [ 13.40268, 52.53347 ], [ 13.39888, 52.53259 ], [ 13.39839, 52.53253 ], [ 13.39812, 52.53251 ], [ 13.39616, 52.53243 ], [ 13.39579, 52.5324 ], [ 13.38973, 52.53173 ], [ 13.39163, 52.53025 ], [ 13.38797, 52.52935 ], [ 13.38763, 52.52996 ] ], "type": "LineString" }, { "coordinates": [ [ 13.38763, 52.52996 ], [ 13.38739, 52.53039 ], [ 13.38724, 52.53036 ], [ 13.38464, 52.52929 ], [ 13.38538, 52.52871 ], [ 13.38634, 52.52792 ], [ 13.38638, 52.52779 ], [ 13.38657, 52.52763 ], [ 13.38676, 52.52741 ], [ 13.38698, 52.52713 ], [ 13.38704, 52.52701 ], [ 13.38753, 52.524 ], [ 13.3877, 52.52307 ], [ 13.3878, 52.52282 ], [ 13.38788, 52.52252 ], [ 13.38802, 52.52174 ], [ 13.38519, 52.52009 ], [ 13.38539, 52.5191 ], [ 13.38548, 52.51852 ], [ 13.38042, 52.51819 ], [ 13.38071, 52.5167 ], [ 13.38076, 52.51652 ], [ 13.38084, 52.51634 ], [ 13.3821, 52.51396 ], [ 13.38055, 52.51365 ] ], "type": "LineString" }, { "coordinates": [ [ 13.38055, 52.51365 ], [ 13.38229, 52.514 ], [ 13.38363, 52.51429 ], [ 13.3848, 52.51445 ], [ 13.38504, 52.51358 ], [ 13.39124, 52.51397 ], [ 13.3911, 52.51488 ], [ 13.39303, 52.51499 ], [ 13.39317, 52.5141 ], [ 13.39548, 52.51419 ], [ 13.39571, 52.51421 ] ], "type": "LineString" }, { "coordinates": [ [ 13.39571, 52.51421 ], [ 13.39695, 52.51434 ], [ 13.39674, 52.51523 ], [ 13.39742, 52.51531 ], [ 13.39873, 52.51558 ], [ 13.39846, 52.51599 ], [ 13.39825, 52.51729 ], [ 13.39805, 52.51755 ], [ 13.39892, 52.51761 ], [ 13.39917, 52.51764 ], [ 13.39964, 52.51775 ], [ 13.40009, 52.51791 ], [ 13.40034, 52.51797 ], [ 13.4021, 52.51864 ], [ 13.40288, 52.51896 ], [ 13.40375, 52.51936 ], [ 13.40498, 52.52001 ], [ 13.40463, 52.5203 ], [ 13.40311, 52.52144 ], [ 13.40442, 52.52189 ], [ 13.40448, 52.52192 ], [ 13.40451, 52.52195 ], [ 13.40473, 52.52199 ], [ 13.40504, 52.52208 ], [ 13.40572, 52.52235 ], [ 13.40687, 52.52294 ], [ 13.40693, 52.52299 ], [ 13.40706, 52.52319 ], [ 13.40738, 52.52378 ], [ 13.40787, 52.52443 ], [ 13.4079, 52.52453 ], [ 13.40938, 52.52401 ], [ 13.40962, 52.52398 ], [ 13.41001, 52.52395 ], [ 13.41072, 52.52391 ], [ 13.41215, 52.52389 ], [ 13.41233, 52.52386 ], [ 13.4131, 52.5235 ], [ 13.41288, 52.52333 ], [ 13.41475, 52.52247 ], [ 13.41496, 52.52264 ], [ 13.41523, 52.52251 ], [ 13.41633, 52.52338 ], [ 13.41631, 52.52346 ], [ 13.41654, 52.52364 ], [ 13.41684, 52.52351 ] ], "type": "LineString" }, { "coordinates": [ [ 13.41684, 52.52351 ], [ 13.41654, 52.52364 ], [ 13.41631, 52.52346 ], [ 13.4163, 52.52344 ], [ 13.41587, 52.52363 ], [ 13.41572, 52.5235 ], [ 13.41409, 52.5242 ], [ 13.41454, 52.52461 ], [ 13.41454, 52.52466 ], [ 13.41358, 52.52508 ], [ 13.41366, 52.52514 ], [ 13.41344, 52.52525 ], [ 13.4133, 52.52514 ], [ 13.41316, 52.5252 ], [ 13.41107, 52.52585 ], [ 13.41118, 52.52606 ], [ 13.41118, 52.52616 ], [ 13.41095, 52.52664 ], [ 13.41097, 52.52678 ], [ 13.41084, 52.52706 ], [ 13.41057, 52.52747 ], [ 13.41028, 52.52809 ], [ 13.41032, 52.52821 ], [ 13.4102, 52.52847 ], [ 13.40999, 52.52875 ], [ 13.40984, 52.52905 ], [ 13.40982, 52.52914 ], [ 13.40984, 52.52926 ], [ 13.4104, 52.52998 ], [ 13.4105, 52.53001 ], [ 13.41064, 52.53016 ], [ 13.41082, 52.5303 ], [ 13.41198, 52.53107 ], [ 13.4122, 52.53128 ], [ 13.41232, 52.53143 ], [ 13.41247, 52.53192 ], [ 13.41267, 52.53245 ], [ 13.41275, 52.53259 ], [ 13.41215, 52.5327 ], [ 13.40731, 52.53463 ], [ 13.40608, 52.53701 ] ], "type": "LineString" } ], "activities": [ { "type": "start", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "end_time": 1554804329, "end_date_time": null, "distance": 0, "driving_time": 0, "preparation_time": 0, "waiting_time": 0, "load_after": [ 0 ] }, { "type": "pickupShipment", "id": "7fe77504-7df8-4497-843c-02d70b6490ce", "location_id": "13.387613_52.529961", "address": { "location_id": "13.387613_52.529961", "lat": 52.529961, "lon": 13.387613 }, "arr_time": 1554804789, "arr_date_time": null, "end_time": 1554804789, "end_date_time": null, "waiting_time": 0, "distance": 2012, "driving_time": 460, "preparation_time": 0, "load_before": [ 0 ], "load_after": [ 1 ] }, { "type": "deliverShipment", "id": "7fe77504-7df8-4497-843c-02d70b6490ce", "location_id": "13.380575_52.513614", "address": { "location_id": "13.380575_52.513614", "lat": 52.513614, "lon": 13.380575 }, "arr_time": 1554805344, "arr_date_time": null, "end_time": 1554805344, "end_date_time": null, "waiting_time": 0, "distance": 4560, "driving_time": 1015, "preparation_time": 0, "load_before": [ 1 ], "load_after": [ 0 ] }, { "type": "service", "id": "s-4", "location_id": "13.395767_52.514038", "address": { "location_id": "13.395767_52.514038", "lat": 52.514038, "lon": 13.395767 }, "arr_time": 1554805632, "arr_date_time": null, "end_time": 1554805632, "end_date_time": null, "waiting_time": 0, "distance": 5887, "driving_time": 1303, "preparation_time": 0, "load_before": [ 0 ], "load_after": [ 1 ] }, { "type": "service", "id": "s-3", "location_id": "13.416882_52.523543", "address": { "location_id": "13.416882_52.523543", "lat": 52.523543, "lon": 13.416882 }, "arr_time": 1554806253, "arr_date_time": null, "end_time": 1554806253, "end_date_time": null, "waiting_time": 0, "distance": 8486, "driving_time": 1924, "preparation_time": 0, "load_before": [ 1 ], "load_after": [ 2 ] }, { "type": "end", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "arr_time": 1554806794, "arr_date_time": null, "distance": 10618, "driving_time": 2465, "preparation_time": 0, "waiting_time": 0, "load_before": [ 2 ] } ] }, { "vehicle_id": "vehicle-1", "distance": 7376, "transport_time": 1629, "completion_time": 1707, "waiting_time": 78, "service_duration": 0, "preparation_time": 0, "points": [ { "coordinates": [ [ 13.40608, 52.53701 ], [ 13.40674, 52.53571 ], [ 13.40433, 52.53313 ], [ 13.40271, 52.53149 ], [ 13.40246, 52.53121 ], [ 13.40148, 52.52999 ], [ 13.40128, 52.52993 ], [ 13.40118, 52.52988 ], [ 13.40133, 52.5296 ], [ 13.40138, 52.52951 ], [ 13.40167, 52.52914 ], [ 13.40188, 52.52895 ], [ 13.398, 52.52885 ], [ 13.39289, 52.52748 ], [ 13.39354, 52.5264 ], [ 13.39358, 52.52628 ], [ 13.39324, 52.52575 ], [ 13.39334, 52.52573 ], [ 13.39339, 52.52584 ] ], "type": "LineString" }, { "coordinates": [ [ 13.39339, 52.52584 ], [ 13.3934, 52.52599 ], [ 13.39358, 52.52628 ], [ 13.39354, 52.5264 ], [ 13.39242, 52.52823 ], [ 13.39381, 52.52852 ], [ 13.38973, 52.53173 ], [ 13.38717, 52.5315 ], [ 13.38678, 52.5315 ], [ 13.38641, 52.53147 ], [ 13.38617, 52.53143 ], [ 13.38607, 52.53155 ], [ 13.38526, 52.53225 ], [ 13.38501, 52.53252 ], [ 13.38316, 52.53418 ], [ 13.38179, 52.5355 ], [ 13.38084, 52.53523 ], [ 13.38081, 52.53531 ], [ 13.3795, 52.53677 ], [ 13.37941, 52.53682 ], [ 13.37935, 52.53683 ], [ 13.37919, 52.53682 ], [ 13.37617, 52.5361 ], [ 13.37502, 52.53698 ], [ 13.37584, 52.53734 ] ], "type": "LineString" }, { "coordinates": [ [ 13.37584, 52.53734 ], [ 13.37566, 52.53726 ], [ 13.37515, 52.53763 ], [ 13.37644, 52.53841 ], [ 13.37807, 52.53935 ], [ 13.37946, 52.5402 ], [ 13.3796, 52.54019 ], [ 13.37984, 52.54021 ], [ 13.37988, 52.54012 ], [ 13.38062, 52.53936 ], [ 13.38169, 52.53832 ], [ 13.38236, 52.5377 ], [ 13.38363, 52.53661 ], [ 13.38492, 52.53555 ], [ 13.38613, 52.53447 ], [ 13.38757, 52.53338 ], [ 13.38791, 52.53354 ], [ 13.38812, 52.53368 ], [ 13.38833, 52.53392 ], [ 13.38977, 52.53518 ], [ 13.39003, 52.53539 ], [ 13.39256, 52.53701 ], [ 13.39316, 52.53739 ], [ 13.39327, 52.53744 ], [ 13.3936, 52.53757 ], [ 13.40155, 52.53982 ], [ 13.40357, 52.53715 ], [ 13.40372, 52.53719 ], [ 13.40465, 52.53727 ], [ 13.4048, 52.53726 ], [ 13.4059, 52.53736 ], [ 13.40608, 52.53701 ] ], "type": "LineString" } ], "activities": [ { "type": "start", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "end_time": 1554804329, "end_date_time": null, "distance": 0, "driving_time": 0, "preparation_time": 0, "waiting_time": 0, "load_after": [ 0 ] }, { "type": "service", "id": "s-2", "location_id": "13.393364_52.525851", "address": { "location_id": "13.393364_52.525851", "lat": 52.525851, "lon": 13.393364 }, "arr_time": 1554804743, "arr_date_time": null, "end_time": 1554804743, "end_date_time": null, "waiting_time": 0, "distance": 1884, "driving_time": 414, "preparation_time": 0, "load_before": [ 0 ], "load_after": [ 1 ] }, { "type": "service", "id": "s-1", "location_id": "13.375854_52.537338", "address": { "location_id": "13.375854_52.537338", "lat": 52.537338, "lon": 13.375854 }, "arr_time": 1554805251, "arr_date_time": null, "end_time": 1554805329, "end_date_time": null, "waiting_time": 78, "distance": 4205, "driving_time": 922, "preparation_time": 0, "load_before": [ 1 ], "load_after": [ 2 ] }, { "type": "end", "location_id": "berlin", "address": { "location_id": "berlin", "lat": 52.537, "lon": 13.406 }, "arr_time": 1554806036, "arr_date_time": null, "distance": 7376, "driving_time": 1629, "preparation_time": 0, "waiting_time": 0, "load_before": [ 2 ] } ] } ], "unassigned": { "services": [], "shipments": [], "breaks": [], "details": [] } } }
Routing
The Routing API calculates the best path connecting two or more points, where the meaning of ''best'' depends on the vehicle profile and use case. Besides path coordinates it can return turn-by-turn instructions, elevation, path details and other useful information about the route.
Use our API Explorer to explore the Routing API.
Geocoding
Geocoding describes the process of transforming an textual address representation to a coordinate (latitude,longitude). For example the conversion from Berlin to 52.5170365,13.3888599.
Reverse geocoding converts a coordinate to a textual address representation or place name. Find out more about Geocoding itself on Wikipedia.
Isochrones
An isochrone of a location is ''a line connecting points at which a vehicle arrives at the same time'', see Wikipedia. With the same API you can also calculate isodistances, just use the parameter distance_limit instead of time_limit`.
Some possible areas in which this API may be useful to you:
- real estate analysis
- realtors
- vehicle scheduling
- geomarketing
- reach of electric vehicles
- transport planning
- logistics (distribution and retail network planning)
See the clients section in the main documentation, and our API explorer.
Map Matching
You can snap measured GPS points typically as GPX files to a digital road network to e.g. clean data or attach certain data like elevation or turn instructions to it.
See the clients section in the main documentation, and our API explorer.
The cost for one request depends on the number of GPS location and is documented here.
One request should not exceed the Map Matching API location limit depending on the package, see the pricing in our dashboard.
Clustering
It solves the “capacity clustering problem” by assigning a set of customers to a given number of distinct groups (called clusters). The API “clusters” by minimizing the total distance from each individual customer to its designated group median. It can also consider minimum and maximum capacity restrictions for each group.
Clustering can be used in many practical applications. For example, it can help to plan territories, i.e. territory optimization for field teams with large territories for field workers, or to solve large vehicle routing problems (VRP).
Try Clustering in our API Explorer!
The idea is to divide a certain number of customers, a pre-specified number of clusters. As already written above, a distribution is sought that minimizes the total cost (e.g. distance or time or a function of distance and time). We currently support two approaches.
You can simply define a certain number of clusters via configuration ("clustering" with empty set of "clusters") and additionally how many customers should be in such a cluster. This is defined by an upper and lower limit ("min_quantity" and "max_quantity). The algorithm then searches for suitable clusters and divides the customers into these clusters.
You can explicitly define clusters via "clusters". In this way, each individual cluster can be defined. This approach not only allows each cluster to have its own capacity upper and lower bound, but each cluster can also be assigned a fixed cluster center. In contrast to 1. the algorithm then does not search for a suitable center, but assigns the customers given the fixed centers to each cluster. Note that if you define clusters explicitly, any configuration of "clustering" will be overwritten by these explicit clusters.
Custom Model
A custom model allows you to modify the default routing behavior of a vehicle profile by specifying a set of rules in JSON language. There are three JSON properties to change a profile: priority, speed and distance_influence that are described in great detail in the next sections and you can get a quick overview in this example-driven blog post.
But first we will give an introductory example for each of these JSON properties. Let's start with speed:
{
"speed": [{
"if": "road_class == MOTORWAY",
"limit_to": "90"
}]
}As you might have already guessed this limits the speed on motorways to 90km/h. Changing the speed will of course change the travel time, but at the same time this makes other road classes more likely as well, so you can use this model to avoid motorways.
You can immediately try this out in the Browser on GraphHopper Maps. GraphHopper Maps offers an interactive text editor to comfortably enter custom models. You can open it by pressing the gear icon in the top left corner. It will check the syntax of your custom model and mark errors in red. You can press Ctrl+Space or Alt+Enter to retrieve auto-complete suggestions. Pressing Ctrl+Enter will send a routing request for the custom model you entered. To disable the custom model you click the gear icon again.
In the second example we show how to avoid certain road classes without changing the travel time:
{
"priority": [{
"if": "road_class == LIVING_STREET || road_class == RESIDENTIAL || road_class == UNCLASSIFIED",
"multiply_by": "0.1"
}]
}This example avoids certain smaller streets. View it in GraphHopper Maps.
The third example shows how to prefer shortest paths:
{
"distance_influence": 200
}View this example in GraphHopper Maps.
There is a fourth JSON property areas that allows you to define areas that can then be used in the if or else_if conditions for speed and priority. Please read more about this and the other properties below and try some examples in GraphHopper Maps with the help of this blog post.
Custom Profiles
You can create routing profiles that are customized to your needs. You can take advantage of all the modelling options described in the Custom Model section and use the created custom profile (prefix cp_) with our Routing, Matrix and Route Optimization APIs.
Notes
- The geographic coverage of each custom profile is restricted and varies based on the selected package.
- This feature is available starting with the premium package.
A curl example to create a profile:
curl -X POST -H "Content-Type: application/json" "https://graphhopper.com/api/1/profiles?key=YOUR_KEY" -d '{"bounds":{"bbox":[11.45462,48.00954,11.77322,48.2076]},"custom_model":{"priority":[{"if":"road_class == MOTORWAY","multiply_by":"0"}]},"profile":"car"}'To quickly test your custom_model you can use the Routing API where a different custom model can be specified in every request. Or use GraphHopper Maps and click the gear button.
Creating custom profiles using the API Explorer
Besides using the /profiles endpoint directly you can also create custom profiles using our API explorer.
- Visit the API explorer.
- Set your API key with the "API key" button.
- Now copy and paste the JSON to create a custom profile into the input window. To get started you can use the already pre-filled example, which will create a custom profile that excludes motorways and is limited to the Munich area.
- Click "Send". This creates a custom profile. Copy the returned
idfrom the output window (it starts withcp_). - To try this profile out you change the drop down to "Optimization API", pick the first example and replace
carin"profile": "car"(vehicle_typessection) with the profileidyou just copied and click "Send".
The optimized route no longer uses motorways. Keep in mind that this is a simple example and the custom model language is a lot more powerful than this with which you can avoid, exclude or prefer certain areas or road classes and a lot more. Make sure you read the Custom Model section to learn about all the details.
Note that you can use the profile id just as well for the /matrix or /route endpoint. E.g. select "Routing API" and use the profile id in the request.