Plan last-mile deliveries
One depot in southeast Calgary, two vans, twelve parcels. Some customers want a morning delivery
(09:00 to 12:00), some an afternoon one (13:00 to 17:00), and the rest take any time. Each parcel
has a size in parcels under loadDemands, and each van carries at most 20, so neither van can
take everything.
What to read in the answer:
- Which van got which parcels, and in what order:
routes[].visits[], byshipmentLabel. visits[].startTimeis when the driver starts handing over the parcel, inside its window.transitions[].waitDurationis time a van spends early at a stop, waiting for its window to open.metrics.totalCostiscostPerHourtimes each van's day pluscostPerKilometertimes its distance. The solver minimizes it, which is why it keeps routes compact and days short.
Send it
Save the body below as last-mile-deliveries.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 @last-mile-deliveries.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: 14:00:00Z is 08:00 in Calgary 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": 8,
"timeout": "10s",
"model": {
"globalStartTime": "2026-10-13T14:00:00Z",
"globalEndTime": "2026-10-14T02:00:00Z",
"shipments": [
{
"label": "parcel-101",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.039, "longitude": -114.079 },
"duration": "300s",
"timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "2" }
}
},
{
"label": "parcel-102",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.033, "longitude": -114.07 },
"duration": "240s"
}
],
"loadDemands": {
"parcels": { "amount": "1" }
}
},
{
"label": "parcel-103",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.036, "longitude": -114.019 },
"duration": "360s",
"timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "4" }
}
},
{
"label": "parcel-104",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.053, "longitude": -114.04 },
"duration": "300s",
"timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "3" }
}
},
{
"label": "parcel-105",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.053, "longitude": -114.087 },
"duration": "240s"
}
],
"loadDemands": {
"parcels": { "amount": "2" }
}
},
{
"label": "parcel-106",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.057, "longitude": -114.099 },
"duration": "420s",
"timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "5" }
}
},
{
"label": "parcel-107",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.02, "longitude": -114.106 },
"duration": "240s",
"timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "1" }
}
},
{
"label": "parcel-108",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.013, "longitude": -114.095 },
"duration": "300s"
}
],
"loadDemands": {
"parcels": { "amount": "3" }
}
},
{
"label": "parcel-109",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.033, "longitude": -114.043 },
"duration": "300s",
"timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "2" }
}
},
{
"label": "parcel-110",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.065, "longitude": -114.037 },
"duration": "420s",
"timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "6" }
}
},
{
"label": "parcel-111",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.074, "longitude": -114.056 },
"duration": "300s"
}
],
"loadDemands": {
"parcels": { "amount": "2" }
}
},
{
"label": "parcel-112",
"deliveries": [
{
"arrivalLocation": { "latitude": 51.032, "longitude": -114.11 },
"duration": "240s",
"timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
}
],
"loadDemands": {
"parcels": { "amount": "1" }
}
}
],
"vehicles": [
{
"label": "van-1",
"startLocation": { "latitude": 51.001, "longitude": -114.007 },
"endLocation": { "latitude": 51.001, "longitude": -114.007 },
"startTimeWindows": [{ "startTime": "2026-10-13T14:00:00Z", "endTime": "2026-10-13T14:30:00Z" }],
"endTimeWindows": [{ "endTime": "2026-10-14T00:00:00Z" }],
"loadLimits": {
"parcels": { "maxLoad": "20" }
},
"costPerHour": 30,
"costPerKilometer": 0.6
},
{
"label": "van-2",
"startLocation": { "latitude": 51.001, "longitude": -114.007 },
"endLocation": { "latitude": 51.001, "longitude": -114.007 },
"startTimeWindows": [{ "startTime": "2026-10-13T14:00:00Z", "endTime": "2026-10-13T14:30:00Z" }],
"endTimeWindows": [{ "endTime": "2026-10-14T00:00:00Z" }],
"loadLimits": {
"parcels": { "maxLoad": "20" }
},
"costPerHour": 30,
"costPerKilometer": 0.6
}
]
}
}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.