Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Field Service Routing
  • Visit service constraints
  • Time windows and opening hours

Field Service Routing

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

Time windows and opening hours

When a customer needs a technician to visit their home, the visit must be scheduled for a time when the customer will be home to let the technician in. The customer might only be available in the morning or the afternoon, or they could be available at any time but have a preference for when the visit occurs.

These times are known as time windows (and opening hours).

Often the time window is decided when the customer books the visit. Timefold’s recommendations API can quickly provide time window recommendations to give customers some options.

There are two types of time windows:

  • Required time windows: When required time windows are specified, the visit can only be scheduled during one of the specified time windows.
    Use required time windows when the visit must occur during specific times.

  • Preferred time windows: When preferred time windows are specified, the visit will be scheduled during the preferred time window if possible, but it could be scheduled at any other time.
    Use preferred time windows when the customer has a preference for when the visit occurs, but they are available at any time. For instance, they are available all day, but would prefer the visit to occur during the morning.

Required and preferred time windows are optional and can be combined.

  • If no time windows are provided, visits can be scheduled at any time.

  • If only required time windows are provided, visits will only be scheduled during one of the provided times. If they cannot be scheduled during the provided times they will be left unassigned.

  • If only preferred time windows are provided, visits will be scheduled during the provided time if possible, if not possible, the visit can be scheduled at any other time.

  • If both required and preferred time windows are provided, visits will be scheduled during a required time window (or left unassigned if they cannot be scheduled during the provided required time windows). If a preferred time window overlaps with the required time window, the visit will be scheduled within the preferred time window if possible.

When using both required and preferred time windows, it is good practise to use broader time windows for required time windows (to give visits the best chance of being scheduled) and narrower time windows for preferred time windows (to accommodate customer preferences).

When assigning required time windows to visits, if the time windows are too narrow and don’t provide much flexibility for when visits can be completed, the route plan could include lots of wait times between jobs that leave technicians idle.

At the same time, if the required time windows are too broad, this may inconvenience customers as they must be available at the location for a longer period of time.

It may be necessary to experiment to find the right balance for your scenarios.

This guide explains time windows with the following examples:

  • Required time windows

  • Multiple time windows

  • Preferred time windows

  • Time windows and other visits

  • Fixed visits

  • Too early (wait time)

  • Too late

Prerequisites

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

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

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

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

1. Required time windows

For a customer visit that must occur between 10:00 and 13:00, a timeWindows object is specified with minStartTime and maxEndTime values:

Visits can have multiple time windows.
{
  "visits": [
    {
      "id": "Visit A",
      "location": [34.31785, -83.82816],
      "serviceDuration": "PT1H30M",
      "timeWindows": [
        {
            "minStartTime": "2027-02-01T10:00:00Z",
            "maxEndTime": "2027-02-01T13:00:00Z"
        }
      ]
    }
  ]
}

minStartTime and maxEndTime use the ISO 8601 date and time format with offset to UTC format.

The Require service max end time hard constraint is invoked when the technician’s departure time is later than the visit’s maxEndTime. The constraint adds a hard penalty to the dataset score for every second between the visit’s maxEndTime and the technician’s departure time.

Visits will not be scheduled if they break this constraint.

Below is an example dataset with one vehicle and one visit with a time window:

  • Input

  • Output

Try this example in Timefold Platform by saving the JSON into a file called time-windows-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": "Time window 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": [34.31785, -83.82816],
        "serviceDuration": "PT1H30M",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T10: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>
{
  "metadata": {
    "id": "ID",
    "name": "Time window example",
    "submitDateTime": "2024-09-16T04:31:15.391186569Z",
    "startDateTime": "2024-09-16T04:31:21.096410685Z",
    "activeDateTime": "2024-09-16T04:31:21.196410685Z",
    "completeDateTime": "2024-09-16T04:31:24.144757106Z",
    "shutdownDateTime": "2024-09-16T04:31:24.244757106Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-8695soft",
    "tags": null,
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T10:11:58Z",
                "startServiceTime": "2027-02-01T10:11:58Z",
                "departureTime": "2027-02-01T11:41:58Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT1H11M58S",
                "travelDistanceMetersFromPreviousStandstill": 94719,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT2H24M55S",
              "travelTimeFromStartLocationToFirstVisit": "PT1H11M58S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT1H12M57S",
              "totalTravelDistanceMeters": 187829,
              "travelDistanceFromStartLocationToFirstVisitMeters": 94719,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 93110,
              "endLocationArrivalTime": "2027-02-01T12:54:55Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT2H24M55S",
    "travelTimeFromStartLocationToFirstVisit": "PT1H11M58S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT1H12M57S",
    "totalTravelDistanceMeters": 187829,
    "travelDistanceFromStartLocationToFirstVisitMeters": 94719,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 93110,
    "totalUnassignedVisits": 0
  }
}

modelOutput contains the itinerary for Carl’s shift and Visit A.

In this example, Carl’s shift begins at 09:00. His travel time is scheduled as soon as he starts his shift and he arrives at the visit at 10:11, which means he can complete the visit in the time window.

The visit duration lasts for 1 hour and 30 minutes "PT1H30M". This is the only visit on Carl’s itinerary today, so when Visit A is completed, he is scheduled to travel back to his startLocation.

visit time windows basic

2. Multiple time windows

A visit can have multiple time windows in which it’s possible to schedule a visit.

For example, Visit A could occur on:

  • Monday 1-feb between 08:30 and 11:30

  • Monday 1-feb between 13:30 and 17:00

  • Tuesday 2-feb between 09:30 and 12:30

Timefold assigns that visit to a vehicle shift during one of those time windows, for instance, to Carl on his Monday shift:

visit multiple time windows

Multiple time windows are assigned to a single visit as follows:

{
  "visits": [
    {
      "id": "Visit A",
      "timeWindows": [
        {
          "minStartTime": "2027-02-01T08:30:00Z",
          "maxEndTime": "2027-02-01T11:30:00Z"
        },
        {
          "minStartTime": "2027-02-01T13:30:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        },
        {
          "minStartTime": "2027-02-02T09:30:00Z",
          "maxEndTime": "2027-02-02T12:30:00Z"
        }
      ]
    }
  ]
}

The examples assume the visit time windows use the UTC time zone offset. Please see Time zones and daylight-saving time (DST) chapter for more details.

3. Preferred time windows

Preferred time windows are used when customers have a preferred time for the visit but are also available at other times, for example, when they prefer a morning visit over an afternoon visit even though they are available all day.

This can be achieved using preferred time windows. Preferred time windows are assigned to a single visit as follows:

{
  "visits": [
    {
      "id": "Visit A",
      "preferredTimeWindows": [
        {
          "minStartTime": "2027-02-01T08:30:00Z",
          "maxEndTime": "2027-02-01T11:30:00Z"
        },
        {
          "minStartTime": "2027-02-01T13:30:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z"
        },
        {
          "minStartTime": "2027-02-02T09:30:00Z",
          "maxEndTime": "2027-02-02T12:30:00Z"
        }
      ]
    }
  ]
}

Preferred time windows can be used independently or combined with required time windows.

When combined with required time windows, the visit must be scheduled within one of the required time windows, and Timefold will try to schedule the visit within an overlapping preferred time windows if possible.

If a given preferred time windows doesn’t have an overlap with any of the regular time windows, a validation warning will be issued as this preferred time window can never be satisfied.

If a preferredTimeWindow has no overlap with a timeWindow, the preferred time window will not be scheduled.

Below is a dataset with a visit that has both a required time window and a preferred time window.

Visit A is scheduled during both the required time window and the preferred time window.

  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called preferred-time-windows-input.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": "Preferred time window example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T14:00:00Z",
            "maxEndTime": "2027-02-01T19:00:00Z"
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T08:00:00Z",
            "maxEndTime": "2027-02-01T13:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.62321, -84.02451],
        "serviceDuration": "PT1H30M",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T08:00:00Z",
            "maxEndTime": "2027-02-01T16:00:00Z"
          }
        ],
        "preferredTimeWindows": [
          {
            "minStartTime": "2027-02-01T14:00:00Z",
            "maxEndTime": "2027-02-01T18: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",
    "originId": "",
    "name": "Preferred time window example",
    "submitDateTime": "2025-11-05T10:56:53.421115+01:00",
    "startDateTime": "2025-11-05T10:56:53.427242+01:00",
    "activeDateTime": "2025-11-05T10:56:53.427472+01:00",
    "completeDateTime": "2025-11-05T10:57:23.430588+01:00",
    "shutdownDateTime": "2025-11-05T10:57:23.43059+01:00",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-35663soft",
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T14:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T14:19:47Z",
                "startServiceTime": "2027-02-01T14:19:47Z",
                "departureTime": "2027-02-01T15:49:47Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT19M47S",
                "travelDistanceMetersFromPreviousStandstill": 16492,
                "minStartTravelTime": "2027-02-01T08:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT39M34S",
              "travelTimeFromStartLocationToFirstVisit": "PT19M47S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT19M47S",
              "totalTravelDistanceMeters": 32984,
              "travelDistanceFromStartLocationToFirstVisitMeters": 16492,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 16492,
              "endLocationArrivalTime": "2027-02-01T16:09:34Z"
            }
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startTime": "2027-02-01T08:00:00Z",
            "itinerary": [],
            "metrics": {
              "totalTravelTime": "PT0S",
              "travelTimeFromStartLocationToFirstVisit": "PT0S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT0S",
              "totalTravelDistanceMeters": 0,
              "travelDistanceFromStartLocationToFirstVisitMeters": 0,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 0
            }
          }
        ]
      }
    ],
    "unassignedVisits": []
  },
  "inputMetrics": {
    "visits": 1,
    "visitGroups": 0,
    "visitDependencies": 0,
    "mandatoryVisits": 1,
    "optionalVisits": 0,
    "vehicles": 2,
    "vehicleShifts": 2,
    "visitsWithSla": 0,
    "movableVisits": 0,
    "pinnedVisits": 0
  },
  "kpis": {
    "totalTravelTime": "PT39M34S",
    "travelTimeFromStartLocationToFirstVisit": "PT19M47S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT19M47S",
    "totalTravelDistanceMeters": 32984,
    "travelDistanceFromStartLocationToFirstVisitMeters": 16492,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 16492,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 1,
    "assignedMandatoryVisits": 1,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 0
  }
}
`modelOutput` contains the solution with Visit A assigned to Carl as that assignment satisfies the preferred time window.

4. Time windows and other visits

Carl had a quiet shift in our first example, but he usually has more than one visit to complete per shift.

When there are multiple visits in a shift, Timefold assigns visits in an order that conforms to the specified time windows.

Below is an example dataset with one vehicle, two visits without time windows, and one visit with a time window.

  • Input

  • Output

Try this example in Timefold Platform by saving the JSON into a file called time-windows-example-2.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": "Time windows and other visits example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T13:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT1H30M"
      }
    ]
  }
}
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": "Time windows and other visits example",
    "submitDateTime": "2024-07-15T06:29:07.287034163Z",
    "startDateTime": "2024-07-15T06:29:13.075791707Z",
    "activeDateTime": "2024-07-15T06:29:13.175791707Z",
    "completeDateTime": "2024-07-15T06:33:11.696296779Z",
    "shutdownDateTime": "2024-07-15T06:33:11.796296779Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-5017soft",
    "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:26:11Z",
                "startServiceTime": "2027-02-01T09:26:11Z",
                "departureTime": "2027-02-01T10:26:11Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT26M11S",
                "travelDistanceMetersFromPreviousStandstill": 29015,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T10:30:36Z",
                "startServiceTime": "2027-02-01T10:30:36Z",
                "departureTime": "2027-02-01T12:00:36Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT4M25S",
                "travelDistanceMetersFromPreviousStandstill": 2702,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T12:24:05Z",
                "startServiceTime": "2027-02-01T13:00:00Z",
                "departureTime": "2027-02-01T14:30:00Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT23M29S",
                "travelDistanceMetersFromPreviousStandstill": 24210,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H23M37S",
              "travelTimeFromStartLocationToFirstVisit": "PT26M11S",
              "travelTimeBetweenVisits": "PT27M54S",
              "travelTimeFromLastVisitToEndLocation": "PT29M32S",
              "totalTravelDistanceMeters": 89911,
              "travelDistanceFromStartLocationToFirstVisitMeters": 29015,
              "travelDistanceBetweenVisitsMeters": 26912,
              "travelDistanceFromLastVisitToEndLocationMeters": 33984,
              "endLocationArrivalTime": "2027-02-01T14:59:32Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT1H23M37S",
    "travelTimeFromStartLocationToFirstVisit": "PT26M11S",
    "travelTimeBetweenVisits": "PT27M54S",
    "travelTimeFromLastVisitToEndLocation": "PT29M32S",
    "totalTravelDistanceMeters": 89911,
    "travelDistanceFromStartLocationToFirstVisitMeters": 29015,
    "travelDistanceBetweenVisitsMeters": 26912,
    "travelDistanceFromLastVisitToEndLocationMeters": 33984,
    "totalUnassignedVisits": 0
  }
}

modelOutput contains the itinerary for Carl’s shifts with Visit A scheduled within the correct time window.

In the output, you can see Timefold assigns visit A as Carl’s 3rd visit that shift.

In the example below, Carl arrives at 14:00 to start work. 14:00 is after the minStartTime of 13:00, and the visit will be completed before the maxEndTime which is 17:00.

visit time windows

5. Fixed visits

Occasionally, visits have to happen at a specific time (fixed visits), and other visits must be scheduled around them.

This can be achieved by adding a maxStartTime that is equal to the minStartTime, and a maxEndTime that occurs after the visit should end.

Given a serviceDuration of "PT1H30M" and minStartTime of "13:00":

{
  "id": "Visit A",
  "timeWindows": [
    {
      "minStartTime": "2027-02-01T13:00:00Z",
      "maxStartTime": "2027-02-01T13:00:00Z",
      "maxEndTime": "2027-02-01T15:00:00Z"
    }
  ]
}

The Require service max start time hard constraint is invoked when the service start time (the time the technician arrives on site) is later than the maxStartTime. The constraint adds a hard penalty to the dataset score for every second between the visit’s maxStartTime and the time the service starts.

Visits will not be scheduled if they break this constraint.

In the following example, Visit A is a fixed visit that must start at 13:00.

  • 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": "Fixed visit example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T13:00:00Z",
            "maxStartTime": "2027-02-01T13:00:00Z",
            "maxEndTime": "2027-02-01T14:30:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT1H30M"
      }
    ]
  }
}
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": "Fixed visit example",
    "submitDateTime": "2024-08-21T05:11:03.373086923Z",
    "startDateTime": "2024-08-21T05:11:11.526430596Z",
    "activeDateTime": "2024-08-21T05:11:11.626430596Z",
    "completeDateTime": "2024-08-21T05:16:11.947930487Z",
    "shutdownDateTime": "2024-08-21T05:16:12.047930487Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-5017soft",
    "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:26:11Z",
                "startServiceTime": "2027-02-01T09:26:11Z",
                "departureTime": "2027-02-01T10:26:11Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT26M11S",
                "travelDistanceMetersFromPreviousStandstill": 29015,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit C",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T10:30:36Z",
                "startServiceTime": "2027-02-01T10:30:36Z",
                "departureTime": "2027-02-01T12:00:36Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT4M25S",
                "travelDistanceMetersFromPreviousStandstill": 2702,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T12:24:05Z",
                "startServiceTime": "2027-02-01T13:00:00Z",
                "departureTime": "2027-02-01T14:30:00Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT23M29S",
                "travelDistanceMetersFromPreviousStandstill": 24210,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H23M37S",
              "travelTimeFromStartLocationToFirstVisit": "PT26M11S",
              "travelTimeBetweenVisits": "PT27M54S",
              "travelTimeFromLastVisitToEndLocation": "PT29M32S",
              "totalTravelDistanceMeters": 89911,
              "travelDistanceFromStartLocationToFirstVisitMeters": 29015,
              "travelDistanceBetweenVisitsMeters": 26912,
              "travelDistanceFromLastVisitToEndLocationMeters": 33984,
              "endLocationArrivalTime": "2027-02-01T14:59:32Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT1H23M37S",
    "travelTimeFromStartLocationToFirstVisit": "PT26M11S",
    "travelTimeBetweenVisits": "PT27M54S",
    "travelTimeFromLastVisitToEndLocation": "PT29M32S",
    "totalTravelDistanceMeters": 89911,
    "travelDistanceFromStartLocationToFirstVisitMeters": 29015,
    "travelDistanceBetweenVisitsMeters": 26912,
    "travelDistanceFromLastVisitToEndLocationMeters": 33984,
    "totalUnassignedVisits": 0
  }
}

modelOutput contains the solution with Visit A scheduled to start at 13:00 and finish at 14:30.

6. Too early (wait time)

With time windows it is sometimes necessary for a technician to arrive early and wait.

For example, if Carl arrives too early at 12:00, before the minStartTime of 13:00, he needs to wait for one hour to start work.

visit time windows too early

Despite the lost time spent waiting, this can be the most productive solution.

Time windows automatically incentivizes Timefold to fill up any waiting time with other visits. However, the Field Service Routing model attempts to minimize travel time and maximize productivity so wait times are kept to a minimum.

visit time windows the wait time paradox

7. Too late

If Carl arrives too late, he can’t finish the job in time. In the example below, if he arrives at 12:30 for a job that ends at 13:00, he won’t have time to finish the job. This is an infeasible schedule.

visit time windows too late

Because the end time is earlier than Carl would finish, Timefold is incentivized to assign that visit earlier, or leave it unassignThe model will try to schedule visits within these preferred time windows, but it can also schedule them outside of these windows if necessary to optimize the overall schedule. ed.

Next

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

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

  • Learn about Shift hours and overtime.

  • Schedule Lunch breaks and personal appointments.

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