Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
    • Task Scheduling
  • Platform
Try models
  • Field Service Routing
  • Visit service constraints
  • Visit service level agreement (SLA)

Field Service Routing

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terminology
      • Use case guide
      • Scheduling API concepts
      • Integration
      • Constraints
      • Using the API
        • Using the OpenAPI spec
        • API tooling
      • Demo datasets
      • Input datasets
        • Model configuration
        • Model input
        • Planning window
        • Time zones and daylight-saving time (DST)
      • Routing with Timefold’s maps service
      • Input validation
      • Model response
      • Output datasets
        • Metadata
        • Model output
        • Input metrics
        • Key performance indicators (KPIs)
      • Key performance indicators (KPIs)
      • Metrics and optimization goals
      • Score analysis
      • Visualizations
    • Vehicle resource constraints
      • Shift hours and overtime
      • Lunch breaks and personal appointments
      • Fairness
      • Route optimization
      • Technician costs
      • Technician ratings
      • Coverage area
    • Visit service constraints
      • Time windows and opening hours
      • Skills
      • Visit dependencies
      • Multi-vehicle visits
      • Multi-day schedules and movable visits
      • Priority visits and optional visits
      • Visit service level agreement (SLA)
      • Duration added for first visit on location
      • Visit profit
      • Visit requirements and tags
        • Visit requirements
        • Tags
    • Manual intervention
    • Recommendations
      • Visit time window recommendations
      • Visit group time window recommendations
      • Bulk 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: actual arrival and departure times
    • Scenarios
      • Configuring labor law compliance
      • Ferry connections
      • Long-running visits
    • Changelog
    • Upgrade to the latest version
    • Feature requests

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:

  • 1. Visit SLAs
  • 2. Prefer scheduling visits with tight SLAs earlier
  • 3. Penalize unassigned visits based on SLA deadline

1. Visit SLAs

Learn how to configure an API Key to run the examples in this guide:
  1. Log in to Timefold Platform: app.timefold.ai.

  2. From the Dashboard, click your tenant, and from the drop-down menu select Manage tenant, then choose API Keys.

  3. Create a new API key or use an existing one. Ensure the list of models for the API key contains the current model.

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

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, where the latestSlaEndTime is not met. 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 equivalent to the number of seconds between latestSlaEndTime and when the visit ends. If a visit with a latestSlaEndTime remains unassigned and the latestSlaEndTime is within the planning window, the soft penalty will also be applied. In this case, the penalty is derived from the time between latestSlaEndTime and the end of the planning window.

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.

1.1. Visit SLAs example

In this example, Carl’s shift begins at 09:00. There are two 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.

visit slas
  • 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 -d@sla-example-1.json
{
  "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": "PT0S"
            }
          }
        ]
      }
    ]
  },
  "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": "PT0S",
    "percentageVisitsInSla": "100.0",
    "absoluteVisitsInSla": "1"
  }
}

2. Prefer scheduling visits with tight SLAs earlier

The Latest SLA end time constraint penalises visits that miss their SLA deadline, but it does not differentiate between visits that both satisfy their SLA. When two visits are tied on other criteria, the visit with the tighter deadline may still end up scheduled later which increases the risk of disruption if the plan changes or the job takes longer than expected.

The Prefer scheduling visits with tight SLAs earlier soft constraint addresses this by incentivising the model to schedule visits with a closer SLA deadline earlier in the planning period. It is disabled by default and can be enabled by setting the preferSchedulingEarlierBasedOnSlaWeight in the model configuration.

{
  "config": {
    "model": {
      "overrides": {
        "preferSchedulingEarlierBasedOnSlaWeight": 1
      }
    }
  }
}

The penalty for each visit is proportional to how late in the planning period it is scheduled relative to how much time remains before its SLA deadline:

penalty = secondsFromPlanningPeriodStart / secondsUntilSlaDeadline * weight

A visit scheduled early with plenty of SLA slack receives a low penalty. A visit scheduled late with little SLA slack receives a high penalty, steering the solver to prefer moving it earlier.

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.

3. Penalize unassigned visits based on SLA deadline

When there is not enough capacity to schedule every visit, visit priority normally decides which visits are left unassigned. As an alternative (or in addition), Timefold can instead decide based on how tight each visit’s latestSlaEndTime is: among the visits that must be left unassigned, it prefers to leave those with the most slack before their SLA deadline unassigned, over those whose deadline is more urgent.

This is controlled by two constraints, both disabled by default:

  • Require scheduling mandatory visits with tighter SLA is a medium constraint, enabled by setting requireSchedulingMandatoryVisitsWithTighterSlaWeight, and applies to mandatory visits.

  • Prefer scheduling optional visits with tighter SLA is a soft constraint, enabled by setting preferSchedulingOptionalVisitsWithTighterSlaWeight, and applies to optional visits.

{
  "config": {
    "model": {
      "overrides": {
        "requireSchedulingMandatoryVisitsWithTighterSlaWeight": 1,
        "preferSchedulingOptionalVisitsWithTighterSlaWeight": 1
      }
    }
  }
}

Only visits with a latestSlaEndTime are affected. Visits without one are unaffected by these two constraints and continue to be governed only by the priority-based constraints.

For each unassigned visit with a latestSlaEndTime, the penalty is derived from its rank among all distinct latestSlaEndTime values found in the dataset, from loosest to tightest:

penalty = weight * 100,000 * (1 + tightnessRank)

tightnessRank ranges from 0 (the visit shares the loosest, that is the latest, deadline in the dataset) to 1 (the visit shares the tightest, that is the earliest, deadline in the dataset). Visits with the same latestSlaEndTime receive the same tightnessRank. As a result, the penalty for an unassigned visit with a latestSlaEndTime always lies between 100,000 * weight and 200,000 * weight, regardless of how far apart the SLA deadlines in the dataset are.

These constraints are recommended to be activated only when not also relying on visit priority to decide which visits get left unassigned. Activating them together with non-default visit priorities produces the VISIT_PRIORITY_AND_TIGHTER_SLA_WEIGHT_CONFLICT validation warning, since the two mechanisms can pull the solver in different directions.

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.

Next

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

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

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