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 with patches
  • Real-time planning: reassignment (using patches)

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
      • Real-time planning: extended stop
      • Real-time planning: reassignment
      • Real-time planning: no show
      • Real-time planning: driver ill
    • Real-time planning with patches
      • Real-time planning: pinning stops (using patches)
      • Real-time planning: extended stop (using patches)
      • Real-time planning: reassignment (using patches)
      • Real-time planning: no show (using patches)
      • Real-time planning: driver ill (using patches)
    • Changelog
    • Upgrading to the latest versions
    • Feature requests

Real-time planning: reassignment (using patches)

This guide describes functionality that relies on the Patch feature, which is currently only available as a preview feature. If you’d like early access to this feature, please Contact us. For information about real-time planning without the Patch functionality, see Real-time planning.

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
  • 2. Real-time planning update: reassignment

1. Batch schedule: reassignment

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.

The original schedule was generated from the following input dataset during batch planning:

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

The following shows the required input and the patch operation that will change the original input:

  • Input

  • Patch

{
  "id": "Stop C",
  "name": "Stop C",
  "location": [33.77911, -84.49644],
  "duration": "PT60M"
}
{
  "op": "replace",
  "path": "/jobs/[id=Job 2]/stops/[id=Stop C]/duration",
  "value": "PT60M"
}

The departure times for stops that have already occurred and that drivers have already begun traveling to can be frozen by adding freezeTime and including the time when the freeze is implemented.

The following shows the required input and the patch operation that will change the original input:

  • Input

  • Patch

{
  "modelInput": {
    "freezeTime": "2027-02-01T10:20:00Z"
  }
}
{
  "op": "add",
  "path": "/freezeTime",
  "value": "2027-02-01T10:20:00Z"
}

Submit the full patch to modify the original input dataset to the following endpoint. Replace <ID> with the dataset ID:

v1/route-plans/<ID>/from-patch/

When submitted, solving is triggered to generate a new revision of the plan.

  • Patch

  • Output

Try this example in Timefold Platform by saving this JSON into a file called patch.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/<ID>/from-patch [email protected]
{
  "config": {
    "run": {
      "name": "Real-time planning: reassignment example"
    }
  },
  "patch": [
    {
      "op": "add",
      "path": "/freezeTime",
      "value": "2027-02-01T10:20:00Z"
    },
    {
      "op": "replace",
      "path": "/jobs/[id=Job 2]/stops/[id=Stop C]/duration",
      "value": "PT60M"
    }
  ]
}
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 shifted into 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.

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