Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Field Service Routing
  • Real-time planning with patches
  • Real-time planning: extended visit (using patches)

Field Service 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
        • Time zones and daylight-saving time (DST)
      • Routing with Timefold’s maps service
      • Input validation
      • Model response
      • Output datasets
        • Metadata
        • Model output
        • Input metrics
        • Key performance indicators (KPIs)
      • Key performance indicators (KPIs)
      • Metrics and optimization goals
      • Visualizations
    • Vehicle resource constraints
      • Shift hours and overtime
      • Lunch breaks and personal appointments
      • Fairness
      • Route optimization
      • Technician costs
      • Technician ratings
      • Technician coverage area
    • Visit service constraints
      • Time windows and opening hours
      • Skills
      • Visit dependencies
      • Multi-vehicle visits
      • Multi-day schedules and movable visits
      • Priority visits and optional visits
      • Visit service level agreement (SLA)
      • Duration added for first visit on location
      • Visit profit
      • Visit requirements and tags
        • Visit requirements
        • Tags
    • Manual intervention
    • Recommendations
      • Visit time window recommendations
      • Visit group time window recommendations
    • Real-time planning
      • Real-time planning: extended visit
      • Real-time planning: reassignment
      • Real-time planning: emergency visit
      • Real-time planning: no show
      • Real-time planning: technician ill
      • Real-time planning: pinning visits
    • Real-time planning with patches
      • Real-time planning: extended visit (using patches)
      • Real-time planning: reassignment (using patches)
      • Real-time planning: emergency visit (using patches)
      • Real-time planning: no show (using patches)
      • Real-time planning: technician ill (using patches)
      • Real-time planning: pinning visits (using patches)
    • Scenarios
      • Long-running visits
      • Configuring labor law compliance
    • Changelog
    • Upgrade to the latest version
    • Feature requests

Real-time planning: extended visit (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 with patches is necessary.

Sometimes visits take longer than expected.

Consider the following shift schedule:

Carl has three visits scheduled for the day: Visit E, Visit B, and Visit D.

However, Visit E takes much longer than expected.

As a result, Visit B and Visit D need to be rescheduled.

real time planning extended visit

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 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 current model.

In the examples, replace <API_KEY> with the API Key you just copied.

1. Batch schedule: extended visit

Carl’s 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/field-service-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Original shift plan: extended visit example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit E",
        "location": [33.84475, -84.63649],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit B",
        "location": [33.90719, -84.28149],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit D",
        "location":  [33.89351, -84.00649],
        "serviceDuration": "PT1H"
      }
    ]
  }
}
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/field-service-routing/v1/route-plans/<ID>
{
  "metadata": {
    "id": "ID",
    "parentId": null,
    "originId": "ID",
    "name": "Original shift plan: extended visit example",
    "submitDateTime": "2025-09-11T05:58:27.042921076Z",
    "startDateTime": "2025-09-11T05:58:46.636403681Z",
    "activeDateTime": "2025-09-11T05:58:46.732047745Z",
    "completeDateTime": "2025-09-11T05:59:17.179322187Z",
    "shutdownDateTime": "2025-09-11T05:59:17.580697436Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-9547soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:49:00Z",
                "startServiceTime": "2027-02-01T09:49:00Z",
                "departureTime": "2027-02-01T10:49:00Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT49M",
                "travelDistanceMetersFromPreviousStandstill": 54141,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:29:33Z",
                "startServiceTime": "2027-02-01T11:29:33Z",
                "departureTime": "2027-02-01T12:29:33Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT40M33S",
                "travelDistanceMetersFromPreviousStandstill": 42300,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:01:14Z",
                "startServiceTime": "2027-02-01T13:01:14Z",
                "departureTime": "2027-02-01T14:01:14Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT31M41S",
                "travelDistanceMetersFromPreviousStandstill": 34290,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT2H39M7S",
              "travelTimeFromStartLocationToFirstVisit": "PT49M",
              "travelTimeBetweenVisits": "PT1H12M14S",
              "travelTimeFromLastVisitToEndLocation": "PT37M53S",
              "totalTravelDistanceMeters": 165335,
              "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
              "travelDistanceBetweenVisitsMeters": 76590,
              "travelDistanceFromLastVisitToEndLocationMeters": 34604,
              "endLocationArrivalTime": "2027-02-01T14:39:07Z"
            }
          }
        ]
      }
    ],
    "unassignedVisits": []
  },
  "inputMetrics": {
    "visits": 3,
    "visitGroups": 0,
    "vehicles": 1,
    "mandatoryVisits": 3,
    "optionalVisits": 0,
    "vehicleShifts": 1,
    "visitsWithSla": 0
  },
  "kpis": {
    "totalTravelTime": "PT2H39M7S",
    "travelTimeFromStartLocationToFirstVisit": "PT49M",
    "travelTimeBetweenVisits": "PT1H12M14S",
    "travelTimeFromLastVisitToEndLocation": "PT37M53S",
    "totalTravelDistanceMeters": 165335,
    "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
    "travelDistanceBetweenVisitsMeters": 76590,
    "travelDistanceFromLastVisitToEndLocationMeters": 34604,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 3,
    "assignedMandatoryVisits": 3,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100
  }
}

modelOutput contains Carl’s shift itinerary.

2. Real-time planning update: extended visit

The plan needs to be updated to reflect the situation in the field.

Visit E took a total of 4 hours, which is 3 hours longer than expected.

The serviceDuration for Visit E needs to be updated to 4 hours.

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

  • Input

  • Patch

{
  "id": "Visit E",
  "location": [33.84475, -84.63649],
  "serviceDuration": "PT4H",
  "minStartTravelTime": "2027-02-01T00:00:00Z"
}
{
  "op": "replace",
  "path": "/visits/[id=Visit E]/serviceDuration",
  "value": "PT4H"
}

The departure times for visits that have already occurred and that technicians have already begin 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-01T14:10:00Z"
  }
}
{
  "op": "add",
  "path": "/freezeTime",
  "value": "2027-02-01T14:10:00Z"
}

Because Carl finished Visit E at 13:48 and is already traveling to Visit B at the freezeTime, this will keep Visit B scheduled after Visit E with a new arrival time.

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

v1/reoute-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/field-service-routing/v1/route-plans/<ID>/from-patch [email protected]
{
  "config": {
    "run": {
      "name": "Real-time planning: extended visit example"
    }
  },
  "patch": [
    {
      "op": "add",
      "path": "/freezeTime",
      "value": "2027-02-01T14:10:00Z"
    },
    {
      "op": "replace",
      "path": "/visits/[id=Visit E]/serviceDuration",
      "value": "PT4H"
    }
  ]
}
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/field-service-routing/v1/route-plans/<ID>
{
  "metadata": {
    "id": "ID",
    "parentId": "ORIGIN-ID",
    "originId": "ORIGIN-ID",
    "name": "Real-time planning: extended visit example",
    "submitDateTime": "2025-09-11T06:16:09.054382562Z",
    "startDateTime": "2025-09-11T06:16:21.0399514Z",
    "activeDateTime": "2025-09-11T06:16:21.077235415Z",
    "completeDateTime": "2025-09-11T06:16:31.233669592Z",
    "shutdownDateTime": "2025-09-11T06:16:31.652761537Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-10000medium/-7038soft",
    "tags": [
      "system.type:from-patch",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:49:00Z",
                "startServiceTime": "2027-02-01T09:49:00Z",
                "departureTime": "2027-02-01T13:49:00Z",
                "effectiveServiceDuration": "PT4H",
                "travelTimeFromPreviousStandstill": "PT49M",
                "travelDistanceMetersFromPreviousStandstill": 54141,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T14:29:33Z",
                "startServiceTime": "2027-02-01T14:29:33Z",
                "departureTime": "2027-02-01T15:29:33Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT40M33S",
                "travelDistanceMetersFromPreviousStandstill": 42300,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H57M18S",
              "travelTimeFromStartLocationToFirstVisit": "PT49M",
              "travelTimeBetweenVisits": "PT40M33S",
              "travelTimeFromLastVisitToEndLocation": "PT27M45S",
              "totalTravelDistanceMeters": 125661,
              "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
              "travelDistanceBetweenVisitsMeters": 42300,
              "travelDistanceFromLastVisitToEndLocationMeters": 29220,
              "endLocationArrivalTime": "2027-02-01T15:57:18Z"
            }
          }
        ]
      }
    ],
    "unassignedVisits": [
      "Visit D"
    ]
  },
  "inputMetrics": {
    "visits": 3,
    "visitGroups": 0,
    "vehicles": 1,
    "mandatoryVisits": 3,
    "optionalVisits": 0,
    "vehicleShifts": 1,
    "visitsWithSla": 0
  },
  "kpis": {
    "totalTravelTime": "PT1H57M18S",
    "travelTimeFromStartLocationToFirstVisit": "PT49M",
    "travelTimeBetweenVisits": "PT40M33S",
    "travelTimeFromLastVisitToEndLocation": "PT27M45S",
    "totalTravelDistanceMeters": 125661,
    "travelDistanceFromStartLocationToFirstVisitMeters": 54141,
    "travelDistanceBetweenVisitsMeters": 42300,
    "travelDistanceFromLastVisitToEndLocationMeters": 29220,
    "totalUnassignedVisits": 1,
    "totalAssignedVisits": 2,
    "assignedMandatoryVisits": 2,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100
  }
}

modelOutput contains Carl’s updated shift itinerary.

Visit E took much longer than expected, Visit B is scheduled for later in the day, and Visit D must be assigned to another technician or be rescheduled for another day.

See Real-time planning: reassignment (using patches) for more details.

Next

  • See the full API spec or try the online API.

  • Learn more about field service routing from our YouTube playlist.

  • Learn about real-time planning.

  • Real-time planning with pinned visits.

  • Real-time planning and emergency visits.

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