Delivery optimization

Assign your stops to your vehicles and order each vehicle's stops, so the fleet drives as little as possible while meeting delivery windows, vehicle capacities and working hours. This is the vehicle routing problem with time windows (VRPTW).

How it works

You describe the vehicles (where they start and end, how much they carry, when they work, which skills they have) and the stops (where they are, how long each takes, how many units it gets, when it can be served, which skills it needs). Lanewise finds the routes with the least travel time on the road network and returns, for each vehicle, its ordered stops with arrival and departure times. Stops that no vehicle can serve are returned in unassigned with a reason.

  • Times are ISO 8601 datetimes, such as 2026-10-01T08:00:00+02:00; a datetime without an offset is UTC. The response uses the offset of the first datetime in your request, or UTC when you send none. Without any datetime, vehicles leave now.
  • Durations are in seconds and distances in meters.
  • Capacity: when at least one vehicle has a capacity, each stop's demand counts against it (1 unit by default) and vehicles without a capacity carry any amount. Without capacities, demand is ignored.
  • End: a vehicle without end finishes at its last stop. For a round trip, set end to the same location as start.
  • Skills: a stop with skills is only served by a vehicle that has all of them, e.g. fridge for chilled goods.

Optimization requires the plan-routes permission. When a stop, start or end is a POI, the key also needs read-pois. Each call counts as one request towards your rate limit.

Limits

  • Name
    Stops
    Description

    Between 1 and 50 per request. To plan more, cluster them first and optimize each cluster.

  • Name
    Vehicles
    Description

    Between 1 and 3 per request.

  • Name
    Time windows
    Description

    Up to 5 per stop; they must not overlap. A stop's service time can be up to 24 hours.

Errors

Besides the usual errors, optimization can answer:

  • Name
    422
    Description

    The body is invalid, a POI does not exist in your team, or a location cannot be reached by road with the chosen profile. The message says which.

  • Name
    502
    Description

    The routing service failed to answer. Retry in a moment.

  • Name
    503
    Description

    Routing is not available right now, or is busy. Retry later.

503 Service Unavailable

{ "message": "Routing is not available yet. Please try again later or contact [email protected]." }

POST/v1/routes/optimize

Optimize deliveries

Assign the stops to the vehicles and order them. Requires the plan-routes permission, plus read-pois when you use POIs.

Required attributes

  • Name
    vehicles
    Type
    array
    Description

    1 to 3 vehicles.

  • Name
    vehicles[].start
    Type
    object
    Description

    Where the vehicle starts: { "latitude", "longitude" } or { "poi_id" }.

  • Name
    stops
    Type
    array
    Description

    1 to 50 stops. Each is { "latitude", "longitude" } or { "poi_id" }.

Optional attributes

  • Name
    profile
    Type
    string
    Description

    driving-car (default), driving-hgv for trucks, cycling-regular or foot-walking.

  • Name
    vehicles[].id
    Type
    string
    Description

    Your name for the vehicle, unique, up to 100 characters. Defaults to vehicle-1, vehicle-2…

  • Name
    vehicles[].end
    Type
    object
    Description

    Where the vehicle finishes. Omit it to finish at the last stop.

  • Name
    vehicles[].capacity
    Type
    integer
    Description

    Most units the vehicle carries.

  • Name
    vehicles[].time_window
    Type
    array
    Description

    Working hours as [start, end] datetimes.

  • Name
    vehicles[].skills
    Type
    array
    Description

    Up to 20 skills, such as fridge or tail-lift.

  • Name
    stops[].id
    Type
    string
    Description

    Your reference for the stop, unique, up to 100 characters. Echoed back.

  • Name
    stops[].service
    Type
    integer
    Description

    Seconds spent at the stop. Defaults to 0.

  • Name
    stops[].demand
    Type
    integer
    Description

    Units delivered at the stop. Defaults to 1; only counts when a vehicle has a capacity.

  • Name
    stops[].time_windows
    Type
    array
    Description

    When the stop can be served: up to 5 non-overlapping [start, end] pairs of datetimes. A vehicle arriving early waits.

  • Name
    stops[].skills
    Type
    array
    Description

    Skills the vehicle needs, up to 20.

Request

POST
/v1/routes/optimize
curl https://api.lanewise.app/v1/routes/optimize \
  -H "X-API-KEY: {YOUR_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "profile": "driving-car",
    "vehicles": [
      {
        "id": "van-1",
        "start": { "latitude": 41.3870, "longitude": 2.1701 },
        "end": { "latitude": 41.3870, "longitude": 2.1701 },
        "capacity": 10,
        "time_window": ["2026-10-01T08:00:00+02:00", "2026-10-01T14:00:00+02:00"]
      }
    ],
    "stops": [
      {
        "id": "order-1",
        "latitude": 41.4036,
        "longitude": 2.1744,
        "service": 300,
        "demand": 4,
        "time_windows": [["2026-10-01T09:00:00+02:00", "2026-10-01T10:00:00+02:00"]]
      },
      { "id": "order-2", "poi_id": "01JGZ8Q3N5T9B2C4D6F8H0K2M4", "service": 120 },
      { "id": "order-3", "latitude": 41.4145, "longitude": 2.1527, "demand": 20 }
    ]
  }'

Response

{
  "data": {
    "summary": {
      "vehicles": 1,
      "stops": 3,
      "assigned": 2,
      "unassigned": 1,
      "distance": 5400.5,
      "duration": 900,
      "service": 420,
      "waiting_time": 2400
    },
    "routes": [
      {
        "vehicle": "van-1",
        "stops": 2,
        "distance": 5400.5,
        "duration": 900,
        "service": 420,
        "waiting_time": 2400,
        "load": 5,
        "start_at": "2026-10-01T08:00:00+02:00",
        "end_at": "2026-10-01T09:12:00+02:00",
        "geometry": {
          "type": "LineString",
          "coordinates": [[2.1701, 41.387], [2.1649, 41.3917], [2.1744, 41.4036], [2.1701, 41.387]]
        },
        "steps": [
          {
            "type": "start",
            "latitude": 41.387,
            "longitude": 2.1701,
            "arrival": "2026-10-01T08:00:00+02:00",
            "departure": "2026-10-01T08:00:00+02:00",
            "waiting_time": 0,
            "service": 0,
            "distance": 0,
            "duration": 0,
            "load": 5
          },
          {
            "type": "stop",
            "index": 1,
            "id": "order-2",
            "poi_id": "01JGZ8Q3N5T9B2C4D6F8H0K2M4",
            "latitude": 41.3917,
            "longitude": 2.1649,
            "arrival": "2026-10-01T08:03:00+02:00",
            "departure": "2026-10-01T08:05:00+02:00",
            "waiting_time": 0,
            "service": 120,
            "distance": 900,
            "duration": 180,
            "load": 4
          },
          {
            "type": "stop",
            "index": 0,
            "id": "order-1",
            "poi_id": null,
            "latitude": 41.4036,
            "longitude": 2.1744,
            "arrival": "2026-10-01T08:20:00+02:00",
            "departure": "2026-10-01T09:05:00+02:00",
            "waiting_time": 2400,
            "service": 300,
            "distance": 2700,
            "duration": 480,
            "load": 0
          },
          {
            "type": "end",
            "latitude": 41.387,
            "longitude": 2.1701,
            "arrival": "2026-10-01T09:12:00+02:00",
            "departure": "2026-10-01T09:12:00+02:00",
            "waiting_time": 0,
            "service": 0,
            "distance": 5400.5,
            "duration": 900,
            "load": 0
          }
        ]
      }
    ],
    "unassigned": [
      {
        "index": 2,
        "id": "order-3",
        "poi_id": null,
        "latitude": 41.4145,
        "longitude": 2.1527,
        "reason": "Its demand exceeds the capacity of every vehicle."
      }
    ]
  }
}

Response

  • Name
    summary
    Type
    object
    Description

    Totals for the whole fleet: vehicles with a route, stops, assigned and unassigned stops, and the distance, driving duration, service and waiting_time of all routes.

  • Name
    routes[].vehicle
    Type
    string
    Description

    The id of the vehicle. Vehicles without stops have no route.

  • Name
    routes[].load
    Type
    integer | null
    Description

    Units loaded at the start. null when no vehicle has a capacity.

  • Name
    routes[].start_at / end_at
    Type
    datetime
    Description

    When the vehicle leaves its start and reaches its end (or last stop).

  • Name
    routes[].geometry
    Type
    object
    Description

    The route as a GeoJSON LineString, coordinates as [longitude, latitude].

  • Name
    routes[].steps
    Type
    array
    Description

    The start, each stop in visiting order, and the end. Every step has its position, arrival and departure times, waiting_time (early arrivals wait for the time window), service, the distance and driving duration so far, and the load on board after it. Stops also have their index in your request, id and poi_id.

  • Name
    unassigned
    Type
    array
    Description

    Stops no vehicle can serve, with a reason: the demand exceeds every capacity, no vehicle has the required skills, the time windows fall outside every vehicle's working hours, or no vehicle can reach it in time with the capacity and hours left.