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:
-
Identify changes: Determine what changes have occurred in the dataset (e.g., new jobs, cancellations).
-
Update the dataset: Modify the existing dataset to reflect these changes.
-
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:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
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.