Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Pick-up and Delivery Routing
  • Driver resource constraints
  • Shift hours and overtime

Pick-up and Delivery Routing

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terminology
      • Use case guide
      • Planning AI concepts
      • Integration
      • Constraints
      • Understanding the API
      • Demo datasets
      • Input datasets
        • Model configuration
        • Model input
        • Planning window
      • Input validation
      • Output datasets
        • Metadata
        • Model output
        • Input metrics
        • Key performance indicators (KPIs)
      • Routing with Timefold’s maps service
      • Metrics and optimization goals
    • Driver resource constraints
      • Lunch breaks and personal appointments
      • Route optimization
      • Shift hours and overtime
      • Driver capacity
    • Job service constraints
      • Time windows and opening hours
      • Skills
      • Multi-day schedules and movable stops
      • Dependencies between stops
      • Priority jobs and optional jobs
      • Stop service level agreement (SLA)
      • Job requirements and tags
        • Job required drivers
        • Job pooling
        • Prohibit job combinations
        • Maximum time burden
        • Tags
    • Recommendations
      • Job time window recommendations
      • Stop time window recommendations
    • Real-time planning
      • Real-time planning: pinning stops
    • Changelog
    • Upgrading to the latest versions
    • Feature requests

Shift hours and overtime

Vehicle drivers work shifts for a maximum number of hours. They start and end at specific times, and work on jobs throughout the day.

Sometimes, overtime might be necessary to complete all the jobs or to limit repeat travel to faraway or difficult to reach stops.

Shifts typically start at the driver’s location, however, labor regulations might permit starting and ending shifts at other locations, including the first and last job of the shift.

This guide explains how to manage shift times with the following examples:

  • 1. Shift start and end
  • 2. Overtime
  • 3. The first travel doesn’t count
  • 4. The last travel doesn’t count

1. Shift start and end

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 Manage tenant, 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.

A driver shift represents a time interval when the driver is available to be assigned stops. A shift is typically one working day. Drivers work for a maximum number of hours, starting at or after a specific time.

For example, a nine to five shift starts at 09:00 and ends at 17:00, for a total of eight hours (ignoring breaks).

Driver shifts are represented by shifts, and include startLocation, endLocation, minStartTime, and maxEndTime:

{
  "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"
        }
      ]
    }
  ]
}

shifts are added for each shift the driver works:

{
  "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"
        }

      ]
    }
  ]
}

minStartTime is the earliest time a shift can start.

maxEndTime is the latest time a shift can end.

All travel and stops occur between the start and end times.

startLocation is the location the driver will start from to the first stop. This could be their home or the depot.

endLocation is the location the driver will drive to after the final stop. This could be their home or the depot. If no endLocation is provided, Timefold defaults to the startLocation.

1.1. Shift start and end example

In the following example, Carl works from 09:00 (minStartTime) until 17:00 (maxEndTime) on Monday.

Carl is assigned four stops.

Including travel time and the stop durations, Carl arrives home at 14:14, which is well before the maxEndTime of 17:00.

driver shift start and end basic
  • 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 -d@sample.json
{
  "config": {
    "run": {
      "name": "Shift hours 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"
          }
        ]
      }
    ],
    "jobs": [
      {
        "id": "Job A",
        "stops": [
          {
            "id": "A1",
            "location": [33.78592, -84.46136],
            "duration": "PT20M"
          },
          {
            "id": "A2",
            "location": [33.72757, -83.96354],
            "duration": "PT20M"
          }
        ]
      },
      {
        "id": "Job B",
        "stops": [
          {
            "id": "B1",
            "location": [34.11110, -84.43002],
            "duration": "PT20M"
          },
          {
            "id": "B2",
            "location": [33.48594, -84.26560],
            "duration": "PT20M"
          }
        ]
      }
    ]
  }
}
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": "506b0f41-1522-4720-9446-3f7bce172ab2",
    "parentId": null,
    "originId": "506b0f41-1522-4720-9446-3f7bce172ab2",
    "name": "Shift hours example",
    "submitDateTime": "2025-07-16T08:03:25.48538455Z",
    "startDateTime": "2025-07-16T08:03:31.123471426Z",
    "activeDateTime": "2025-07-16T08:03:35.633743705Z",
    "completeDateTime": "2025-07-16T08:08:35.830395749Z",
    "shutdownDateTime": "2025-07-16T08:08:36.293093206Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-14068soft",
    "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": "B1",
                "arrivalTime": "2027-02-01T10:02:52Z",
                "startServiceTime": "2027-02-01T10:02:52Z",
                "departureTime": "2027-02-01T10:22:52Z",
                "effectiveServiceDuration": "PT20M",
                "travelTimeFromPreviousStandstill": "PT1H2M52S",
                "travelDistanceMetersFromPreviousStandstill": 69415,
                "kind": "STOP"
              },
              {
                "id": "A1",
                "arrivalTime": "2027-02-01T11:08:56Z",
                "startServiceTime": "2027-02-01T11:08:56Z",
                "departureTime": "2027-02-01T11:28:56Z",
                "effectiveServiceDuration": "PT20M",
                "travelTimeFromPreviousStandstill": "PT46M4S",
                "travelDistanceMetersFromPreviousStandstill": 52509,
                "kind": "STOP"
              },
              {
                "id": "B2",
                "arrivalTime": "2027-02-01T12:14:10Z",
                "startServiceTime": "2027-02-01T12:14:10Z",
                "departureTime": "2027-02-01T12:34:10Z",
                "effectiveServiceDuration": "PT20M",
                "travelTimeFromPreviousStandstill": "PT45M14S",
                "travelDistanceMetersFromPreviousStandstill": 48856,
                "kind": "STOP"
              },
              {
                "id": "A2",
                "arrivalTime": "2027-02-01T13:25:38Z",
                "startServiceTime": "2027-02-01T13:25:38Z",
                "departureTime": "2027-02-01T13:45:38Z",
                "effectiveServiceDuration": "PT20M",
                "travelTimeFromPreviousStandstill": "PT51M28S",
                "travelDistanceMetersFromPreviousStandstill": 48636,
                "kind": "STOP"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT3H54M28S",
              "travelTimeFromStartLocationToFirstStop": "PT1H2M52S",
              "travelTimeBetweenStops": "PT2H22M46S",
              "travelTimeFromLastStopToEndLocation": "PT28M50S",
              "totalTravelDistanceMeters": 250935,
              "travelDistanceFromStartLocationToFirstStopMeters": 69415,
              "travelDistanceBetweenStopsMeters": 150001,
              "travelDistanceFromLastStopToEndLocationMeters": 31519,
              "endLocationArrivalTime": "2027-02-01T14:14:28Z",
              "overtime": "PT0S"
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "stops": 4,
    "drivers": 1,
    "driverShifts": 1
  },
  "kpis": {
    "totalTravelTime": "PT3H54M28S",
    "travelTimeFromStartLocationToFirstStop": "PT1H2M52S",
    "travelTimeBetweenStops": "PT2H22M46S",
    "travelTimeFromLastStopToEndLocation": "PT28M50S",
    "totalTravelDistanceMeters": 250935,
    "travelDistanceFromStartLocationToFirstStopMeters": 69415,
    "travelDistanceBetweenStopsMeters": 150001,
    "travelDistanceFromLastStopToEndLocationMeters": 31519,
    "totalUnassignedStops": 0,
    "totalAssignedStops": 4,
    "totalActivatedDrivers": 1,
    "totalOvertime": "PT0S"
  }
}

2. Overtime

What happens if Carl works too long and overtime is forbidden?

If Carl’s assigned stops force him to work until 18:00 but his shift has a maxEndTime of 17:00, the schedule is infeasible. The model penalizes the amount of time that Carl finishes his shift too late as a hard constraint by one hour. Timefold is automatically incentivized to assign the stop to other drivers or potentially to leave some stops unassigned.

driver shift overtime

However, if overtime is allowed, Carl can work longer, but it’s undesirable.

To allow for overtime, use maxSoftEndTime for the normal end of his shift (17:00) and maxEndTime for the end of the potential overtime period. The potential overtime period is between maxSoftEndTime and maxEndTime.

Carl could work one hour of overtime by adding a maxSoftEndTime of 17:00 and a maxEndTime of 18:00.

{
  "shifts": [
     {
       "id": "Carl Mon",
       "startLocation": [33.68786, -84.18487],
       "minStartTime": "2027-02-01T09:00:00Z",
       "maxSoftEndTime": "2027-02-01T17:00:00Z",
       "maxEndTime": "2027-02-01T18:00:00Z"
     }
   ]
 }

The Preferred shift end time soft constraint is invoked when a driver shift’s end-of-route arrival time exceeds the shift’s maximum soft end time. The constraint adds a soft penalty to the dataset score that is equivalent to the number of seconds between the maxSoftEndTime and the time the driver arrives at the end location for their shift, incentivizing Timefold to minimize overtime by completing shifts within preferred time windows..

Stops can still be scheduled even if doing so breaks this constraint, but Timefold is incentivized to use the route plan with the best score.

3. The first travel doesn’t count

In some cases, the employer doesn’t have to pay for the travel to the first stop. Instead, it comes out of the employee’s personal time, regardless if the employee lives near the first stop or on the other side of the region.

In this case, Carl needs to leave home in time to arrive at the first stop. If the stop is one hour away, they will need to leave home at 08:00 to arrive at 09:00.

minFirstStopArrivalTime sets the time Carl needs to arrive at his first stop.

However, the amount of time Carl has to travel before his first stop starts can be limited with minStartTime. For instance, by setting minStartTime to the earliest time Carl could be expected to leave for the first stop.

3.1. The first travel doesn’t count example

In the following example, minFirstStopArrivalTime is 09:00 and minStartTime is 07:00.

first travel does not count

To disregard the travel time to the first stop, use minFirstStopArrivalTime instead of minStartTime. If Carl needs to arrive at his first stop at 09:00, set minFirstStopArrivalTime to 09:00:

{
  "shifts": [
    {
      "id": "Carl Mon",
      "startLocation": [33.68786, -84.18487],
      "minStartTime": "2027-02-01T07:00:00Z",
      "minFirstStopArrivalTime": "2027-02-01T09:00:00Z",
      "maxEndTime": "2027-02-01T17:00:00Z"
    }
  ]
}

To limit the amount of travel time Carl could be asked to complete to arrive at his first stop, use minStartTime and minFirstStopArrivalTime.

Timefold is incentivized to schedule as many stops as possible. If no limits are placed on travel before the shift starts, drivers could be assigned to start their shifts at stops a long distance away from their start location, requiring them to travel for a long time before their shifts even start.

If there’s a stop on an island with a long travel time by ferry, Timefold assigns that travel during Carl’s personal time. To arrive at the island stop at 09:00, Carl must depart at 06:00. Adding a minStartTime of 07:00 limits the time Carl must leave for the first stop.

{
  "shifts": [
    {
      "id": "Carl Mon",
      "startLocation": [33.68786, -84.18487],
      "minStartTime": "2027-02-01T07:00:00Z",
      "minFirstStopArrivalTime": "2027-02-01T09:00:00Z",
      "maxEndTime": "2027-02-01T17:00:00Z"
    }
  ]
}

Now, Carl’s travel time will not start before 7:00. Carl departs at 7:00 to arrive at the island stop at 10:00.

4. The last travel doesn’t count

Similarly, the employer doesn’t always have to pay for travel back home from the last stop.

For example, Carl must finish his last stop at 17:00, regardless of how much time it takes him to get home.

maxLastStopDepartureTime sets the latest time Carl can depart from his last stop.

However, the amount of time Carl has to travel after his last stop can be limited with maxEndTime. For instance, by setting maxEndTime to the latest time Carl could be expected to arrive home.

driver shift last stop departure

To disregard the travel time from the last stop, use maxLastStopDepartureTime instead of maxEndTime.

{
  "shifts": [
    {
      "id": "Carl Mon",
      "startLocation": [33.68786, -84.18487],
      "minStartTime": "2027-02-01T09:00:00Z",
      "maxLastStopDepartureTime": "2027-02-01T17:00:00Z"
    }
  ]
}

To limit the amount of travel time Carl could be asked to complete after his last stop, use maxEndTime and maxLastStopDepartureTime.

{
  "shifts": [
    {
      "id": "Carl Mon",
      "startLocation": [33.68786, -84.18487],
      "minStartTime": "2027-02-01T09:00:00Z",
      "maxLastStopDepartureTime": "2027-02-01T17:00:00Z",
      "maxEndTime": "2027-02-01T19:00:00Z"
    }
  ]
}

Next

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

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