Skip to content

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[], by shipmentLabel.
  • visits[].startTime is when the driver starts handing over the parcel, inside its window.
  • transitions[].waitDuration is time a van spends early at a stop, waiting for its window to open.
  • metrics.totalCost is costPerHour times each van's day plus costPerKilometer times 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.json

The 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