Visit service level agreement (SLA)
Visits can have hard and soft requirements for the time windows when visits can occur. These time windows can represent the business hours of the service location, the customer’s availability, or even the customer’s preferred times.
In addition to these constraints about site availability, there are often requirements (including contractual requirements) regarding the latest time visits can be completed. For instance, if the company promises service within a specific time period of a service being requested.
This requirement is called a service level agreement (SLA).
This is often determined by the urgency of the incident that has been reported, and how quickly the service must be performed.
This guide describes the SLA with the following example:
Prerequisites
Learn how to configure an API Key to run the examples in this guide:
-
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 SLAs
SLAs are defined in visits:
{
"visits": [
{
"id": "Visit A",
"location": [34.31785, -83.82816],
"serviceDuration": "PT1H30M",
"latestSlaEndTime": "2027-02-01T13:00:00Z"
}
]
}
latestSlaEndTime defines the latest time the visit can end to satisfy the SLA.
latestSlaEndTime uses ISO 8601 date and time format with offset to UTC format.
The Latest SLA end time soft constraint is invoked for any visit with a latestSlaEndTime.
To optimize for SLAs, the constraint adds a soft score penalty to the dataset if the visit ends after the time specified by latestSlaEndTime.
The penalty is derived from the time between latestSlaEndTime and when the visit ends.
Visits can still be scheduled even if doing so breaks this constraint, but Timefold is incentivized to use the route plan with the best score.
|
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 this example, Carl’s shift begins at 09:00.
There are 2 visits, Visit A and Visit B, that need to be scheduled.
Visit B has a latestSlaEndTime of 2027-02-01T13:00:00Z, which means that it must be completed by 13:00.
Visit A does not include a latestSlaEndTime.
Visit B is scheduled first so the visit can be completed by the latestSlaEndTime.
-
Input
-
Output
Try this example in Timefold Platform by saving the JSON into a file called sla-example-1.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": "SLA 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.31785, -83.82816],
"serviceDuration": "PT2H30M"
},
{
"id": "Visit B",
"location": [33.32468, -84.127456],
"serviceDuration": "PT3H",
"latestSlaEndTime": "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>
{
"metadata": {
"id": "ID",
"name": "SLA example",
"submitDateTime": "2025-05-16T12:01:05.974541+02:00",
"startDateTime": "2025-05-16T12:01:05.987945+02:00",
"activeDateTime": "2025-05-16T12:01:05.993695+02:00",
"completeDateTime": "2025-05-16T12:01:36.002054+02:00",
"shutdownDateTime": "2025-05-16T12:01:36.004414+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-130078soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"vehicles": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl-2027-02-01",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "Visit B",
"kind": "VISIT",
"arrivalTime": "2027-02-01T09:48:53Z",
"startServiceTime": "2027-02-01T09:48:53Z",
"departureTime": "2027-02-01T12:48:53Z",
"effectiveServiceDuration": "PT3H",
"travelTimeFromPreviousStandstill": "PT48M53S",
"travelDistanceMetersFromPreviousStandstill": 40733,
"minStartTravelTime": "2027-02-01T00:00:00Z"
},
{
"id": "Visit A",
"kind": "VISIT",
"arrivalTime": "2027-02-01T13:22:16Z",
"startServiceTime": "2027-02-01T13:22:16Z",
"departureTime": "2027-02-01T15:52:16Z",
"effectiveServiceDuration": "PT2H30M",
"travelTimeFromPreviousStandstill": "PT33M23S",
"travelDistanceMetersFromPreviousStandstill": 27819,
"minStartTravelTime": "2027-02-01T00:00:00Z"
}
],
"metrics": {
"totalTravelTime": "PT2H25M37S",
"travelTimeFromStartLocationToFirstVisit": "PT48M53S",
"travelTimeBetweenVisits": "PT33M23S",
"travelTimeFromLastVisitToEndLocation": "PT1H3M21S",
"totalTravelDistanceMeters": 121341,
"travelDistanceFromStartLocationToFirstVisitMeters": 40733,
"travelDistanceBetweenVisitsMeters": 27819,
"travelDistanceFromLastVisitToEndLocationMeters": 52789,
"endLocationArrivalTime": "2027-02-01T16:55:37Z",
"technicianCosts": null,
"overtime": null
}
}
]
}
]
},
"inputMetrics": {
"visits": 2,
"visitGroups": 0,
"vehicles": 1,
"mandatoryVisits": 2,
"optionalVisits": 0,
"vehicleShifts": 1,
"visitsWithSla": 1
},
"kpis": {
"totalTravelTime": "PT2H25M37S",
"travelTimeFromStartLocationToFirstVisit": "PT48M53S",
"travelTimeBetweenVisits": "PT33M23S",
"travelTimeFromLastVisitToEndLocation": "PT1H3M21S",
"totalTravelDistanceMeters": 121341,
"travelDistanceFromStartLocationToFirstVisitMeters": 40733,
"travelDistanceBetweenVisitsMeters": 27819,
"travelDistanceFromLastVisitToEndLocationMeters": 52789,
"totalUnassignedVisits": 0,
"totalAssignedVisits": 2,
"assignedMandatoryVisits": 2,
"assignedOptionalVisits": 0,
"totalActivatedVehicles": 1,
"workingTimeFairnessPercentage": 100.0,
"totalTechnicianCosts": null,
"totalOvertime": null,
"percentageVisitsInSla": "100.0",
"absoluteVisitsInSla": "1"
}
}
Next
-
See the full API spec or try the online API.
-
Learn more about field service routing from our YouTube playlist.