Visit group time window recommendations
When visits require more than a single technician in a single vehicle, they are known as multi-vehicle visits. Learn more about Multi-vehicle visits.
Visit group recommendations provide a range of recommendations for multi-vehicle visits that include all the technicians who are required for the visits in the visit group.
This guide explains visit group recommendations with the following examples:
Prerequisites
To run the examples in this guide, you need to authenticate with a valid API key for this model:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
Create a new API key or use an existing one. Ensure the list of models for the API key contains the current model.
In the examples, replace <API_KEY>
with the API Key you just copied.
1. Visit group recommendations for unsolved route plans
Continuous planning includes four stages:
-
Historic
The historic stage includes schedules that have already been executed and cannot be changed. -
Published
The published stage includes schedules that have been shared with your customers and technicians and should only be changed in special circumstances. -
Draft
The draft stage includes schedules that can change without impacting customers or technicians. At some point, draft schedules will be solved and moved to the published stage. -
Unplanned
The unplanned stage is too far away to start planning schedules.
When customers request work during a time period that is still in the draft phase, you can use recommendations to provide the customer with time windows to choose from.
The customer’s chosen time window can be added to the draft schedule, which will be optimized before being published.
1.1. The recommendation input dataset
A recommendation input dataset includes the following:
1.1.1. Max number of recommendations per time window
The maximum number of recommendations to make per time window specified in the input dataset.
The recommendation will consider technicians' vehicle shifts that overlap the specified time window and make up to the number of recommendations specified in maxNumberOfRecommendationsPerTimeWindow
.
{
"maxNumberOfRecommendationsPerTimeWindow": 2
}
1.1.2. Fit visit group ID
The ID of the visit group to make the recommendations for. The provided fitVisitGroupID
must match an ID from a visit group in the modelInput
.
{
"fitVisitGroupId": "Group A"
}
1.1.3. Time windows
The time windows to be considered for the recommendation. For instance, 09:00 to 13:00 on February 1, 2027 and 13:00 to 17:00 on February 1, 2027.
Time windows can cover an entire day or even longer, but as time windows get longer, you reduce the number of recommendations and the customer’s options.
{
"timeWindows": [
{
"startTime": "2027-02-01T09:00:00Z",
"endTime": "2027-02-01T13:00:00Z"
},
{
"startTime": "2027-02-01T13:00:00Z",
"endTime": "2027-02-01T17:00:00Z"
}
]
}
1.1.4. Model input
The model input must include the technician’s vehicles and shifts and the visit group the recommendations are for.
To learn more about vehicle shifts and visits, see Shift hours and overtime.
{
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.77301, -84.43899],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
],
"visitGroups": [
{
"id": "Group A",
"visits": [
{
"id": "Visit A1",
"name": "visit group A 1/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H"
},
{
"id": "Visit A2",
"name": "visit group A 2/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H"
}
]
}
]
}
}
1.2. Submit the input dataset
Submit the recommendations input dataset to the API endpoint: /v1/route-plans/recommendations/visit-group-recommend-time-windows
.
The HTTP response provides the recommendations.
-
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/recommendations/visit-group-recommend-time-windows [email protected]
{
"maxNumberOfRecommendationsPerTimeWindow": 2,
"fitVisitGroupId": "Group A",
"timeWindows": [
{
"startTime": "2027-02-01T09:00:00Z",
"endTime": "2027-02-01T13:00:00Z"
},
{
"startTime": "2027-02-01T13:00:00Z",
"endTime": "2027-02-01T17:00:00Z"
}
],
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.77301, -84.43899],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
],
"visitGroups": [
{
"id": "Group A",
"visits": [
{
"id": "Visit A1",
"name": "visit group A 1/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H"
},
{
"id": "Visit A2",
"name": "visit group A 2/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H"
}
]
}
]
}
}
{
"recommendations": [
{
"scoreDiff": "0hard/20000medium/-86080soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/-285soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-81240soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-4555soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T09:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T13:00:00Z"
},
"vehicleShifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:09:52Z",
"startServiceTime": "2027-02-01T09:26:56Z",
"departureTime": "2027-02-01T11:26:56Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT9M52S",
"travelDistanceMetersFromPreviousStandstill": 9447,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT19M3S",
"travelTimeFromStartLocationToFirstVisit": "PT9M52S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT9M11S",
"totalTravelDistanceMeters": 16563,
"travelDistanceFromStartLocationToFirstVisitMeters": 9447,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 7116,
"endLocationArrivalTime": "2027-02-01T11:36:07Z",
"technicianCosts": 0
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:26:56Z",
"startServiceTime": "2027-02-01T09:26:56Z",
"departureTime": "2027-02-01T11:26:56Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT26M56S",
"travelDistanceMetersFromPreviousStandstill": 29982,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT56M52S",
"travelTimeFromStartLocationToFirstVisit": "PT26M56S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 64677,
"travelDistanceFromStartLocationToFirstVisitMeters": 29982,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T11:56:52Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT1H15M55S",
"travelTimeFromStartLocationToFirstVisit": "PT36M48S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT39M7S",
"totalTravelDistanceMeters": 81240,
"travelDistanceFromStartLocationToFirstVisitMeters": 39429,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 41811,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 49.7,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
},
{
"scoreDiff": "0hard/20000medium/-86441soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/-646soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-81240soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-4555soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T13:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T17:00:00Z"
},
"vehicleShifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:09:52Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT9M52S",
"travelDistanceMetersFromPreviousStandstill": 9447,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT19M3S",
"travelTimeFromStartLocationToFirstVisit": "PT9M52S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT9M11S",
"totalTravelDistanceMeters": 16563,
"travelDistanceFromStartLocationToFirstVisitMeters": 9447,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 7116,
"endLocationArrivalTime": "2027-02-01T15:09:11Z",
"technicianCosts": 0
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:26:56Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT26M56S",
"travelDistanceMetersFromPreviousStandstill": 29982,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT56M52S",
"travelTimeFromStartLocationToFirstVisit": "PT26M56S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 64677,
"travelDistanceFromStartLocationToFirstVisitMeters": 29982,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T15:29:56Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT1H15M55S",
"travelTimeFromStartLocationToFirstVisit": "PT36M48S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT39M7S",
"totalTravelDistanceMeters": 81240,
"travelDistanceFromStartLocationToFirstVisitMeters": 39429,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 41811,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 49.94,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
}
]
}
1.3. The recommendations
In this example, there are 2 recommendations.
-
09:00-13:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.
-
13:00-17:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.

Each recommendation includes the following:
1.3.1. ScoreDiff
The difference implementing the recommendation will make to the final score of the optimized solution:
{
"scoreDiff": "0hard/10000medium/-117593soft"
}
1.3.2. Constraint scores
The score for each constraint, for instance:
{
"score": "0hard/0medium/-111336soft",
"constraintName": "Minimize travel distance"
}
1.3.3. The time window
The time window the recommendation is for:
{
"timeWindow": {
"minStartTime": "2027-02-01T09:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T13:00:00Z"
}
}
1.3.4. The vehicle shifts
The vehicle shift the recommendation applies to and the shift itinerary, metrics, and KPIs.
{
"vehicleShifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:09:52Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT9M52S",
"travelDistanceMetersFromPreviousStandstill": 9447,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT19M3S",
"travelTimeFromStartLocationToFirstVisit": "PT9M52S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT9M11S",
"totalTravelDistanceMeters": 16563,
"travelDistanceFromStartLocationToFirstVisitMeters": 9447,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 7116,
"endLocationArrivalTime": "2027-02-01T15:09:11Z"
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:26:56Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT26M56S",
"travelDistanceMetersFromPreviousStandstill": 29982,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT56M52S",
"travelTimeFromStartLocationToFirstVisit": "PT26M56S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 64677,
"travelDistanceFromStartLocationToFirstVisitMeters": 29982,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T15:29:56Z"
}
}
]
}
1.4. Implementing the recommendation
The recommendation output for this example includes two recommendations:
-
09:00-13:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.
-
13:00-17:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.
The recommendations include specific times, however, the plan is still a draft and those times will likely change as additional visits are added to the plan. |
If the customer agrees to one of the time windows, it must be added to the recommendation input dataset (the draft plan).
In this instance, the customer accepted the second recommendation:
-
13:00-17:00: Ann is assigned to Visit A2 and Carl is assigned to Visit A1.
First, add the visits to Ann’s and Carl’s itineraries:
Where there are multiple visits to include in the itinerary, they must be included in the order they occur on the schedule. |
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit A2",
"kind": "VISIT"
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.77301, -84.43899],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT"
}
]
}
]
}
]
}
Next, add the time window to the visits in the visit group:
{
"visitGroups": [
{
"id": "Group A",
"visits": [
{
"id": "Visit A1",
"name": "visit group A 1/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Visit A2",
"name": "visit group A 2/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
]
}
]
}
It is important to decide how to manage concurrent requests. If multiple requests are made for recommendations, which recommendation is added to the input dataset and what happens to other recommendations? |
2. Visit group recommendations for unsolved route plans with existing visits
When additional customers call to request technicians to complete work at their premises, their requests can be added to the recommendation input dataset.
In the following example, a customer has called and needs two technicians to complete work. Group B is the new visit group:
-
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/recommendations/visit-group-recommend-time-windows [email protected]
{
"maxNumberOfRecommendationsPerTimeWindow": 2,
"fitVisitGroupId": "Group B",
"timeWindows": [
{
"startTime": "2027-02-01T09:00:00Z",
"endTime": "2027-02-01T13:00:00Z"
},
{
"startTime": "2027-02-01T13:00:00Z",
"endTime": "2027-02-01T17:00:00Z"
}
],
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit A2",
"kind": "VISIT"
}
]
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.77301, -84.43899],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT"
}
]
}
]
}
],
"visitGroups": [
{
"id": "Group A",
"visits": [
{
"id": "Visit A1",
"name": "visit group A 1/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Visit A2",
"name": "visit group A 2/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
]
},
{
"id": "Group B",
"visits": [
{
"id": "Visit B1",
"name": "visit group B 1/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT2H"
},
{
"id": "Visit B2",
"name": "visit group B 2/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT2H"
}
]
}
]
}
}
{
"recommendations": [
{
"scoreDiff": "0hard/20000medium/-37218soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/242soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-35090soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-2370soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T09:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T13:00:00Z"
},
"vehicleShifts": [
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:12:00Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT12M",
"travelDistanceMetersFromPreviousStandstill": 9019,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:45:11Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT33M11S",
"travelDistanceMetersFromPreviousStandstill": 38376,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H15M7S",
"travelTimeFromStartLocationToFirstVisit": "PT12M",
"travelTimeBetweenVisits": "PT33M11S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 82090,
"travelDistanceFromStartLocationToFirstVisitMeters": 9019,
"travelDistanceBetweenVisitsMeters": 38376,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T15:29:56Z",
"technicianCosts": 0
}
},
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:10:37Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT10M37S",
"travelDistanceMetersFromPreviousStandstill": 8843,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT21M15S",
"travelTimeFromStartLocationToFirstVisit": "PT10M37S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 17677,
"travelDistanceFromStartLocationToFirstVisitMeters": 8843,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"endLocationArrivalTime": "2027-02-01T11:22:38Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT1H55M25S",
"travelTimeFromStartLocationToFirstVisit": "PT32M29S",
"travelTimeBetweenVisits": "PT33M11S",
"travelTimeFromLastVisitToEndLocation": "PT49M45S",
"totalTravelDistanceMeters": 116330,
"travelDistanceFromStartLocationToFirstVisitMeters": 27309,
"travelDistanceBetweenVisitsMeters": 38376,
"travelDistanceFromLastVisitToEndLocationMeters": 50645,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 73.65,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
},
{
"scoreDiff": "-9745hard/20000medium/-42113818soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/379soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "-5537hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/-42080000soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-31923soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-2274soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "-4208hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T13:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T17:00:00Z"
},
"vehicleShifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:10:37Z",
"startServiceTime": "2027-02-01T15:35:04Z",
"departureTime": "2027-02-01T17:35:04Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT10M37S",
"travelDistanceMetersFromPreviousStandstill": 8843,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT21M15S",
"travelTimeFromStartLocationToFirstVisit": "PT10M37S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 17677,
"travelDistanceFromStartLocationToFirstVisitMeters": 8843,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"endLocationArrivalTime": "2027-02-01T17:45:42Z",
"technicianCosts": 0
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:26:56Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT26M56S",
"travelDistanceMetersFromPreviousStandstill": 29982,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit B2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T15:35:04Z",
"startServiceTime": "2027-02-01T15:35:04Z",
"departureTime": "2027-02-01T17:35:04Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT35M4S",
"travelDistanceMetersFromPreviousStandstill": 40244,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H13M31S",
"travelTimeFromStartLocationToFirstVisit": "PT26M56S",
"travelTimeBetweenVisits": "PT35M4S",
"travelTimeFromLastVisitToEndLocation": "PT11M31S",
"totalTravelDistanceMeters": 78923,
"travelDistanceFromStartLocationToFirstVisitMeters": 29982,
"travelDistanceBetweenVisitsMeters": 40244,
"travelDistanceFromLastVisitToEndLocationMeters": 8697,
"endLocationArrivalTime": "2027-02-01T17:46:35Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT1H53M49S",
"travelTimeFromStartLocationToFirstVisit": "PT47M25S",
"travelTimeBetweenVisits": "PT35M4S",
"travelTimeFromLastVisitToEndLocation": "PT31M20S",
"totalTravelDistanceMeters": 113163,
"travelDistanceFromStartLocationToFirstVisitMeters": 48272,
"travelDistanceBetweenVisitsMeters": 40244,
"travelDistanceFromLastVisitToEndLocationMeters": 24647,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 88.95,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
}
]
}
The recommendation output for this example includes two recommendations:
-
09:00-13:00: Ann is assigned to Visit B1 and Beth is assigned to Visit B2.
-
13:00-17:00: Ann is assigned to Visit B2 and Beth is assigned to Visit B1.
The second recommendation breaks hard constraints and is not a feasible schedule.

Because Ann already has a visit on the draft plan for the second recommendation, scheduling Visit B2 during this time window with Ann breaks the following hard constraints and is not a feasible schedule:
-
Max shift end time (hard)
-
Require service max end time
{
"score": "-5537hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "-4208hard/0medium/0soft",
"constraintName": "Require service max end time"
}
Depending on the urgency of the situation, it might be possible to reschedule the other visit group during that time window in Ann’s shift, so that Visit B2 can take this time window. |
3. Visit Group recommendations for published route plans
If a customer visit needs to be added to a published schedule, the process for requesting recommendations is the same as above.
The following input dataset generated the schedule that is currently being executed:
-
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": "Published sample schedule"
}
},
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT"
},
{
"id": "Visit A2",
"kind": "VISIT"
}
]
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT"
}
]
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.77301, -84.43899],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT"
}
]
}
]
}
],
"visitGroups": [
{
"id": "Group A",
"visits": [
{
"id": "Visit A1",
"name": "visit group A 1/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Visit A2",
"name": "visit group A 2/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
]
},
{
"id": "Group B",
"visits": [
{
"id": "Visit B1",
"name": "visit group B 1/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
},
{
"id": "Visit B2",
"name": "visit group B 2/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13: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>
{
"run": {
"id": "ID",
"name": "Published sample schedule",
"submitDateTime": "2025-01-28T04:31:53.522648502Z",
"startDateTime": "2025-01-28T04:32:00.35717356Z",
"activeDateTime": "2025-01-28T04:32:00.670623262Z",
"completeDateTime": "2025-01-28T04:37:00.750822425Z",
"shutdownDateTime": "2025-01-28T04:37:01.205303486Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-123659soft",
"tags": [],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:12:00Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT12M",
"travelDistanceMetersFromPreviousStandstill": 9019,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:45:11Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT33M11S",
"travelDistanceMetersFromPreviousStandstill": 38376,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H15M7S",
"travelTimeFromStartLocationToFirstVisit": "PT12M",
"travelTimeBetweenVisits": "PT33M11S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 82090,
"travelDistanceFromStartLocationToFirstVisitMeters": 9019,
"travelDistanceBetweenVisitsMeters": 38376,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T15:29:56Z",
"technicianCosts": 0
}
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:10:37Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT10M37S",
"travelDistanceMetersFromPreviousStandstill": 8843,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT21M15S",
"travelTimeFromStartLocationToFirstVisit": "PT10M37S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 17677,
"travelDistanceFromStartLocationToFirstVisitMeters": 8843,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"endLocationArrivalTime": "2027-02-01T11:22:38Z",
"technicianCosts": 0
}
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:09:52Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T15:00:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT9M52S",
"travelDistanceMetersFromPreviousStandstill": 9447,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT19M3S",
"travelTimeFromStartLocationToFirstVisit": "PT9M52S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT9M11S",
"totalTravelDistanceMeters": 16563,
"travelDistanceFromStartLocationToFirstVisitMeters": 9447,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 7116,
"endLocationArrivalTime": "2027-02-01T15:09:11Z",
"technicianCosts": 0
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT1H55M25S",
"travelTimeFromStartLocationToFirstVisit": "PT32M29S",
"travelTimeBetweenVisits": "PT33M11S",
"travelTimeFromLastVisitToEndLocation": "PT49M45S",
"totalTravelDistanceMeters": 116330,
"travelDistanceFromStartLocationToFirstVisitMeters": 27309,
"travelDistanceBetweenVisitsMeters": 38376,
"travelDistanceFromLastVisitToEndLocationMeters": 50645,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 73.65,
"totalTechnicianCosts": 0
}
}
modelOutput
contains Ann’s, Beth’s, and Carl’s itineraries.
If a customer calls to request a technician on the same day, the following recommendations input dataset will return time window recommendations for the request (Group C):
-
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/recommendations/visit-group-recommend-time-windows [email protected]
{
"maxNumberOfRecommendationsPerTimeWindow": 2,
"fitVisitGroupId": "Group C",
"timeWindows": [
{
"startTime": "2027-02-01T09:00:00Z",
"endTime": "2027-02-01T13:00:00Z"
},
{
"startTime": "2027-02-01T13:00:00Z",
"endTime": "2027-02-01T17:00:00Z"
}
],
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT"
},
{
"id": "Visit A2",
"kind": "VISIT"
}
]
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT"
}
]
}
]
},
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startLocation": [33.77301, -84.43899],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT"
}
]
}
]
}
],
"visitGroups": [
{
"id": "Group A",
"visits": [
{
"id": "Visit A1",
"name": "visit group A 1/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Visit A2",
"name": "visit group A 2/2",
"location": [ 33.80209, -84.40296 ],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T13:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
]
},
{
"id": "Group B",
"visits": [
{
"id": "Visit B1",
"name": "visit group B 1/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
},
{
"id": "Visit B2",
"name": "visit group B 2/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT2H",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
}
]
},
{
"id": "Group C",
"visits": [
{
"id": "Visit C1",
"name": "visit group C 1/2",
"location": [33.51744, -84.12329],
"serviceDuration": "PT1H"
},
{
"id": "Visit C2",
"name": "visit group C 2/2",
"location": [33.51744, -84.12329],
"serviceDuration": "PT1H"
}
]
}
]
}
}
{
"recommendations": [
{
"scoreDiff": "0hard/20000medium/-80851soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/141soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-75987soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-5005soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T09:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T13:00:00Z"
},
"vehicleShifts": [
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:12:00Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT12M",
"travelDistanceMetersFromPreviousStandstill": 9019,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit C1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:35:23Z",
"startServiceTime": "2027-02-01T11:35:23Z",
"departureTime": "2027-02-01T12:35:23Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M23S",
"travelDistanceMetersFromPreviousStandstill": 21217,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:22:38Z",
"startServiceTime": "2027-02-01T13:22:38Z",
"departureTime": "2027-02-01T15:22:38Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT47M15S",
"travelDistanceMetersFromPreviousStandstill": 51351,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H52M34S",
"travelTimeFromStartLocationToFirstVisit": "PT12M",
"travelTimeBetweenVisits": "PT1H10M38S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 116282,
"travelDistanceFromStartLocationToFirstVisitMeters": 9019,
"travelDistanceBetweenVisitsMeters": 72568,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T15:52:34Z",
"technicianCosts": 0
}
},
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:10:37Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT10M37S",
"travelDistanceMetersFromPreviousStandstill": 8843,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit C2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:35:23Z",
"startServiceTime": "2027-02-01T11:35:23Z",
"departureTime": "2027-02-01T12:35:23Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M23S",
"travelDistanceMetersFromPreviousStandstill": 21217,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H7M13S",
"travelTimeFromStartLocationToFirstVisit": "PT10M37S",
"travelTimeBetweenVisits": "PT23M23S",
"travelTimeFromLastVisitToEndLocation": "PT33M13S",
"totalTravelDistanceMeters": 59472,
"travelDistanceFromStartLocationToFirstVisitMeters": 8843,
"travelDistanceBetweenVisitsMeters": 21217,
"travelDistanceFromLastVisitToEndLocationMeters": 29412,
"endLocationArrivalTime": "2027-02-01T13:08:36Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT3H18M50S",
"travelTimeFromStartLocationToFirstVisit": "PT32M29S",
"travelTimeBetweenVisits": "PT1H34M1S",
"travelTimeFromLastVisitToEndLocation": "PT1H12M20S",
"totalTravelDistanceMeters": 192317,
"travelDistanceFromStartLocationToFirstVisitMeters": 27309,
"travelDistanceBetweenVisitsMeters": 93785,
"travelDistanceFromLastVisitToEndLocationMeters": 71223,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 85.31,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
},
{
"scoreDiff": "-1031hard/20000medium/-80851soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/141soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "-1031hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-75987soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-5005soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T13:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T17:00:00Z"
},
"vehicleShifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:10:37Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT10M37S",
"travelDistanceMetersFromPreviousStandstill": 8843,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit C1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:35:23Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T14:00:00Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M23S",
"travelDistanceMetersFromPreviousStandstill": 21217,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H7M13S",
"travelTimeFromStartLocationToFirstVisit": "PT10M37S",
"travelTimeBetweenVisits": "PT23M23S",
"travelTimeFromLastVisitToEndLocation": "PT33M13S",
"totalTravelDistanceMeters": 59472,
"travelDistanceFromStartLocationToFirstVisitMeters": 8843,
"travelDistanceBetweenVisitsMeters": 21217,
"travelDistanceFromLastVisitToEndLocationMeters": 29412,
"endLocationArrivalTime": "2027-02-01T14:33:13Z",
"technicianCosts": 0
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:12:00Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT12M",
"travelDistanceMetersFromPreviousStandstill": 9019,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit C2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:35:23Z",
"startServiceTime": "2027-02-01T13:00:00Z",
"departureTime": "2027-02-01T14:00:00Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M23S",
"travelDistanceMetersFromPreviousStandstill": 21217,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T14:47:15Z",
"startServiceTime": "2027-02-01T14:47:15Z",
"departureTime": "2027-02-01T16:47:15Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT47M15S",
"travelDistanceMetersFromPreviousStandstill": 51351,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H52M34S",
"travelTimeFromStartLocationToFirstVisit": "PT12M",
"travelTimeBetweenVisits": "PT1H10M38S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 116282,
"travelDistanceFromStartLocationToFirstVisitMeters": 9019,
"travelDistanceBetweenVisitsMeters": 72568,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T17:17:11Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT3H18M50S",
"travelTimeFromStartLocationToFirstVisit": "PT32M29S",
"travelTimeBetweenVisits": "PT1H34M1S",
"travelTimeFromLastVisitToEndLocation": "PT1H12M20S",
"totalTravelDistanceMeters": 192317,
"travelDistanceFromStartLocationToFirstVisitMeters": 27309,
"travelDistanceBetweenVisitsMeters": 93785,
"travelDistanceFromLastVisitToEndLocationMeters": 71223,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 88.16,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
}
]
}
The recommendation output for this example includes two recommendations:
-
09:00-13:00: Ann is assigned to Visit C1 and Beth is assigned to Visit C2.
-
13:00-17:00: Beth is assigned to Visit C1 and Ann is assigned to Visit C2.

The second recommendation breaks hard constraints and would result in Ann arriving home after 17:00. This is not a feasible solution.
The customer can only accept the first recommendation.
After the customer has accepted the time window, create a real-time plan to include the visit in the optimized plan for the day. Failure to update the real-time plan will lead to errors with Visit Group C not being included if there are further updates to the plan throughout the day.
4. Include dependencies
The recommendations output only includes vehicle shifts that are directly impacted by the recommendations. However, there are times when recommendations also change vehicle shifts that are not part of the recommendations. For instance, some jobs require multiple visits in a specific order to complete the work (learn more about this topic in Visit dependencies).
In the previous example Visit Group A is changed to accommodate Visit Group B. Carl is assigned to Visit A1 which is part of Visit Group A.
To include all dependencies in the recommendations output, include "includeDependencies": "ALL"
in the recommendations input dataset:
{
"maxNumberOfRecommendationsPerTimeWindow": 2,
"fitVisitGroupId": "Group C",
"includeDependencies": "ALL"
}
"includeDependencies"
can be set to ALL
or NONE
.
The default value is NONE
.
With "includeDependencies"
set to true, the recommendation output from the previous example would also include Carl’s shift:
{
"vehicleShifts": [
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:12:00Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT12M",
"travelDistanceMetersFromPreviousStandstill": 9019,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit C1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:35:23Z",
"startServiceTime": "2027-02-01T11:35:23Z",
"departureTime": "2027-02-01T12:35:23Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M23S",
"travelDistanceMetersFromPreviousStandstill": 21217,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:22:38Z",
"startServiceTime": "2027-02-01T13:22:38Z",
"departureTime": "2027-02-01T15:22:38Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT47M15S",
"travelDistanceMetersFromPreviousStandstill": 51351,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H52M34S",
"travelTimeFromStartLocationToFirstVisit": "PT12M",
"travelTimeBetweenVisits": "PT1H10M38S",
"travelTimeFromLastVisitToEndLocation": "PT29M56S",
"totalTravelDistanceMeters": 116282,
"travelDistanceFromStartLocationToFirstVisitMeters": 9019,
"travelDistanceBetweenVisitsMeters": 72568,
"travelDistanceFromLastVisitToEndLocationMeters": 34695,
"endLocationArrivalTime": "2027-02-01T15:52:34Z",
"technicianCosts": 0
}
},
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:10:37Z",
"startServiceTime": "2027-02-01T09:12:00Z",
"departureTime": "2027-02-01T11:12:00Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT10M37S",
"travelDistanceMetersFromPreviousStandstill": 8843,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit C2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:35:23Z",
"startServiceTime": "2027-02-01T11:35:23Z",
"departureTime": "2027-02-01T12:35:23Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M23S",
"travelDistanceMetersFromPreviousStandstill": 21217,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H7M13S",
"travelTimeFromStartLocationToFirstVisit": "PT10M37S",
"travelTimeBetweenVisits": "PT23M23S",
"travelTimeFromLastVisitToEndLocation": "PT33M13S",
"totalTravelDistanceMeters": 59472,
"travelDistanceFromStartLocationToFirstVisitMeters": 8843,
"travelDistanceBetweenVisitsMeters": 21217,
"travelDistanceFromLastVisitToEndLocationMeters": 29412,
"endLocationArrivalTime": "2027-02-01T13:08:36Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT3H18M50S",
"travelTimeFromStartLocationToFirstVisit": "PT32M29S",
"travelTimeBetweenVisits": "PT1H34M1S",
"travelTimeFromLastVisitToEndLocation": "PT1H12M20S",
"totalTravelDistanceMeters": 192317,
"travelDistanceFromStartLocationToFirstVisitMeters": 27309,
"travelDistanceBetweenVisitsMeters": 93785,
"travelDistanceFromLastVisitToEndLocationMeters": 71223,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 85.31,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit A1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:09:52Z",
"startServiceTime": "2027-02-01T13:22:38Z",
"departureTime": "2027-02-01T15:22:38Z",
"effectiveServiceDuration": "PT2H",
"travelTimeFromPreviousStandstill": "PT9M52S",
"travelDistanceMetersFromPreviousStandstill": 9447,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT19M3S",
"travelTimeFromStartLocationToFirstVisit": "PT9M52S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT9M11S",
"totalTravelDistanceMeters": 16563,
"travelDistanceFromStartLocationToFirstVisitMeters": 9447,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 7116,
"endLocationArrivalTime": "2027-02-01T15:31:49Z",
"technicianCosts": 0
}
}
]
}
5. Recommendations with pinned visits
When a plan has been published, it will often include visits that can’t be moved to accommodate new visits on the schedule.
Recommendations can reschedule visits and visit groups to accommodate new visits, however, visits can be pinned to prevent them from being moved.
In the following example there is 1 visit group and 2 visits that have been scheduled.

The published schedule was previously generated from the following input dataset:
-
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": "Published route plan"
}
},
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
],
"visits": [
{
"id": "Visit B",
"location": [33.90719, -84.28149],
"serviceDuration": "PT1H"
},
{
"id": "Visit D",
"location": [33.89351, -84.00649],
"serviceDuration": "PT1H"
}
],
"visitGroups": [
{
"id": "Group C",
"visits": [
{
"id": "Visit C1",
"name": "visit group C 1/2",
"location": [33.51744, -84.12329],
"serviceDuration": "PT1H30M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T12:00:00Z"
}
]
},
{
"id": "Visit C2",
"name": "visit group C 2/2",
"location": [33.51744, -84.12329],
"serviceDuration": "PT1H30M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T12: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>
{
"run": {
"id": "ID",
"name": "Published route plan",
"submitDateTime": "2025-01-30T09:18:18.596847518Z",
"startDateTime": "2025-01-30T09:18:25.217188031Z",
"activeDateTime": "2025-01-30T09:18:25.935527926Z",
"completeDateTime": "2025-01-30T09:23:26.007621776Z",
"shutdownDateTime": "2025-01-30T09:23:26.278569611Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-210676soft",
"tags": [],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit C1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:25:22Z",
"startServiceTime": "2027-02-01T09:33:20Z",
"departureTime": "2027-02-01T11:03:20Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT25M22S",
"travelDistanceMetersFromPreviousStandstill": 23325,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit D",
"kind": "VISIT",
"arrivalTime": "2027-02-01T12:00:28Z",
"startServiceTime": "2027-02-01T12:00:28Z",
"departureTime": "2027-02-01T13:00:28Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT57M8S",
"travelDistanceMetersFromPreviousStandstill": 52223,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:32:13Z",
"startServiceTime": "2027-02-01T13:32:13Z",
"departureTime": "2027-02-01T14:32:13Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT31M45S",
"travelDistanceMetersFromPreviousStandstill": 34237,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT2H22M4S",
"travelTimeFromStartLocationToFirstVisit": "PT25M22S",
"travelTimeBetweenVisits": "PT1H28M53S",
"travelTimeFromLastVisitToEndLocation": "PT27M49S",
"totalTravelDistanceMeters": 139006,
"travelDistanceFromStartLocationToFirstVisitMeters": 23325,
"travelDistanceBetweenVisitsMeters": 86460,
"travelDistanceFromLastVisitToEndLocationMeters": 29221,
"endLocationArrivalTime": "2027-02-01T15:00:02Z",
"technicianCosts": 0
}
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit C2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:33:20Z",
"startServiceTime": "2027-02-01T09:33:20Z",
"departureTime": "2027-02-01T11:03:20Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT33M20S",
"travelDistanceMetersFromPreviousStandstill": 29441,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H6M33S",
"travelTimeFromStartLocationToFirstVisit": "PT33M20S",
"travelTimeBetweenVisits": "PT0S",
"travelTimeFromLastVisitToEndLocation": "PT33M13S",
"totalTravelDistanceMeters": 58853,
"travelDistanceFromStartLocationToFirstVisitMeters": 29441,
"travelDistanceBetweenVisitsMeters": 0,
"travelDistanceFromLastVisitToEndLocationMeters": 29412,
"endLocationArrivalTime": "2027-02-01T11:36:33Z",
"technicianCosts": 0
}
}
]
}
]
},
"kpis": {
"totalTravelTime": "PT3H28M37S",
"travelTimeFromStartLocationToFirstVisit": "PT58M42S",
"travelTimeBetweenVisits": "PT1H28M53S",
"travelTimeFromLastVisitToEndLocation": "PT1H1M2S",
"totalTravelDistanceMeters": 197859,
"travelDistanceFromStartLocationToFirstVisitMeters": 52766,
"travelDistanceBetweenVisitsMeters": 86460,
"travelDistanceFromLastVisitToEndLocationMeters": 58633,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 60.6,
"totalTechnicianCosts": 0
}
}
modelOutput
contains Ann’s, Beth’s, and Carl’s itineraries.
If a customer calls and needs an emergency visit to be added to the schedule, the process for requesting recommendations is the same as above, however, any visits that can’t be moved as part of the recommendation can be pinned.
In this example, Visit D cannot be moved. This visit can be pinned, which prevents the visit from being rescheduled.
Any visit a technician is scheduled to make before a pinned visit, must also be pinned.
Learn more about pinning in the Real-time planning: pinning visits guide.
In this example, Ann is scheduled to complete the following visits: Visit C1, Visit D, and Visit B. Visit C1 is before Visit D and cannot be rescheduled, so it is also pinned, and as Visit C1 is part of a visit group, the other visit in the group (Visit C2) should also be pinned.
To pin visits, add "pinningRequested": true
to each visit, and add the "minStartTravelTime"
from the previous output dataset to each visit.
{
"visits": [
{
"id": "Visit D",
"location": [33.90719, -84.28149],
"serviceDuration": "PT1H",
"minStartTravelTime": "2027-02-01T00:00:00Z",
"pinningRequested": true
}
]
}
If a customer calls to schedule a visit group on the same day (Visit Group E), the following recommendations input dataset will provide recommendations for the visit group. Because Visit C1, Visit C2, and Visit D are pinned, the recommendations will not reschedule them.
-
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/recommendations/visit-group-recommend-time-windows [email protected]
{
"maxNumberOfRecommendationsPerTimeWindow": 2,
"fitVisitGroupId": "Group E",
"timeWindows": [
{
"startTime": "2027-02-01T09:00:00Z",
"endTime": "2027-02-01T13:00:00Z"
},
{
"startTime": "2027-02-01T13:00:00Z",
"endTime": "2027-02-01T17:00:00Z"
}
],
"modelInput": {
"vehicles": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann-2027-02-01",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit C1",
"kind": "VISIT"
},
{
"id": "Visit D",
"kind": "VISIT"
},
{
"id": "Visit B",
"kind": "VISIT"
}
]
}
]
},
{
"id": "Beth",
"shifts": [
{
"id": "Beth-2027-02-01",
"startLocation": [33.70474, -84.06508],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z",
"itinerary": [
{
"id": "Visit C2",
"kind": "VISIT"
}
]
}
]
}
],
"visits": [
{
"id": "Visit B",
"location": [33.90719, -84.28149],
"serviceDuration": "PT1H"
},
{
"id": "Visit D",
"location": [33.89351, -84.00649],
"serviceDuration": "PT1H",
"minStartTravelTime": "2027-02-01T00:00:00Z",
"pinningRequested": true
}
],
"visitGroups": [
{
"id": "Group C",
"visits": [
{
"id": "Visit C1",
"name": "visit group C 1/2",
"location": [33.51744, -84.12329],
"serviceDuration": "PT1H30M",
"minStartTravelTime": "2027-02-01T00:00:00Z",
"pinningRequested": true,
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
},
{
"id": "Visit C2",
"name": "visit group C 2/2",
"location": [33.51744, -84.12329],
"serviceDuration": "PT1H30M",
"minStartTravelTime": "2027-02-01T00:00:00Z",
"pinningRequested": true,
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
}
]
},
{
"id": "Group E",
"visits": [
{
"id": "Visit E1",
"name": "visit group E 1/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT1H"
},
{
"id": "Visit E2",
"name": "visit group E 2/2",
"location": [33.67590, -84.11845],
"serviceDuration": "PT1H"
}
]
}
]
}
}
{
"recommendations": [
{
"scoreDiff": "0hard/20000medium/-16988soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/299soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-16194soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-1093soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T13:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T17:00:00Z"
},
"vehicleShifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit C2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:33:20Z",
"startServiceTime": "2027-02-01T09:33:20Z",
"departureTime": "2027-02-01T11:03:20Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT33M20S",
"travelDistanceMetersFromPreviousStandstill": 29441,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit E1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:26:42Z",
"startServiceTime": "2027-02-01T15:05:57Z",
"departureTime": "2027-02-01T16:05:57Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M22S",
"travelDistanceMetersFromPreviousStandstill": 21307,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H7M20S",
"travelTimeFromStartLocationToFirstVisit": "PT33M20S",
"travelTimeBetweenVisits": "PT23M22S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 59582,
"travelDistanceFromStartLocationToFirstVisitMeters": 29441,
"travelDistanceBetweenVisitsMeters": 21307,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"endLocationArrivalTime": "2027-02-01T16:16:35Z",
"technicianCosts": 0
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit C1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:25:22Z",
"startServiceTime": "2027-02-01T09:33:20Z",
"departureTime": "2027-02-01T11:03:20Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT25M22S",
"travelDistanceMetersFromPreviousStandstill": 23325,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit D",
"kind": "VISIT",
"arrivalTime": "2027-02-01T12:00:28Z",
"startServiceTime": "2027-02-01T12:00:28Z",
"departureTime": "2027-02-01T13:00:28Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT57M8S",
"travelDistanceMetersFromPreviousStandstill": 52223,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:32:13Z",
"startServiceTime": "2027-02-01T13:32:13Z",
"departureTime": "2027-02-01T14:32:13Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT31M45S",
"travelDistanceMetersFromPreviousStandstill": 34237,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit E2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T15:05:57Z",
"startServiceTime": "2027-02-01T15:05:57Z",
"departureTime": "2027-02-01T16:05:57Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT33M44S",
"travelDistanceMetersFromPreviousStandstill": 35989,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT2H39M30S",
"travelTimeFromStartLocationToFirstVisit": "PT25M22S",
"travelTimeBetweenVisits": "PT2H2M37S",
"travelTimeFromLastVisitToEndLocation": "PT11M31S",
"totalTravelDistanceMeters": 154471,
"travelDistanceFromStartLocationToFirstVisitMeters": 23325,
"travelDistanceBetweenVisitsMeters": 122449,
"travelDistanceFromLastVisitToEndLocationMeters": 8697,
"endLocationArrivalTime": "2027-02-01T16:17:28Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT3H46M50S",
"travelTimeFromStartLocationToFirstVisit": "PT58M42S",
"travelTimeBetweenVisits": "PT2H25M59S",
"travelTimeFromLastVisitToEndLocation": "PT22M9S",
"totalTravelDistanceMeters": 214053,
"travelDistanceFromStartLocationToFirstVisitMeters": 52766,
"travelDistanceBetweenVisitsMeters": 143756,
"travelDistanceFromLastVisitToEndLocationMeters": 17531,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 99.89,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
},
{
"scoreDiff": "-11422hard/20000medium/-38866soft",
"constraintDiffs": [
{
"score": "0hard/0medium/0soft",
"constraintName": "Balance movable and non-movable visits"
},
{
"score": "0hard/0medium/137soft",
"constraintName": "Balance time utilization"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max last visit departure time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max shift end time (soft)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Max travel time per visit (hard)"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize scheduling vehicles with unnecessary skill levels"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize shift costs"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Minimize the visit completion risk"
},
{
"score": "0hard/0medium/-36766soft",
"constraintName": "Minimize travel distance"
},
{
"score": "0hard/0medium/-2237soft",
"constraintName": "Minimize travel time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No conflict with fixed location break"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No loops allowed"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "No semi-assigned visit groups"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer scheduling optional visits"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visit vehicle match preferred vehicles"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Prefer visits scheduled to the earliest day"
},
{
"score": "0hard/20000medium/0soft",
"constraintName": "Require scheduling mandatory visits"
},
{
"score": "-11422hard/0medium/0soft",
"constraintName": "Require service max end time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require service max start time"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require skills"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require tags"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require the same vehicle for dependent visit"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency delay"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit dependency prerequisite assigned"
},
{
"score": "0hard/0medium/0soft",
"constraintName": "Require visit vehicle match required vehicles"
}
],
"timeWindow": {
"minStartTime": "2027-02-01T09:00:00Z",
"maxStartTime": null,
"maxEndTime": "2027-02-01T13:00:00Z"
},
"vehicleShifts": [
{
"id": "Beth-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit C2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:33:20Z",
"startServiceTime": "2027-02-01T09:33:20Z",
"departureTime": "2027-02-01T11:03:20Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT33M20S",
"travelDistanceMetersFromPreviousStandstill": 29441,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit E1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T11:26:42Z",
"startServiceTime": "2027-02-01T13:35:11Z",
"departureTime": "2027-02-01T14:35:11Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT23M22S",
"travelDistanceMetersFromPreviousStandstill": 21307,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT1H7M20S",
"travelTimeFromStartLocationToFirstVisit": "PT33M20S",
"travelTimeBetweenVisits": "PT23M22S",
"travelTimeFromLastVisitToEndLocation": "PT10M38S",
"totalTravelDistanceMeters": 59582,
"travelDistanceFromStartLocationToFirstVisitMeters": 29441,
"travelDistanceBetweenVisitsMeters": 21307,
"travelDistanceFromLastVisitToEndLocationMeters": 8834,
"endLocationArrivalTime": "2027-02-01T14:45:49Z",
"technicianCosts": 0
}
},
{
"id": "Ann-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit C1",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:25:22Z",
"startServiceTime": "2027-02-01T09:33:20Z",
"departureTime": "2027-02-01T11:03:20Z",
"effectiveServiceDuration": "PT1H30M",
"travelTimeFromPreviousStandstill": "PT25M22S",
"travelDistanceMetersFromPreviousStandstill": 23325,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit D",
"kind": "VISIT",
"arrivalTime": "2027-02-01T12:00:28Z",
"startServiceTime": "2027-02-01T12:00:28Z",
"departureTime": "2027-02-01T13:00:28Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT57M8S",
"travelDistanceMetersFromPreviousStandstill": 52223,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit E2",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:35:11Z",
"startServiceTime": "2027-02-01T13:35:11Z",
"departureTime": "2027-02-01T14:35:11Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT34M43S",
"travelDistanceMetersFromPreviousStandstill": 31571,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T15:08:43Z",
"startServiceTime": "2027-02-01T15:08:43Z",
"departureTime": "2027-02-01T16:08:43Z",
"effectiveServiceDuration": "PT1H",
"travelTimeFromPreviousStandstill": "PT33M32S",
"travelDistanceMetersFromPreviousStandstill": 38703,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT2H58M34S",
"travelTimeFromStartLocationToFirstVisit": "PT25M22S",
"travelTimeBetweenVisits": "PT2H5M23S",
"travelTimeFromLastVisitToEndLocation": "PT27M49S",
"totalTravelDistanceMeters": 175043,
"travelDistanceFromStartLocationToFirstVisitMeters": 23325,
"travelDistanceBetweenVisitsMeters": 122497,
"travelDistanceFromLastVisitToEndLocationMeters": 29221,
"endLocationArrivalTime": "2027-02-01T16:36:32Z",
"technicianCosts": 0
}
}
],
"kpis": {
"totalTravelTime": "PT4H5M54S",
"travelTimeFromStartLocationToFirstVisit": "PT58M42S",
"travelTimeBetweenVisits": "PT2H28M45S",
"travelTimeFromLastVisitToEndLocation": "PT38M27S",
"totalTravelDistanceMeters": 234625,
"travelDistanceFromStartLocationToFirstVisitMeters": 52766,
"travelDistanceBetweenVisitsMeters": 143804,
"travelDistanceFromLastVisitToEndLocationMeters": 38055,
"totalUnassignedVisits": 0,
"workingTimeFairnessPercentage": 86.2,
"totalTechnicianCosts": 0
},
"dependentVehicleShifts": []
}
]
}
Only one of the recommendations does not break hard constraints and results in the following schedule with visits C1, C2, and D unaffected by the recommendation.

Learn more about pinning in the Real-time planning: pinning visits guide.
Next
-
Understand the constraints of the Field Service Routing model.
-
See the full API spec or try the online API.
-
Manage shift times with Time zones and daylight-saving time (DST) changes.
-
Learn about Visit time window recommendations for individual visits.