Run a pickup and delivery courier
Two couriers in Montréal, starting from a hub downtown, with a morning to cover: 09:00 to 11:00.
Six orders, each picked up in one neighbourhood and dropped off in another. A pickup and its
delivery are one shipment, with both pickups and deliveries, so the same courier carries the
order from one to the other, pickup first.
Each order is a number of boxes (loadDemands), and a courier holds at most four at once
(loadLimits). That is what forces the routes to interleave: a courier picks up, drops off, and
picks up again rather than collecting everything first. Three orders have a delivery time window.
order-306 has a penaltyCost of 40, which makes it optional: the solver may leave it out if
serving it would cost more than 40, and it would then be listed in skippedShipments.
What to read in the answer:
visits[].isPickuptells the pickup visit of an order from its delivery.metrics.performedShipmentCountagainst the six orders tells you whether anything was skipped.X-Unmap-Billable-Shipmentscounts only the orders that were delivered.
Send it
Save the body below as pickup-delivery-courier.json and post it to /optimize. Set UNMAP_API_KEY to your API key.
curl -X POST "https://api.unmap.dev/optimize" \
-H "X-API-Key: $UNMAP_API_KEY" \
-H "Content-Type: application/json" \
--data @pickup-delivery-courier.jsonThe body uses straight-line travel at a fixed speed (useGeodesicDistances with geodesicMetersPerSecond), so it needs no road network. Delete those two fields to plan with driving times from the Canada road network instead. Times are UTC: 13:00:00Z is 09:00 in Montréal in October. "timeout": "10s" is the longest solve on the Hobby plan, and the request stays inside Hobby's limits (25 shipments, 5 vehicles). The solver may return a different visit order between runs.
This body is a file in Unmap's conformance suite, which validates it and checks the solver's answers for feasibility. After you change it, send it with "solvingMode": "VALIDATE_ONLY" to check its fields, then solve it to check that your constraints fit together.
{
"useGeodesicDistances": true,
"geodesicMetersPerSecond": 7,
"timeout": "10s",
"model": {
"globalStartTime": "2026-10-13T13:00:00Z",
"globalEndTime": "2026-10-13T23:00:00Z",
"shipments": [
{
"label": "order-301",
"pickups": [
{
"arrivalLocation": { "latitude": 45.522, "longitude": -73.58 },
"duration": "180s"
}
],
"deliveries": [
{
"arrivalLocation": { "latitude": 45.507, "longitude": -73.554 },
"duration": "240s",
"timeWindows": [{ "endTime": "2026-10-13T14:00:00Z" }]
}
],
"loadDemands": {
"boxes": { "amount": "2" }
}
},
{
"label": "order-302",
"pickups": [
{
"arrivalLocation": { "latitude": 45.525, "longitude": -73.6 },
"duration": "180s"
}
],
"deliveries": [
{
"arrivalLocation": { "latitude": 45.492, "longitude": -73.56 },
"duration": "240s"
}
],
"loadDemands": {
"boxes": { "amount": "1" }
}
},
{
"label": "order-303",
"pickups": [
{
"arrivalLocation": { "latitude": 45.543, "longitude": -73.585 },
"duration": "180s"
}
],
"deliveries": [
{
"arrivalLocation": { "latitude": 45.478, "longitude": -73.585 },
"duration": "240s",
"timeWindows": [{ "endTime": "2026-10-13T14:30:00Z" }]
}
],
"loadDemands": {
"boxes": { "amount": "3" }
}
},
{
"label": "order-304",
"pickups": [
{
"arrivalLocation": { "latitude": 45.545, "longitude": -73.62 },
"duration": "180s"
}
],
"deliveries": [
{
"arrivalLocation": { "latitude": 45.544, "longitude": -73.54 },
"duration": "240s"
}
],
"loadDemands": {
"boxes": { "amount": "1" }
}
},
{
"label": "order-305",
"pickups": [
{
"arrivalLocation": { "latitude": 45.52, "longitude": -73.61 },
"duration": "180s"
}
],
"deliveries": [
{
"arrivalLocation": { "latitude": 45.46, "longitude": -73.57 },
"duration": "240s",
"timeWindows": [{ "startTime": "2026-10-13T14:00:00Z", "endTime": "2026-10-13T15:00:00Z" }]
}
],
"loadDemands": {
"boxes": { "amount": "2" }
}
},
{
"label": "order-306",
"pickups": [
{
"arrivalLocation": { "latitude": 45.473, "longitude": -73.617 },
"duration": "180s"
}
],
"deliveries": [
{
"arrivalLocation": { "latitude": 45.5017, "longitude": -73.5673 },
"duration": "240s"
}
],
"loadDemands": {
"boxes": { "amount": "2" }
},
"penaltyCost": 40
}
],
"vehicles": [
{
"label": "courier-1",
"startLocation": { "latitude": 45.5, "longitude": -73.57 },
"endLocation": { "latitude": 45.5, "longitude": -73.57 },
"startTimeWindows": [{ "startTime": "2026-10-13T13:00:00Z", "endTime": "2026-10-13T13:30:00Z" }],
"endTimeWindows": [{ "endTime": "2026-10-13T15:00:00Z" }],
"loadLimits": {
"boxes": { "maxLoad": "4" }
},
"costPerHour": 25,
"costPerKilometer": 0.5
},
{
"label": "courier-2",
"startLocation": { "latitude": 45.5, "longitude": -73.57 },
"endLocation": { "latitude": 45.5, "longitude": -73.57 },
"startTimeWindows": [{ "startTime": "2026-10-13T13:00:00Z", "endTime": "2026-10-13T13:30:00Z" }],
"endTimeWindows": [{ "endTime": "2026-10-13T15:00:00Z" }],
"loadLimits": {
"boxes": { "maxLoad": "4" }
},
"costPerHour": 25,
"costPerKilometer": 0.5
}
]
}
}Next steps
- Optimize API: endpoints, field support, limits and pricing.
- Optimize errors: what a skipped shipment's reason means, and every error and warning.
- Optimize playground: run this request with your key and see the routes on a map.