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
      • 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

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.

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:

  • Shift start and end

  • The first travel doesn’t count

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. Shift start and end

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 8 hours (ignoring breaks).

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

{
  "drivers": [
    {
      "id": "Carl",
      "shifts": [
        {
          "id": "Carl-2027-02-01",
          "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-2027-02-01",
          "startLocation": [33.68786, -84.18487],
          "endLocation": [33.68786, -84.18487],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
        {
          "id": "Carl-2027-02-02",
          "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.

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

Carl is assigned 4 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 [email protected]
{
  "config": {
    "run": {
      "name": "Shift hours example"
    }
  },
  "modelInput": {
    "drivers": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "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",
            "stopDependencies": [
              {
                "id": "jobA_dep1",
                "precedingStop": "A1"
              }
            ]
          }
        ]
      },
      {
        "id": "Job B",
        "stops": [
          {
            "id": "B1",
            "location": [34.11110, -84.43002],
            "duration": "PT20M"
          },
          {
            "id": "B2",
            "location": [33.48594, -84.26560],
            "duration": "PT20M",
            "stopDependencies": [
              {
                "id": "jobB_dep1",
                "precedingStop": "B1"
              }
            ]
          }
        ]
      }
    ]
  }
}
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-2027-02-01",
            "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. 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 1 hour away, he 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 can be limited with minStartTime. For instance, by setting minStartTime to the earliest time Carl could be expected to leave for the first stop.

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

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

minStartTime is optional with minFirstStopArrivalTime, however, Timefold is incentivised to maximize use of free travel time, to fit more stops during the normal shift hours, which could lead to Carl being assigned a first stop that requires him to travel for a long time to arrive at the stop on time.

For instance, if there’s a stop on an island with a long travel time by ferry, Timefold assigns that travel during Carl’s personal time. But to arrive at the island stop at 9:00, Carl must depart at 6:00. Adding a minStartTime of 07:00 limits the time Carl must leave for the first stop.

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