Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Pick-up and Delivery Routing
  • Real-time planning in pick-up and delivery routing

Pick-up and Delivery Routing

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terms
      • Planning AI concepts
      • Constraints
      • 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 jobs and optional jobs
      • Job requirements and tags
        • Job required drivers
        • Job pooling
        • Prohibit job combinations
        • Maximum time burden
        • Driver capacity
        • Tags
    • Real-time planning in pick-up and delivery routing
    • Changelog
    • Upgrading to the latest versions
    • Feature requests

Real-time planning in pick-up and delivery routing

Solving a dataset to generate an optimized route plan is often not a one-time task. In real-world scenarios, conditions can change dynamically, requiring adjustments to the existing route plan. This is where real-time planning is useful. Real-time planning allows you to modify the existing solution based on new information or changes in constraints without starting from scratch.

1. When to use real-time planning

Real-time planning is particularly useful in the following scenarios:

  • New jobs added: When new pick-up and delivery jobs are introduced after the initial optimization.

  • Cancellations: When existing jobs are canceled and need to be removed from the route plan.

  • Driver availability changes: When drivers become unavailable or new drivers are added to the pool.

  • Time window adjustments: When the time windows for pick-ups or deliveries change.

2. How real-time planning works

Real-time planning in the Pick-Up and Delivery Routing model involves the following steps:

  1. Identify changes: Determine what changes have occurred in the dataset (e.g., new jobs, cancellations).

  2. Update the dataset: Modify the existing dataset to reflect these changes.

  3. Re-optimize: Run the optimization process again, using the updated dataset while retaining the existing solution as a starting point.

In order to provide the existing solution as a starting point for real-time planning, you can add an input itinerary to each driver shift in the updated dataset:

{
    "driverShifts": [
        {
            "id": "driver-1",
            "itinerary": [
                {
                    "id": "stop-1",
                    "kind": "STOP"
                },
                {
                    "id": "stop-2",
                    "kind": "STOP"
                },
                {
                    "id": "lunch-break-1",
                    "kind": "BREAK"
                },
                {
                    "id": "stop-3",
                    "kind": "STOP"
                }
            ]
        }
    ]
}

As shown in this example, the itinerary field contains a list of stops and breaks that represent the current route plan for the driver. This itinerary only contains a reference to the stop IDs and break IDs, without any timing or sequencing information. The timing information will be recalculated during the reoptimization. The full break details are still defined in the requiredBreaks section of the driver shift. The full stop details are still defined in the job definitions.

3. Scenario: Adding new jobs

The following scenario should illustrate how real-time planning works in practice. It uses an existing plan as a starting point, extends the dataset with a new job and re-optimizes the plan.

3.1. 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.

3.2. Original dataset

Consider the following example with a single driver and two jobs to schedule.

  • 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": "Real-time planning example"
    }
  },
  "modelInput": {
    "drivers": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann 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": "A",
        "stops": [
          {
            "id": "A1",
            "name": "A1",
            "location": [33.77911, -84.49644],
            "duration": "PT20M"
          },
          {
            "id": "A2",
            "name": "A2",
            "location": [33.65979, -84.46366],
            "duration": "PT20M",
            "stopDependencies": [
              {
                "id": "A1_dep1",
                "precedingStop": "A1"
              }
            ]
          }
        ]
      },
      {
        "id": "B",
        "stops": [
          {
            "id": "B1",
            "name": "B1",
            "location": [33.74648, -84.46461],
            "duration": "PT20M"
          },
          {
            "id": "B2",
            "name": "B2",
            "location": [33.65207, -84.46496],
            "duration": "PT20M",
            "stopDependencies": [
              {
                "id": "B1_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": "ID",
    "originId": "ID",
    "name": "Real-time planning example",
    "submitDateTime": "2025-11-18T10:36:05.393943+01:00",
    "startDateTime": "2025-11-18T10:36:05.534844+01:00",
    "activeDateTime": "2025-11-18T10:36:05.53571+01:00",
    "completeDateTime": "2025-11-18T10:36:35.55112+01:00",
    "shutdownDateTime": "2025-11-18T10:36:35.551122+01:00",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-5179soft",
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "drivers": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann Mon",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "A1",
                "arrivalTime": "2027-02-01T09:36:39Z",
                "startServiceTime": "2027-02-01T09:36:39Z",
                "departureTime": "2027-02-01T09:56:39Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT36M39S",
                "travelDistanceMetersFromPreviousStandstill": 30546
              },
              {
                "id": "B1",
                "arrivalTime": "2027-02-01T10:02:15Z",
                "startServiceTime": "2027-02-01T10:02:15Z",
                "departureTime": "2027-02-01T10:22:15Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT5M36S",
                "travelDistanceMetersFromPreviousStandstill": 4671
              },
              {
                "id": "A2",
                "arrivalTime": "2027-02-01T10:33:49Z",
                "startServiceTime": "2027-02-01T10:33:49Z",
                "departureTime": "2027-02-01T10:53:49Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT11M34S",
                "travelDistanceMetersFromPreviousStandstill": 9640
              },
              {
                "id": "B2",
                "arrivalTime": "2027-02-01T10:54:51Z",
                "startServiceTime": "2027-02-01T10:54:51Z",
                "departureTime": "2027-02-01T11:14:51Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT1M2S",
                "travelDistanceMetersFromPreviousStandstill": 867
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H26M19S",
              "travelTimeFromStartLocationToFirstStop": "PT36M39S",
              "travelTimeBetweenStops": "PT18M12S",
              "travelTimeFromLastStopToEndLocation": "PT31M28S",
              "totalTravelDistanceMeters": 71948,
              "travelDistanceFromStartLocationToFirstStopMeters": 30546,
              "travelDistanceBetweenStopsMeters": 15178,
              "travelDistanceFromLastStopToEndLocationMeters": 26224,
              "endLocationArrivalTime": "2027-02-01T11:46:19Z",
              "overtime": "PT0S"
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "jobs": 2,
    "stops": 4,
    "drivers": 1,
    "driverShifts": 1
  },
  "kpis": {
    "totalTravelTime": "PT1H26M19S",
    "totalTravelDistanceMeters": 71948,
    "totalActivatedDrivers": 1,
    "totalUnassignedJobs": "0",
    "totalAssignedJobs": "2",
    "totalUnassignedStops": 0,
    "totalAssignedStops": 4,
    "totalOvertime": "PT0S",
    "travelTimeFromStartLocationToFirstStop": "PT36M39S",
    "travelTimeBetweenStops": "PT18M12S",
    "travelTimeFromLastStopToEndLocation": "PT31M28S",
    "travelDistanceFromStartLocationToFirstStopMeters": 30546,
    "travelDistanceBetweenStopsMeters": 15178,
    "travelDistanceFromLastStopToEndLocationMeters": 26224
  }
}

3.3. Dataset update and real-time planning

Now, a new job (Job C) needs to be added to the existing plan. To do this, update the original dataset to include the new job and add the existing itinerary to the driver shift.

  • 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": "Real-time planning example - new job"
    }
  },
  "modelInput": {
    "drivers": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann Mon",
            "startLocation": [33.68786, -84.18487],
            "endLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "itinerary": [
              {
                "id": "A1",
                "kind": "STOP"
              },
              {
                "id": "B1",
                "kind": "STOP"
              },
              {
                "id": "A2",
                "kind": "STOP"
              },
              {
                "id": "B2",
                "kind": "STOP"
              }
            ]
          }
        ]
      }
    ],
    "jobs": [
      {
        "id": "A",
        "stops": [
          {
            "id": "A1",
            "name": "A1",
            "location": [33.77911, -84.49644],
            "duration": "PT20M"
          },
          {
            "id": "A2",
            "name": "A2",
            "location": [33.65979, -84.46366],
            "duration": "PT20M",
            "stopDependencies": [
              {
                "id": "A1_dep1",
                "precedingStop": "A1"
              }
            ]
          }
        ]
      },
      {
        "id": "B",
        "stops": [
          {
            "id": "B1",
            "name": "B1",
            "location": [33.74648, -84.46461],
            "duration": "PT20M"
          },
          {
            "id": "B2",
            "name": "B2",
            "location": [33.65207, -84.46496],
            "duration": "PT20M",
            "stopDependencies": [
              {
                "id": "B1_dep1",
                "precedingStop": "B1"
              }
            ]
          }
        ]
      },
      {
        "id": "Job C",
        "stops": [
          {
            "id": "C1",
            "location": [33.78592, -84.46136],
            "duration": "PT20M"
          },
          {
            "id": "C2",
            "location": [33.72757, -83.96354],
            "duration": "PT20M",
            "stopDependencies": [
              {
                "id": "jobC_dep1",
                "precedingStop": "C1"
              }
            ]
          }
        ]
      }
    ]
  }
}
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",
    "originId": "ID",
    "name": "Real-time planning example - new job",
    "submitDateTime": "2025-11-18T10:58:38.118381+01:00",
    "startDateTime": "2025-11-18T10:58:38.212713+01:00",
    "activeDateTime": "2025-11-18T10:58:38.213217+01:00",
    "completeDateTime": "2025-11-18T10:59:08.230731+01:00",
    "shutdownDateTime": "2025-11-18T10:59:08.230734+01:00",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-8235soft",
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "drivers": [
      {
        "id": "Ann",
        "shifts": [
          {
            "id": "Ann Mon",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "C1",
                "arrivalTime": "2027-02-01T09:33:21Z",
                "startServiceTime": "2027-02-01T09:33:21Z",
                "departureTime": "2027-02-01T09:53:21Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT33M21S",
                "travelDistanceMetersFromPreviousStandstill": 27795
              },
              {
                "id": "A1",
                "arrivalTime": "2027-02-01T09:57:21Z",
                "startServiceTime": "2027-02-01T09:57:21Z",
                "departureTime": "2027-02-01T10:17:21Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT4M",
                "travelDistanceMetersFromPreviousStandstill": 3329
              },
              {
                "id": "B1",
                "arrivalTime": "2027-02-01T10:22:57Z",
                "startServiceTime": "2027-02-01T10:22:57Z",
                "departureTime": "2027-02-01T10:42:57Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT5M36S",
                "travelDistanceMetersFromPreviousStandstill": 4671
              },
              {
                "id": "A2",
                "arrivalTime": "2027-02-01T10:54:31Z",
                "startServiceTime": "2027-02-01T10:54:31Z",
                "departureTime": "2027-02-01T11:14:31Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT11M34S",
                "travelDistanceMetersFromPreviousStandstill": 9640
              },
              {
                "id": "B2",
                "arrivalTime": "2027-02-01T11:15:33Z",
                "startServiceTime": "2027-02-01T11:15:33Z",
                "departureTime": "2027-02-01T11:35:33Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT1M2S",
                "travelDistanceMetersFromPreviousStandstill": 867
              },
              {
                "id": "C2",
                "arrivalTime": "2027-02-01T12:32:07Z",
                "startServiceTime": "2027-02-01T12:32:07Z",
                "departureTime": "2027-02-01T12:52:07Z",
                "effectiveServiceDuration": "PT20M",
                "kind": "STOP",
                "travelTimeFromPreviousStandstill": "PT56M34S",
                "travelDistanceMetersFromPreviousStandstill": 47145
              }
            ],
            "metrics": {
              "totalTravelTime": "PT2H17M15S",
              "travelTimeFromStartLocationToFirstStop": "PT33M21S",
              "travelTimeBetweenStops": "PT1H18M46S",
              "travelTimeFromLastStopToEndLocation": "PT25M8S",
              "totalTravelDistanceMeters": 114391,
              "travelDistanceFromStartLocationToFirstStopMeters": 27795,
              "travelDistanceBetweenStopsMeters": 65652,
              "travelDistanceFromLastStopToEndLocationMeters": 20944,
              "endLocationArrivalTime": "2027-02-01T13:17:15Z",
              "overtime": "PT0S"
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "jobs": 3,
    "stops": 6,
    "drivers": 1,
    "driverShifts": 1
  },
  "kpis": {
    "totalTravelTime": "PT2H17M15S",
    "totalTravelDistanceMeters": 114391,
    "totalActivatedDrivers": 1,
    "totalUnassignedJobs": "0",
    "totalAssignedJobs": "3",
    "totalUnassignedStops": 0,
    "totalAssignedStops": 6,
    "totalOvertime": "PT0S",
    "travelTimeFromStartLocationToFirstStop": "PT33M21S",
    "travelTimeBetweenStops": "PT1H18M46S",
    "travelTimeFromLastStopToEndLocation": "PT25M8S",
    "travelDistanceFromStartLocationToFirstStopMeters": 27795,
    "travelDistanceBetweenStopsMeters": 65652,
    "travelDistanceFromLastStopToEndLocationMeters": 20944
  }
}

When solving the updated dataset, Timefold uses the provided itinerary as a starting point and optimizes the route to include the new job. This approach ensures that the existing plan is respected as much as possible while accommodating the new requirements.

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