Real-time planning: reassignment
| This guide details the currently supported real-time planning features. If you are using the Patch (preview) feature, please see Real-time planning with patches |
There are many situations where Real-time planning is necessary.
When a stop takes longer than expected other stops might need to be rescheduled or even assigned to another driver.
Consider the following shift schedule:
Ann has six stops scheduled for the day: Stop E, Stop C, Stop A, Stop B, Stop D and Stop F.
However, Stop C takes much longer than expected.
As a result, Stop A, Stop B, Stop D and Stop F need to be rescheduled.
This guide explains real-time planning: reassignment with the following:
1. Batch schedule: reassignment
Learn how to configure an API Key to run the examples in this guide:
In the examples, replace |
The original schedule was generated from the following input dataset during the regular batch scheduling:
-
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": "Original shift plan: reassignment example"
}
},
"modelInput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.77284, -84.42989],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z"
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.79426, -84.330114],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z"
}
]
}
],
"jobs": [
{
"id": "Job 1",
"stops": [
{
"id": "Stop A",
"name": "Stop A",
"location": [33.74648, -84.46461],
"duration": "PT30M"
},
{
"id": "Stop B",
"name": "Stop B",
"location": [33.65207, -84.46496],
"duration": "PT30M"
}
]
},
{
"id": "Job 2",
"stops": [
{
"id": "Stop C",
"name": "Stop C",
"location": [33.77911, -84.49644],
"duration": "PT30M"
},
{
"id": "Stop D",
"name": "Stop D",
"location": [33.65979, -84.46366],
"duration": "PT30M"
}
]
},
{
"id": "Job 3",
"stops": [
{
"id": "Stop E",
"name": "Stop E",
"location": [ 33.78468, -84.48469],
"duration": "PT30M"
},
{
"id": "Stop F",
"name": "Stop F",
"location": [ 33.67966, -84.30062 ],
"duration": "PT30M"
}
]
},
{
"id": "Job 4",
"stops": [
{
"id": "Stop G",
"name": "Stop G",
"location": [ 33.73698, -84.34712 ],
"duration": "PT30M"
},
{
"id": "Stop H",
"name": "Stop H",
"location": [ 33.74661, -84.47359 ],
"duration": "PT30M"
}
]
}
]
}
}
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": "Original shift plan: reassignment example",
"submitDateTime": "2026-04-09T17:06:00.955838+02:00",
"startDateTime": "2026-04-09T17:06:01.006547+02:00",
"activeDateTime": "2026-04-09T17:06:01.008361+02:00",
"completeDateTime": "2026-04-09T17:06:31.02371+02:00",
"shutdownDateTime": "2026-04-09T17:06:31.023713+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-6136soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop G",
"arrivalTime": "2027-02-01T09:10:21Z",
"startServiceTime": "2027-02-01T09:10:21Z",
"departureTime": "2027-02-01T09:40:21Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT10M21S",
"travelDistanceMetersFromPreviousStandstill": 8629,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop H",
"arrivalTime": "2027-02-01T09:54:26Z",
"startServiceTime": "2027-02-01T09:54:26Z",
"departureTime": "2027-02-01T10:24:26Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT14M5S",
"travelDistanceMetersFromPreviousStandstill": 11743,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT30M25S",
"travelTimeFromStartLocationToFirstStop": "PT10M21S",
"travelTimeBetweenStops": "PT14M5S",
"travelTimeFromLastStopToEndLocation": "PT5M59S",
"totalTravelDistanceMeters": 25355,
"travelDistanceFromStartLocationToFirstStopMeters": 8629,
"travelDistanceBetweenStopsMeters": 11743,
"travelDistanceFromLastStopToEndLocationMeters": 4983,
"endLocationArrivalTime": "2027-02-01T10:30:25Z"
}
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop E",
"arrivalTime": "2027-02-01T09:17:11Z",
"startServiceTime": "2027-02-01T09:17:11Z",
"departureTime": "2027-02-01T09:47:11Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT17M11S",
"travelDistanceMetersFromPreviousStandstill": 14324,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop C",
"arrivalTime": "2027-02-01T09:48:41Z",
"startServiceTime": "2027-02-01T09:48:41Z",
"departureTime": "2027-02-01T10:18:41Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT1M30S",
"travelDistanceMetersFromPreviousStandstill": 1250,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop A",
"arrivalTime": "2027-02-01T10:24:17Z",
"startServiceTime": "2027-02-01T10:24:17Z",
"departureTime": "2027-02-01T10:54:17Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT5M36S",
"travelDistanceMetersFromPreviousStandstill": 4671,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop B",
"arrivalTime": "2027-02-01T11:06:53Z",
"startServiceTime": "2027-02-01T11:06:53Z",
"departureTime": "2027-02-01T11:36:53Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT12M36S",
"travelDistanceMetersFromPreviousStandstill": 10498,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop D",
"arrivalTime": "2027-02-01T11:37:55Z",
"startServiceTime": "2027-02-01T11:37:55Z",
"departureTime": "2027-02-01T12:07:55Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT1M2S",
"travelDistanceMetersFromPreviousStandstill": 867,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop F",
"arrivalTime": "2027-02-01T12:26:13Z",
"startServiceTime": "2027-02-01T12:26:13Z",
"departureTime": "2027-02-01T12:56:13Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT18M18S",
"travelDistanceMetersFromPreviousStandstill": 15249,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT1H11M51S",
"travelTimeFromStartLocationToFirstStop": "PT17M11S",
"travelTimeBetweenStops": "PT39M2S",
"travelTimeFromLastStopToEndLocation": "PT15M38S",
"totalTravelDistanceMeters": 59891,
"travelDistanceFromStartLocationToFirstStopMeters": 14324,
"travelDistanceBetweenStopsMeters": 32535,
"travelDistanceFromLastStopToEndLocationMeters": 13032,
"endLocationArrivalTime": "2027-02-01T13:11:51Z"
}
}
]
}
],
"unassignedJobs": []
},
"inputMetrics": {
"jobs": 4,
"stops": 8,
"drivers": 2,
"driverShifts": 2,
"pinnedStops": 0
},
"kpis": {
"totalTravelTime": "PT1H42M16S",
"totalTravelDistanceMeters": 85246,
"totalActivatedDrivers": 2,
"totalUnassignedJobs": 0,
"totalAssignedJobs": 4,
"assignedMandatoryJobs": 4,
"totalUnassignedStops": 0,
"totalAssignedStops": 8,
"assignedMandatoryStops": 8,
"travelTimeFromStartLocationToFirstStop": "PT27M32S",
"travelTimeBetweenStops": "PT53M7S",
"travelTimeFromLastStopToEndLocation": "PT21M37S",
"travelDistanceFromStartLocationToFirstStopMeters": 22953,
"travelDistanceBetweenStopsMeters": 44278,
"travelDistanceFromLastStopToEndLocationMeters": 18015
}
}
modelOutput contains Carl’s shift itinerary.
2. Real-time planning update: reassignment
If the same scenario occurs, and Stop C takes much longer than expected, the input dataset needs to be modified and resubmitted.
Update the duration for Stop C from 30 minutes to 60 minutes.
Add the minStartTravelTime from the most recent planning output dataset (batch or real-time) to each stop.
{
"jobs": [
{
"id": "Job 1",
"stops": [
{
"id": "Stop A",
"name": "Stop A",
"location": [33.74648, -84.46461],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop B",
"name": "Stop B",
"location": [33.65207, -84.46496],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
},
{
"id": "Job 2",
"stops": [
{
"id": "Stop C",
"name": "Stop C",
"location": [33.77911, -84.49644],
"duration": "PT60M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop D",
"name": "Stop D",
"location": [33.65979, -84.46366],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
},
{
"id": "Job 3",
"stops": [
{
"id": "Stop E",
"name": "Stop E",
"location": [ 33.78468, -84.48469],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop F",
"name": "Stop F",
"location": [ 33.67966, -84.30062 ],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
},
{
"id": "Job 4",
"stops": [
{
"id": "Stop G",
"name": "Stop G",
"location": [ 33.73698, -84.34712 ],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Stop H",
"name": "Stop H",
"location": [ 33.74661, -84.47359 ],
"duration": "PT30M",
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
]
}
]
}
Add the itineraries to Carl’s and Ann’s shifts, including the stop IDs and the stop kind:
{
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.77284, -84.42989],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T11:00:00Z",
"itinerary":[
{
"id": "Stop G",
"kind": "STOP"
},
{
"id": "Stop H",
"kind": "STOP"
}
]
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.79426, -84.330114],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z",
"itinerary":[
{
"id": "Stop E",
"kind": "STOP"
},
{
"id": "Stop C",
"kind": "STOP"
},
{
"id": "Stop A",
"kind": "STOP"
},
{
"id": "Stop B",
"kind": "STOP"
},
{
"id": "Stop D",
"kind": "STOP"
},
{
"id": "Stop F",
"kind": "STOP"
}
]
}
]
}
]
}
Freeze the departure times for stops that have already occurred and that drivers have begun traveling to by adding freezeTime:
{
"modelInput": {
"freezeTime": "2027-02-01T10:20:00Z"
}
}
Because Ann finished Stop C at 10:18 and is already traveling to Stop A at the freezeTime, this will keep Stop A scheduled after Stop C with a new arrival time.
Submit the updated input dataset to generate the new real-time plan.
-
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": "Original shift plan: reassignment example"
}
},
"modelInput": {
"freezeTime": "2027-02-01T10:20:00Z",
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.77284, -84.42989],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z",
"itinerary":[
{
"id": "Stop G",
"kind": "STOP"
},
{
"id": "Stop H",
"kind": "STOP"
}
]
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.79426, -84.330114],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:30:00Z",
"itinerary":[
{
"id": "Stop E",
"kind": "STOP"
},
{
"id": "Stop C",
"kind": "STOP"
},
{
"id": "Stop A",
"kind": "STOP"
},
{
"id": "Stop B",
"kind": "STOP"
},
{
"id": "Stop D",
"kind": "STOP"
},
{
"id": "Stop F",
"kind": "STOP"
}
]
}
]
}
],
"jobs": [
{
"id": "Job 1",
"stops": [
{
"id": "Stop A",
"name": "Stop A",
"location": [33.74648, -84.46461],
"duration": "PT30M"
},
{
"id": "Stop B",
"name": "Stop B",
"location": [33.65207, -84.46496],
"duration": "PT30M"
}
]
},
{
"id": "Job 2",
"stops": [
{
"id": "Stop C",
"name": "Stop C",
"location": [33.77911, -84.49644],
"duration": "PT60M"
},
{
"id": "Stop D",
"name": "Stop D",
"location": [33.65979, -84.46366],
"duration": "PT30M"
}
]
},
{
"id": "Job 3",
"stops": [
{
"id": "Stop E",
"name": "Stop E",
"location": [ 33.78468, -84.48469],
"duration": "PT30M"
},
{
"id": "Stop F",
"name": "Stop F",
"location": [ 33.67966, -84.30062 ],
"duration": "PT30M"
}
]
},
{
"id": "Job 4",
"stops": [
{
"id": "Stop G",
"name": "Stop G",
"location": [ 33.73698, -84.34712 ],
"duration": "PT30M"
},
{
"id": "Stop H",
"name": "Stop H",
"location": [ 33.74661, -84.47359 ],
"duration": "PT30M"
}
]
}
]
}
}
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": "Original shift plan: reassignment example",
"submitDateTime": "2026-04-10T17:00:10.026997+02:00",
"startDateTime": "2026-04-10T17:00:10.071052+02:00",
"activeDateTime": "2026-04-10T17:00:10.072466+02:00",
"completeDateTime": "2026-04-10T17:00:40.086433+02:00",
"shutdownDateTime": "2026-04-10T17:00:40.086435+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-7414soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop G",
"arrivalTime": "2027-02-01T09:10:21Z",
"startServiceTime": "2027-02-01T09:10:21Z",
"departureTime": "2027-02-01T09:40:21Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT10M21S",
"travelDistanceMetersFromPreviousStandstill": 8629,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop H",
"arrivalTime": "2027-02-01T09:54:26Z",
"startServiceTime": "2027-02-01T09:54:26Z",
"departureTime": "2027-02-01T10:24:26Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT14M5S",
"travelDistanceMetersFromPreviousStandstill": 11743,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop A",
"arrivalTime": "2027-02-01T10:25:26Z",
"startServiceTime": "2027-02-01T10:25:26Z",
"departureTime": "2027-02-01T10:55:26Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT1M",
"travelDistanceMetersFromPreviousStandstill": 830,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop B",
"arrivalTime": "2027-02-01T11:08:02Z",
"startServiceTime": "2027-02-01T11:08:02Z",
"departureTime": "2027-02-01T11:38:02Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT12M36S",
"travelDistanceMetersFromPreviousStandstill": 10498,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT54M37S",
"travelTimeFromStartLocationToFirstStop": "PT10M21S",
"travelTimeBetweenStops": "PT27M41S",
"travelTimeFromLastStopToEndLocation": "PT16M35S",
"totalTravelDistanceMeters": 45515,
"travelDistanceFromStartLocationToFirstStopMeters": 8629,
"travelDistanceBetweenStopsMeters": 23071,
"travelDistanceFromLastStopToEndLocationMeters": 13815,
"endLocationArrivalTime": "2027-02-01T11:54:37Z"
}
}
]
},
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Stop E",
"arrivalTime": "2027-02-01T09:17:11Z",
"startServiceTime": "2027-02-01T09:17:11Z",
"departureTime": "2027-02-01T09:47:11Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT17M11S",
"travelDistanceMetersFromPreviousStandstill": 14324,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop C",
"arrivalTime": "2027-02-01T09:48:41Z",
"startServiceTime": "2027-02-01T09:48:41Z",
"departureTime": "2027-02-01T10:48:41Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT1M30S",
"travelDistanceMetersFromPreviousStandstill": 1250,
"minStartTravelTime": "2027-02-01T00:00:00Z",
"load": [],
"pinned": true,
"kind": "STOP"
},
{
"id": "Stop D",
"arrivalTime": "2027-02-01T11:05:01Z",
"startServiceTime": "2027-02-01T11:05:01Z",
"departureTime": "2027-02-01T11:35:01Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT16M20S",
"travelDistanceMetersFromPreviousStandstill": 13610,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
},
{
"id": "Stop F",
"arrivalTime": "2027-02-01T11:53:19Z",
"startServiceTime": "2027-02-01T11:53:19Z",
"departureTime": "2027-02-01T12:23:19Z",
"effectiveServiceDuration": "PT30M",
"travelTimeFromPreviousStandstill": "PT18M18S",
"travelDistanceMetersFromPreviousStandstill": 15249,
"minStartTravelTime": "2027-02-01T10:20:00Z",
"load": [],
"kind": "STOP"
}
],
"metrics": {
"totalTravelTime": "PT1H8M57S",
"travelTimeFromStartLocationToFirstStop": "PT17M11S",
"travelTimeBetweenStops": "PT36M8S",
"travelTimeFromLastStopToEndLocation": "PT15M38S",
"totalTravelDistanceMeters": 57465,
"travelDistanceFromStartLocationToFirstStopMeters": 14324,
"travelDistanceBetweenStopsMeters": 30109,
"travelDistanceFromLastStopToEndLocationMeters": 13032,
"endLocationArrivalTime": "2027-02-01T12:38:57Z"
}
}
]
}
],
"unassignedJobs": []
},
"inputMetrics": {
"jobs": 4,
"stops": 8,
"drivers": 2,
"driverShifts": 2,
"pinnedStops": 4
},
"kpis": {
"totalTravelTime": "PT2H3M34S",
"totalTravelDistanceMeters": 102980,
"totalActivatedDrivers": 2,
"totalUnassignedJobs": 0,
"totalAssignedJobs": 4,
"assignedMandatoryJobs": 4,
"totalUnassignedStops": 0,
"totalAssignedStops": 8,
"assignedMandatoryStops": 8,
"travelTimeFromStartLocationToFirstStop": "PT27M32S",
"travelTimeBetweenStops": "PT1H3M49S",
"travelTimeFromLastStopToEndLocation": "PT32M13S",
"travelDistanceFromStartLocationToFirstStopMeters": 22953,
"travelDistanceBetweenStopsMeters": 53180,
"travelDistanceFromLastStopToEndLocationMeters": 26847
}
}
modelOutput contains Ann’s and Carl’s updated shift itineraries.
Stop C took much longer than expected, Stop A and Stop B have been reassigned to Carl’s shift. Stop D and Stop F are rescheduled on Ann’s shift.
Next
-
See the full API spec or try the online API.
-
Learn about real-time planning.
-
Real-time planning with pinned stops.
-
Real-time planning with extended stops.