Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Field Service Routing
  • Visit service constraints
  • Priority visits and optional visits

Field Service Routing

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terms
      • Planning AI concepts
      • Constraints
      • Understanding the API
      • Demo datasets
      • Planning window
      • Model configuration
      • Configuration overrides
      • Time zones and daylight-saving time (DST)
      • Routing with Timefold’s maps service
      • Input validation
      • Model response
      • Key performance indicators (KPIs)
      • Metrics and optimization goals
    • Vehicle resource constraints
      • Shift hours and overtime
      • Lunch breaks and personal appointments
      • Fairness
      • Route optimization
      • Technician costs
      • Technician ratings
    • Visit service constraints
      • Time windows and opening hours
      • Skills
      • Visit dependencies
      • Visit requirements, area affinity, and tags
      • Multi-vehicle visits
      • Movable visits and multi-day schedules
      • Priority visits and optional visits
      • Visit service level agreement (SLA)
    • 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 (preview)
      • Real-time planning: extended visit (preview)
      • Real-time planning: reassignment (preview)
      • Real-time planning: emergency visit (preview)
      • Real-time planning: no show (Preview)
      • Real-time planning: technician ill (Preview)
      • Real-time planning: pinning visits (preview)
    • Scenarios
      • Long-running visits
    • Changelog
    • Upgrade to the latest version
    • Feature requests

Priority visits and optional visits

The urgency of visits varies.

Some visits are high priority while others are low.

When you are planning in field service routing, you can introduce mandatory visits and optional visits to help prioritize which visits should be assigned as soon as possible and which visits can wait until later.

Mandatory visits are visits with a time window that falls within the current planning window.

Optional visits are visits with a time window that ends outside the current planning window.

You can also give individual visits priority levels to further control the order visits should be assigned.

This guide explains how to manage resource-limited planning and optional visits with the following examples:

  • 1. Assign mandatory visits
  • 2. Too many mandatory visits
  • 3. Assign mandatory visits and optional visits
  • 4. Priority visits
  • 5. Minimize the visit completion risk

1. Assign mandatory visits

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

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

In the following example, the planning window is for February 1st, 2027:

{
  "planningWindow": {
    "startDate": "2027-02-01T00:00:00Z",
    "endDate": "2027-02-02T00:00:00Z"
  }
}

There are 5 visits.

3 of the visits (Visit C, Visit D, and Visit E) have time windows that are in the planning window, making them mandatory visits. 2 of the visits (Visit A and Visit B) have time windows that end after the planning window, making them optional visits.

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-02T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit B",
      "location": [33.74699, -84.02504],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-02T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit C",
      "location": [33.88664, -84.28118],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit D",
      "location": [33.71030, -84.05439],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit E",
      "location": [33.87673, -84.26024],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    }
  ]
}

The Require scheduling mandatory visits is a medium constraint that penalizes unassigned mandatory visits, incentivizing Timefold to schedule as many mandatory visits as possible.

In the example, there is only time for 3 of the visits to be scheduled during the planning window.

Because Visit C, Visit D, and Visit E have time windows that end on February 1st, 2027, during the planning window, they are scheduled. Visit A and Visit B, which have time windows that end after the planning window, are left unscheduled.

assign mandatory visits
  • 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": "Assign mandatory visits 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 A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-04T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.74699, -84.02504],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-04T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit D",
        "location": [33.71030, -84.05439],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit E",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "planningWindow": {
      "startDate": "2027-02-01T00:00:00Z",
      "endDate": "2027-02-02T00:00:00Z"
    }
  }
}
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",
    "name": "Assign mandatory visits example",
    "submitDateTime": "2025-02-25T05:08:07.977085453Z",
    "startDateTime": "2025-02-25T05:08:34.443085661Z",
    "activeDateTime": "2025-02-25T05:08:34.775097095Z",
    "completeDateTime": "2025-02-25T05:13:35.123701244Z",
    "shutdownDateTime": "2025-02-25T05:13:35.313892244Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-1005088soft",
    "tags": [
      "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 D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:16:24Z",
                "startServiceTime": "2027-02-01T09:16:24Z",
                "departureTime": "2027-02-01T11:16:24Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT16M24S",
                "travelDistanceMetersFromPreviousStandstill": 15551,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:53:09Z",
                "startServiceTime": "2027-02-01T11:53:09Z",
                "departureTime": "2027-02-01T13:53:09Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT36M45S",
                "travelDistanceMetersFromPreviousStandstill": 43211,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:57:34Z",
                "startServiceTime": "2027-02-01T13:57:34Z",
                "departureTime": "2027-02-01T15:57:34Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT4M25S",
                "travelDistanceMetersFromPreviousStandstill": 2712,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H24M48S",
              "travelTimeFromStartLocationToFirstVisit": "PT16M24S",
              "travelTimeBetweenVisits": "PT41M10S",
              "travelTimeFromLastVisitToEndLocation": "PT27M14S",
              "totalTravelDistanceMeters": 89600,
              "travelDistanceFromStartLocationToFirstVisitMeters": 15551,
              "travelDistanceBetweenVisitsMeters": 45923,
              "travelDistanceFromLastVisitToEndLocationMeters": 28126,
              "endLocationArrivalTime": "2027-02-01T16:24:48Z",
              "technicianCosts": null,
              "overtime": null
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "visits": 5,
    "visitGroups": 0,
    "vehicles": 1,
    "mandatoryVisits": 3,
    "optionalVisits": 2,
    "vehicleShifts": 1
  },
  "kpis": {
    "totalTravelTime": "PT1H24M48S",
    "travelTimeFromStartLocationToFirstVisit": "PT16M24S",
    "travelTimeBetweenVisits": "PT41M10S",
    "travelTimeFromLastVisitToEndLocation": "PT27M14S",
    "totalTravelDistanceMeters": 89600,
    "travelDistanceFromStartLocationToFirstVisitMeters": 15551,
    "travelDistanceBetweenVisitsMeters": 45923,
    "travelDistanceFromLastVisitToEndLocationMeters": 28126,
    "totalUnassignedVisits": 2,
    "totalAssignedVisits": 3,
    "assignedMandatoryVisits": 3,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalTechnicianCosts": null,
    "totalOvertime": null
  }
}

2. Too many mandatory visits

When there are too many mandatory visits to assign during the planning window, mandatory visits will be left unassigned.

From the original example, Visit A’s time window has been changed to end within the current planning window, making it a mandatory visit.

{
  "id": "Visit A",
  "location": [33.77301, -84.43838],
  "serviceDuration": "PT2H",
  "timeWindows": [
    {
      "minStartTime": "2027-02-01T09:00:00Z",
      "maxEndTime": "2027-02-01T17:00:00Z"
    }
  ]
}

This time, mandatory Visit D, Visit C, and Visit A are assigned. However, mandatory Visit E is not assigned, and optional Visit B is also not assigned.

too many mandatory visits
  • 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": "Too many mandatory visits 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 A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.74699, -84.02504],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-04T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit D",
        "location": [33.71030, -84.05439],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit E",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "planningWindow": {
      "startDate": "2027-02-01T00:00:00Z",
      "endDate": "2027-02-02T00:00:00Z"
    }
  }
}
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",
    "name": "Too many mandatory visits example",
    "submitDateTime": "2025-02-25T05:24:25.71765674Z",
    "startDateTime": "2025-02-25T05:24:37.019507815Z",
    "activeDateTime": "2025-02-25T05:24:37.874868846Z",
    "completeDateTime": "2025-02-25T05:29:38.112430598Z",
    "shutdownDateTime": "2025-02-25T05:29:38.363738161Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-10000medium/-505022soft",
    "tags": [
      "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:26:12Z",
                "startServiceTime": "2027-02-01T09:26:12Z",
                "departureTime": "2027-02-01T11:26:12Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT26M12S",
                "travelDistanceMetersFromPreviousStandstill": 29031,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:30:38Z",
                "startServiceTime": "2027-02-01T11:30:38Z",
                "departureTime": "2027-02-01T13:30:38Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT4M26S",
                "travelDistanceMetersFromPreviousStandstill": 2704,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:54:10Z",
                "startServiceTime": "2027-02-01T13:54:10Z",
                "departureTime": "2027-02-01T15:54:10Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT23M32S",
                "travelDistanceMetersFromPreviousStandstill": 24218,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H23M42S",
              "travelTimeFromStartLocationToFirstVisit": "PT26M12S",
              "travelTimeBetweenVisits": "PT27M58S",
              "travelTimeFromLastVisitToEndLocation": "PT29M32S",
              "totalTravelDistanceMeters": 89937,
              "travelDistanceFromStartLocationToFirstVisitMeters": 29031,
              "travelDistanceBetweenVisitsMeters": 26922,
              "travelDistanceFromLastVisitToEndLocationMeters": 33984,
              "endLocationArrivalTime": "2027-02-01T16:23:42Z",
              "technicianCosts": null,
              "overtime": null
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "visits": 5,
    "visitGroups": 0,
    "vehicles": 1,
    "mandatoryVisits": 4,
    "optionalVisits": 1,
    "vehicleShifts": 1
  },
  "kpis": {
    "totalTravelTime": "PT1H23M42S",
    "travelTimeFromStartLocationToFirstVisit": "PT26M12S",
    "travelTimeBetweenVisits": "PT27M58S",
    "travelTimeFromLastVisitToEndLocation": "PT29M32S",
    "totalTravelDistanceMeters": 89937,
    "travelDistanceFromStartLocationToFirstVisitMeters": 29031,
    "travelDistanceBetweenVisitsMeters": 26922,
    "travelDistanceFromLastVisitToEndLocationMeters": 33984,
    "totalUnassignedVisits": 2,
    "totalAssignedVisits": 3,
    "assignedMandatoryVisits": 3,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalTechnicianCosts": null,
    "totalOvertime": null
  }
}

3. Assign mandatory visits and optional visits

When the planning window includes enough time to assign mandatory and optional visits, the Require scheduling mandatory visits constraint will assign as many mandatory visits as possible, then the Prefer scheduling optional visits soft constraint will assign as many optional visits as possible.

Every soft constraint has a weight that can be configured to change the relative importance of the constraint compared to other constraints.

Learn about constraint weights.

In the following example, there are 5 visits. The planning window is for all day February 1st, 2027 and all day February 2nd 2027:

{
  "planningWindow": {
    "startDate": "2027-02-01T00:00:00Z",
    "endDate": "2027-02-03T00:00:00Z"
  }
}

Carl has 2 shifts that cover the planning window:

{
  "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"
    },
    {
      "id": "Carl-2027-02-02",
      "startLocation": [33.68786, -84.18487],
      "minStartTime": "2027-02-02T09:00:00Z",
      "maxEndTime": "2027-02-02T17:00:00Z"
    }
  ]
}

3 of the visits (Visit C, Visit D, and Visit E) have time windows that are in the planning window, making them mandatory visits. 2 of the visits (Visit A and Visit B) have time windows that end after the planning window, making them optional visits.

The mandatory visits are prioritized over optional visits.

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-04T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit B",
      "location": [33.74699, -84.02504],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-04T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit C",
      "location": [33.88664, -84.28118],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit D",
      "location": [33.71030, -84.05439],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit E",
      "location": [33.87673, -84.26024],
      "serviceDuration": "PT2H",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    }
  ]
}
assign mandatory and optional visits

The mandatory visits in this example all have time windows on February 1st, and they are scheduled for February 1st.

The optional visits are scheduled for February 2nd.

If Visit C and Visit E had time windows that extended to February 2nd, the route plan could include optional visits on February 1st.

assign mandatory and optional visits 2
  • 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": "Assign mandatory and optional visits 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"
          },
          {
            "id": "Carl-2027-02-02",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-02T09:00:00Z",
            "maxEndTime": "2027-02-02T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-04T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.74699, -84.02504],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-04T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit D",
        "location": [33.71030, -84.05439],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit E",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT2H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "planningWindow": {
      "startDate": "2027-02-01T00:00:00Z",
      "endDate": "2027-02-03T00:00:00Z"
    }
  }
}
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",
    "name": "Assign mandatory and optional visits example",
    "submitDateTime": "2025-02-25T07:02:34.131265202Z",
    "startDateTime": "2025-02-25T07:02:44.418846349Z",
    "activeDateTime": "2025-02-25T07:02:44.684333186Z",
    "completeDateTime": "2025-02-25T07:07:45.028612891Z",
    "shutdownDateTime": "2025-02-25T07:07:45.305145223Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-12800soft",
    "tags": [
      "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 D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:16:24Z",
                "startServiceTime": "2027-02-01T09:16:24Z",
                "departureTime": "2027-02-01T11:16:24Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT16M24S",
                "travelDistanceMetersFromPreviousStandstill": 15551,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:53:09Z",
                "startServiceTime": "2027-02-01T11:53:09Z",
                "departureTime": "2027-02-01T13:53:09Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT36M45S",
                "travelDistanceMetersFromPreviousStandstill": 43211,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:57:34Z",
                "startServiceTime": "2027-02-01T13:57:34Z",
                "departureTime": "2027-02-01T15:57:34Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT4M25S",
                "travelDistanceMetersFromPreviousStandstill": 2712,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H24M48S",
              "travelTimeFromStartLocationToFirstVisit": "PT16M24S",
              "travelTimeBetweenVisits": "PT41M10S",
              "travelTimeFromLastVisitToEndLocation": "PT27M14S",
              "totalTravelDistanceMeters": 89600,
              "travelDistanceFromStartLocationToFirstVisitMeters": 15551,
              "travelDistanceBetweenVisitsMeters": 45923,
              "travelDistanceFromLastVisitToEndLocationMeters": 28126,
              "endLocationArrivalTime": "2027-02-01T16:24:48Z",
              "technicianCosts": null,
              "overtime": null
            }
          },
          {
            "id": "Carl-2027-02-02",
            "startTime": "2027-02-02T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-02T09:29:56Z",
                "startServiceTime": "2027-02-02T09:29:56Z",
                "departureTime": "2027-02-02T11:29:56Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT29M56S",
                "travelDistanceMetersFromPreviousStandstill": 31493,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-02T12:13:32Z",
                "startServiceTime": "2027-02-02T12:13:32Z",
                "departureTime": "2027-02-02T14:13:32Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT43M36S",
                "travelDistanceMetersFromPreviousStandstill": 49957,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H35M12S",
              "travelTimeFromStartLocationToFirstVisit": "PT29M56S",
              "travelTimeBetweenVisits": "PT43M36S",
              "travelTimeFromLastVisitToEndLocation": "PT21M40S",
              "totalTravelDistanceMeters": 102672,
              "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
              "travelDistanceBetweenVisitsMeters": 49957,
              "travelDistanceFromLastVisitToEndLocationMeters": 21222,
              "endLocationArrivalTime": "2027-02-02T14:35:12Z",
              "technicianCosts": null,
              "overtime": null
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "visits": 5,
    "visitGroups": 0,
    "vehicles": 1,
    "mandatoryVisits": 3,
    "optionalVisits": 2,
    "vehicleShifts": 2
  },
  "kpis": {
    "totalTravelTime": "PT3H",
    "travelTimeFromStartLocationToFirstVisit": "PT46M20S",
    "travelTimeBetweenVisits": "PT1H24M46S",
    "travelTimeFromLastVisitToEndLocation": "PT48M54S",
    "totalTravelDistanceMeters": 192272,
    "travelDistanceFromStartLocationToFirstVisitMeters": 47044,
    "travelDistanceBetweenVisitsMeters": 95880,
    "travelDistanceFromLastVisitToEndLocationMeters": 49348,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 5,
    "assignedMandatoryVisits": 3,
    "assignedOptionalVisits": 2,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalTechnicianCosts": null,
    "totalOvertime": null
  }
}

4. Priority visits

In the previous example 1 of the mandatory visits was left unassigned. Some visits are more important than others and have a higher priority.

Visits can define their priority.

There are 10 built-in priorities.

"1" is the highest priority, and "10" is the lowest priority.

The default built-in priority is "6". These built-in priorities use exponential penalty weights, where priority "1" is 10 times more important than priority "2", priority "2" is 10 times more important than priority "3", and so on.

LOW, NORMAL, HIGH, and CRITICAL priorities have been deprecated. Internally, these deprecated priorities map respectively to 10, 6, 5, and 1.

4.1. Priority visits example

In the following example, there is 1 vehicle shift on February 1st 2027, and there are 5 visits with the following priorities:

  • Visit A: 5

  • Visit B: 10

  • Visit C: 5

  • Visit D: 10

  • Visit E: 1

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT2H",
      "priority": "5",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit B",
      "location": [33.74699, -84.02504],
      "serviceDuration": "PT2H",
      "priority": "10",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-04T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit C",
      "location": [33.88664, -84.28118],
      "serviceDuration": "PT2H",
      "priority": "5",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit D",
      "location": [33.71030, -84.05439],
      "serviceDuration": "PT2H",
      "priority": "10",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    },
    {
      "id": "Visit E",
      "location": [33.87673, -84.26024],
      "serviceDuration": "PT2H",
      "priority": "1",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        }
      ]
    }
  ]
}

Visit E has the highest priority (1) and is assigned as the first visit of the day. Visit C and Visit A both have a priority of 5 and are assigned.

Visit D is a mandatory visit, but its priority is lower than the other priority visits and is left unassigned. Visit B is an optional visit and is left unassigned.

assign high priority visits
  • 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": "Assign high priority visits 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 A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT2H",
        "priority": "5",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.74699, -84.02504],
        "serviceDuration": "PT2H",
        "priority": "10",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-04T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT2H",
        "priority": "5",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit D",
        "location": [33.71030, -84.05439],
        "serviceDuration": "PT2H",
        "priority": "10",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit E",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT2H",
        "priority": "1",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "planningWindow": {
      "startDate": "2027-02-01T00:00:00Z",
      "endDate": "2027-02-02T00:00:00Z"
    }
  }
}
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",
    "name": "Assign high priority visits example",
    "submitDateTime": "2025-02-25T09:12:20.49667869Z",
    "startDateTime": "2025-02-25T09:13:54.643876572Z",
    "activeDateTime": "2025-02-25T09:13:55.099440057Z",
    "completeDateTime": "2025-02-25T09:18:55.501439836Z",
    "shutdownDateTime": "2025-02-25T09:18:55.73524306Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-1medium/-505022soft",
    "tags": [
      "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:26:12Z",
                "startServiceTime": "2027-02-01T09:26:12Z",
                "departureTime": "2027-02-01T11:26:12Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT26M12S",
                "travelDistanceMetersFromPreviousStandstill": 29031,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:30:38Z",
                "startServiceTime": "2027-02-01T11:30:38Z",
                "departureTime": "2027-02-01T13:30:38Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT4M26S",
                "travelDistanceMetersFromPreviousStandstill": 2704,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:54:10Z",
                "startServiceTime": "2027-02-01T13:54:10Z",
                "departureTime": "2027-02-01T15:54:10Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT23M32S",
                "travelDistanceMetersFromPreviousStandstill": 24218,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H23M42S",
              "travelTimeFromStartLocationToFirstVisit": "PT26M12S",
              "travelTimeBetweenVisits": "PT27M58S",
              "travelTimeFromLastVisitToEndLocation": "PT29M32S",
              "totalTravelDistanceMeters": 89937,
              "travelDistanceFromStartLocationToFirstVisitMeters": 29031,
              "travelDistanceBetweenVisitsMeters": 26922,
              "travelDistanceFromLastVisitToEndLocationMeters": 33984,
              "endLocationArrivalTime": "2027-02-01T16:23:42Z",
              "technicianCosts": null,
              "overtime": null
            }
          }
        ]
      }
    ]
  },
  "inputMetrics": {
    "visits": 5,
    "visitGroups": 0,
    "vehicles": 1,
    "mandatoryVisits": 4,
    "optionalVisits": 1,
    "vehicleShifts": 1
  },
  "kpis": {
    "totalTravelTime": "PT1H23M42S",
    "travelTimeFromStartLocationToFirstVisit": "PT26M12S",
    "travelTimeBetweenVisits": "PT27M58S",
    "travelTimeFromLastVisitToEndLocation": "PT29M32S",
    "totalTravelDistanceMeters": 89937,
    "travelDistanceFromStartLocationToFirstVisitMeters": 29031,
    "travelDistanceBetweenVisitsMeters": 26922,
    "travelDistanceFromLastVisitToEndLocationMeters": 33984,
    "totalUnassignedVisits": 2,
    "totalAssignedVisits": 3,
    "assignedMandatoryVisits": 3,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalTechnicianCosts": null,
    "totalOvertime": null
  }
}

4.2. Custom priorities

Optionally, you can override these weights or define custom priorities in the model configuration overrides.

The priorityConfiguration objects require a "priority" (format: string) and a "weight" (format: int) to be set. The "priority" field is the name for the priority override, and the "weight" field indicates the magnitude of impact it will have.

A priority configuration with "weight": 50 will have an impact 5 times the size than a priority with "weight": 10. This means that the solver deems 1 unassigned visit with the higher priority weight equal to 5 unassigned visits with the lower priority weight.

{
  "config": {
    "model": {
      "overrides": {
        "priorityWeights": [
          {
            "priority": "A",
            "weight": 1000
          },
          {
            "priority": "B",
            "weight": 50
          },
          {
            "priority": "C",
            "weight": 15
          },
          {
            "priority": "D",
            "weight": 10
          },
          {
            "priority": "E",
            "weight": 1
          }
        ]
      }
    }
  }
}

5. Minimize the visit completion risk

If a technician falls behind schedule during the day, there is a risk that visits later in the day might not be finished on time and may even need to be rescheduled to another day.

To avoid high priority visits being scheduled late in the day and being at risk of rescheduling, you can define a buffer at the end of the shift in which to avoid assigning visits above a specified priority level.

{
  "config": {
    "model": {
      "overrides": {
        "visitCompletionRiskMinimalTimeToShiftEnd": "PT4H",
        "visitCompletionRiskMinimalPriority": "6"
      }
    }
  }
}

visitCompletionRiskMinimalTimeToShiftEnd sets the time span (in ISO 8601 duration) before the end of the vehicle shift to avoid scheduling high priority visits. Every visit with a scheduled completion before that timespan is considered without completion risk.

visitCompletionRiskMinimalPriority sets the minimum priority level to consider for visit completion risk. The default value if none is provided is 6.

visitCompletionRiskMinimalPriority only accepts the built-in priorities and cannot be used with custom priorities.

The Minimize the visit completion risk soft constraint adds a soft score penalty for any visit with a departure time that exceeds the visitCompletionRiskMinimalTimeToShiftEnd based on the visit priority, incentivizing Timefold to schedule high priority visits as early as possible through the day.

Every soft constraint has a weight that can be configured to change the relative importance of the constraint compared to other constraints.

Learn about constraint weights.

In the following example there are 5 visits scheduled over 2 days.

Without priorities the visits are scheduled as follows:

visit completion risk

With visitCompletionRiskMinimalTimeToShiftEnd set to PT4H, and visitCompletionRiskMinimalPriority set to 3, and the following visit priorities:

  • Visit A: priority 1

  • Visit B: priority 5

  • Visit C: priority 5

  • Visit D: priority 1

  • Visit E: priority 5

Visit A and Visit D are scheduled earlier in the day because they are higher priority than Visit B, Visit C, and Visit E.

visit completion risk 2
  • 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": "Visit risk completion example"
    },
    "model": {
      "overrides": {
        "visitCompletionRiskMinimalTimeToShiftEnd": "PT4H",
        "visitCompletionRiskMinimalPriority": "3"
      }
    }
  },
  "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"
          },
          {
            "id": "Carl-2027-02-02",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-02T09:00:00Z",
            "maxEndTime": "2027-02-02T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT2H",
        "priority": "1"
      },
      {
        "id": "Visit B",
        "location": [33.74699, -84.02504],
        "serviceDuration": "PT2H",
        "priority": "5"
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT2H",
        "priority": "5"
      },
      {
        "id": "Visit D",
        "location": [33.71030, -84.05439],
        "serviceDuration": "PT2H",
        "priority": "1"
      },
      {
        "id": "Visit E",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT2H",
        "priority": "5"
      }
    ]
  }
}
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",
    "originId": "ID",
    "name": "Visit risk completion example",
    "submitDateTime": "2025-11-24T09:11:31.131810802Z",
    "startDateTime": "2025-11-24T09:11:38.133202569Z",
    "activeDateTime": "2025-11-24T09:11:38.227441763Z",
    "completeDateTime": "2025-11-24T09:12:09.125074975Z",
    "shutdownDateTime": "2025-11-24T09:12:09.125079895Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-9873soft",
    "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 A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:29:57Z",
                "startServiceTime": "2027-02-01T09:29:57Z",
                "departureTime": "2027-02-01T11:29:57Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT29M57S",
                "travelDistanceMetersFromPreviousStandstill": 31493,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:53:21Z",
                "startServiceTime": "2027-02-01T11:53:21Z",
                "departureTime": "2027-02-01T13:53:21Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT23M24S",
                "travelDistanceMetersFromPreviousStandstill": 24717,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit E",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T13:57:51Z",
                "startServiceTime": "2027-02-01T13:57:51Z",
                "departureTime": "2027-02-01T15:57:51Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT4M30S",
                "travelDistanceMetersFromPreviousStandstill": 2712,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H25M7S",
              "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
              "travelTimeBetweenVisits": "PT27M54S",
              "travelTimeFromLastVisitToEndLocation": "PT27M16S",
              "totalTravelDistanceMeters": 87048,
              "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
              "travelDistanceBetweenVisitsMeters": 27429,
              "travelDistanceFromLastVisitToEndLocationMeters": 28126,
              "endLocationArrivalTime": "2027-02-01T16:25:07Z"
            }
          },
          {
            "id": "Carl-2027-02-02",
            "startTime": "2027-02-02T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit D",
                "kind": "VISIT",
                "arrivalTime": "2027-02-02T09:16:24Z",
                "startServiceTime": "2027-02-02T09:16:24Z",
                "departureTime": "2027-02-02T11:16:24Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT16M24S",
                "travelDistanceMetersFromPreviousStandstill": 15551,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-02T11:24:26Z",
                "startServiceTime": "2027-02-02T11:24:26Z",
                "departureTime": "2027-02-02T13:24:26Z",
                "effectiveServiceDuration": "PT2H",
                "travelTimeFromPreviousStandstill": "PT8M2S",
                "travelDistanceMetersFromPreviousStandstill": 7287,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT46M6S",
              "travelTimeFromStartLocationToFirstVisit": "PT16M24S",
              "travelTimeBetweenVisits": "PT8M2S",
              "travelTimeFromLastVisitToEndLocation": "PT21M40S",
              "totalTravelDistanceMeters": 44060,
              "travelDistanceFromStartLocationToFirstVisitMeters": 15551,
              "travelDistanceBetweenVisitsMeters": 7287,
              "travelDistanceFromLastVisitToEndLocationMeters": 21222,
              "endLocationArrivalTime": "2027-02-02T13:46:06Z"
            }
          }
        ]
      }
    ],
    "unassignedVisits": []
  },
  "inputMetrics": {
    "visits": 5,
    "visitGroups": 0,
    "visitDependencies": 0,
    "mandatoryVisits": 5,
    "optionalVisits": 0,
    "vehicles": 1,
    "vehicleShifts": 2,
    "visitsWithSla": 0,
    "movableVisits": 5,
    "pinnedVisits": 0
  },
  "kpis": {
    "totalTravelTime": "PT2H11M13S",
    "travelTimeFromStartLocationToFirstVisit": "PT46M21S",
    "travelTimeBetweenVisits": "PT35M56S",
    "travelTimeFromLastVisitToEndLocation": "PT48M56S",
    "totalTravelDistanceMeters": 131108,
    "travelDistanceFromStartLocationToFirstVisitMeters": 47044,
    "travelDistanceBetweenVisitsMeters": 34716,
    "travelDistanceFromLastVisitToEndLocationMeters": 49348,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 5,
    "assignedMandatoryVisits": 5,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100
  },
  "run": {
    "id": "ID",
    "originId": "ID",
    "name": "Visit risk completion example",
    "submitDateTime": "2025-11-24T09:11:31.131810802Z",
    "startDateTime": "2025-11-24T09:11:38.133202569Z",
    "activeDateTime": "2025-11-24T09:11:38.227441763Z",
    "completeDateTime": "2025-11-24T09:12:09.125074975Z",
    "shutdownDateTime": "2025-11-24T09:12:09.125079895Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-9873soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  }
}

Next

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

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

  • Schedule Lunch breaks and personal appointments.

  • Use Time windows to specify visit availability and limit when visits can be scheduled.

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