Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Pick-up and Delivery Routing
  • Job service constraints
  • Time windows and opening hours

Pick-up and Delivery Routing

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terms
      • Planning AI concepts
      • Demo datasets
      • Validation
      • Routing with Timefold’s maps service
      • Metrics and optimization goals
    • Driver resource constraints
      • Lunch breaks and personal appointments
      • Route optimization
      • Shift hours and overtime
    • Job service constraints
      • Time windows and opening hours
      • Skills
      • Movable stops and multi-day schedules
      • Dependencies between stops
      • Priority stops and optional stops
      • Job requirements and tags
        • Job required drivers
        • Job pooling
        • Prohibit job combinations
        • Maximum time burden
        • Driver capacity
        • Tags
    • Changelog
    • Upgrading to the latest versions
    • Feature requests

Time windows and opening hours

In pick-up and delivery routing jobs have multiple stops. Typically, there is a pick-up stop and a delivery stop (there can also be service stops in between), for instance, a parcel must be picked up from location A and delivered to location B.

The stops must occur in a specific order and often within specific time windows (also known as opening hours).

Time windows specify when a stop can be made. If the parcel won’t be at location A until midday (and as such can’t be picked up until midday), there is no reason to arrive at location before midday. Similarly, if the parcel is needed by a specific time at location B, it would not be acceptable to arrive later than the required time.

When assigning time windows to stops, if the time windows are too narrow and don’t provide much flexibility for when drivers can complete the stop, the route plan could include lots of wait times between stops that leave drivers idle.

At the same time, if the time windows are too broad, this may inconvenience customers as they must be available at the location for a longer period of time.

It may be necessary to experiment to find the right balance for your scenarios.

This guide explains time windows with the following examples:

  • Required latest arrival time

  • Required latest departure time

Prerequisites

Learn how to configure an API Key to run the examples in this guide:
  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.

  3. Create a new API key or use an existing one. Ensure the list of models for the API key contains the Pick-up and Delivery Routing model.

In the examples, replace <API_KEY> with the API Key you just copied.

1. Required latest arrival time

Every stop in a job can include timeWindows that specify when a stop can be made:

{
  "jobs": [
    {
      "id": "Job A",
      "stops": [
        {
          "id": "A1",
          "location": [33.78592, -84.46136],
          "duration": "PT20M",
          "timeWindows": [
            {
              "minStartTime": "2027-02-01T10:00:00Z",
              "maxStartTime": "2027-02-01T16:00:00Z",
              "maxEndTime": "2027-02-01T17:00:00Z"
            }
          ]
        }
      ]
    }
  ]
}

At a minimum, timeWindows must include a minStartTime and a maxEndTime. They can optionally include a maxStartTime. minStartTime, maxEndTime, and maxStartTime all use ISO 8601 date and time format with offset to UTC format.

  • minStartTime is the earliest time a stop can start.

  • maxStartTime is the latest time a stop can start.

  • maxEndTime is the latest time a stop can end.

When stops have both a minStartTime and a maxStartTime, the stop’s start time will be scheduled between these 2 times (if it can be scheduled).

Stops can have 1 or more time windows, for instance:

  • A single time window on a single day: 09:00 to 17:00.

  • Multiple time windows on a single day: 09:00 to 11:00 and 13:00 to 17:00.

  • A single time window that spans multiple days: February 1st 2027 at 09:00 to February 2nd 2027 at 17:00.

  • Multiple time windows on multiple days: February 1st 2027 at 09:00 to 17:00 and February 2nd 2027 at 09:00 to 17:00.

The Require service max start time hard constraint penalizes the solution with a hard score if the stop is scheduled after the max start time. The hard score penalty is based on the seconds between the actual start and the max start time.

Stops will be left unassigned if they break this constraint.

In the following example there is 1 job (Job A) with 2 stops (Stop A1 and Stop A2).

Both stops have a time window.

Stop A1 has a time window with a minStartTime of 10:00 and a MaxStartTime of 12:00.

Stop A2 has a time window with a minStartTime of 11:00 and a MaxStartTime of 13:00.

Carl arrives at Stop A1’s location at 09:31. Because is early, he waits until the minStartTime of 10:00 for the stop to begin.

Next he arrives at Stop A2’s location at 11:13, which is later than the stop’s minStartTime of 11:00 and earlier than the stop’s maxStartTime of 13:00. He completes the delivery.

require service max start time
  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Required max start time example"
    }
  },
  "modelInput": {
    "drivers": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl Mon",
            "startLocation": [33.68786, -84.18487],
            "endLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          },
          {
            "id": "Carl Tue",
            "startLocation": [33.68786, -84.18487],
            "endLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-02T09:00:00Z",
            "maxEndTime": "2027-02-02T17:00:00Z"
          }
        ]
      }
    ],
    "jobs": [
      {
        "id": "Job A",
        "stops": [
          {
            "id": "A1",
            "location": [33.78592, -84.46136],
            "duration": "PT20M",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T10:00:00Z",
                "maxStartTime": "2027-02-01T12:00:00Z",
                "maxEndTime": "2027-02-01T12:30:00Z"
              }
            ]
          },
          {
            "id": "A2",
            "location": [33.72757, -83.96354],
            "duration": "PT20M",
            "timeWindows": [
              {
                "minStartTime": "2027-02-01T11:00:00Z",
                "maxStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T13:30:00Z"
              }
            ],
            "stopDependencies": [
              {
                "id": "jobA_dep1",
                "precedingStop": "A1"
              }
            ]
          }
        ]
      }
    ]
  }
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans/<ID>
{
  "metadata": {
    "id": "ID",
    "parentId": null,
    "originId": "ID",
    "name": "Required max start time example",
    "submitDateTime": "2025-07-28T05:34:53.293683322Z",
    "startDateTime": "2025-07-28T05:35:04.080685645Z",
    "activeDateTime": "2025-07-28T05:35:08.436202335Z",
    "completeDateTime": "2025-07-28T05:40:08.616942538Z",
    "shutdownDateTime": "2025-07-28T05:40:09.032664754Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-6852soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "drivers": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl Mon",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "A1",
                "arrivalTime": "2027-02-01T09:31:38Z",
                "startServiceTime": "2027-02-01T10:00:00Z",
                "departureTime": "2027-02-01T10:20:00Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT31M38S",
                "travelDistanceMetersFromPreviousStandstill": 33381
              },
              {
                "id": "A2",
                "arrivalTime": "2027-02-01T11:13:44Z",
                "startServiceTime": "2027-02-01T11:13:44Z",
                "departureTime": "2027-02-01T11:33:44Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT53M44S",
                "travelDistanceMetersFromPreviousStandstill": 62124
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H54M12S",
              "travelTimeFromStartLocationToFirstStop": "PT31M38S",
              "travelTimeBetweenStops": "PT53M44S",
              "travelTimeFromLastStopToEndLocation": "PT28M50S",
              "totalTravelDistanceMeters": 127024,
              "travelDistanceFromStartLocationToFirstStopMeters": 33381,
              "travelDistanceBetweenStopsMeters": 62124,
              "travelDistanceFromLastStopToEndLocationMeters": 31519,
              "endLocationArrivalTime": "2027-02-01T12:02:34Z",
              "overtime": "PT0S"
            }
          },
          {
            "id": "Carl Tue",
            "startTime": "2027-02-02T09:00:00Z",
            "itinerary": [],
            "metrics": {
              "totalTravelTime": "PT0S",
              "travelTimeFromStartLocationToFirstStop": "PT0S",
              "travelTimeBetweenStops": "PT0S",
              "travelTimeFromLastStopToEndLocation": "PT0S",
              "totalTravelDistanceMeters": 0,
              "travelDistanceFromStartLocationToFirstStopMeters": 0,
              "travelDistanceBetweenStopsMeters": 0,
              "travelDistanceFromLastStopToEndLocationMeters": 0,
              "overtime": "PT0S"
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "stops": 2,
    "drivers": 1,
    "driverShifts": 2
  },
  "kpis": {
    "totalTravelTime": "PT1H54M12S",
    "travelTimeFromStartLocationToFirstStop": "PT31M38S",
    "travelTimeBetweenStops": "PT53M44S",
    "travelTimeFromLastStopToEndLocation": "PT28M50S",
    "totalTravelDistanceMeters": 127024,
    "travelDistanceFromStartLocationToFirstStopMeters": 33381,
    "travelDistanceBetweenStopsMeters": 62124,
    "travelDistanceFromLastStopToEndLocationMeters": 31519,
    "totalUnassignedStops": 0,
    "totalAssignedStops": 2,
    "totalActivatedDrivers": 1,
    "totalOvertime": "PT0S"
  }
}

2. Required latest departure time

In addition to specifying the earliest and latest arrival times at stop, timeWindows also specify the latest time a stop can end:

{
  "timeWindows": [
    {
      "minStartTime": "2027-02-01T10:00:00Z",
      "maxStartTime": "2027-02-01T16:00:00Z",
      "maxEndTime": "2027-02-01T17:00:00Z"
    }
  ]
}

maxEndTime is the latest time a stop can end.

The Require service max end time hard constraint penalizes the solution with a hard score if a service is scheduled after the max end time. The hard score penalty is based on the seconds between the actual end time and the max end time.

Stops will be left unassigned if they break this constraint.

In the following example there is 1 job (Job A) with 2 stops (Stop A1 and Stop A2).

Both stops have a time window.

Stop A1 has a maxEndTime of 12:30. Stop A2 has a maxEndTime of 13:30.

Stop A1 is scheduled to start at 10:00 and ends at 10:20, which is before its maxEndTime of 12:30. Stop A2 is scheduled to start at 11:13 and ends at 11:33, which is before its maxEndTime of 13:30.

require service max end time

Next

  • See the full API spec or try the online API.

  • © 2025 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default