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 dependencies

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
    • Real-time planning with patches
      • Real-time planning: extended visit (using patches)
      • Real-time planning: reassignment (using patches)
      • Real-time planning: emergency visit (using patches)
      • Real-time planning: no show (using patches)
      • Real-time planning: technician ill (using patches)
      • Real-time planning: pinning visits (using patches)
    • Scenarios
      • Configuring labor law compliance
      • Ferry connections
      • Long-running visits
    • Changelog
    • Upgrade to the latest version
    • Feature requests

Visit dependencies

In field service routing it is often necessary to perform visits in a specific order. This could be multiple visits at the same location, such as, a visit where a wall must be constructed before the wall can be painted in another visit. A third visit may even need to be completed after the painting visit, for instance, fitting an electrical socket to the painted wall.

Sometimes it is necessary to include delays between visits, for example, if paint needs to dry before further work can start.

Visits at different locations can also be dependent visits, such as, if one task needs to be completed at a workshop before further work at the customer location can be performed.

There can also be requirements that the same technician attends multiple dependent visits.

This guide describes how to schedule visits in the correct order, with the following examples:

  • 1. Single visit dependency
  • 2. Multiple visit dependencies
  • 3. Visit dependency with delay
  • 4. Visit dependency with coordination
  • 5. Visit dependency assignment type
  • 6. Visit dependency pooling type
  • 7. Alternative visits

1. Single visit dependency

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.

When a visit depends on another visit, the dependent visit can start no sooner than the visit it depends on ends.

For instance, if Carl is assigned Visit B in his shift itinerary, and Visit B is an electrician task that cannot start until after a related painting task, Visit A, is completed:

visit dependency

Visit A can be performed by another technician on another day, but it needs to be completed before Visit B starts.

Because Visit B is dependent on Visit A, Visit B cannot be scheduled unless Visit A has also been scheduled. However, because Visit A is not dependent on Visit B, it is possible for Visit A to be scheduled even if Visit B is not scheduled.
If Visit A and Visit B need to be completed by the same technician, see Visit dependency with coordination for details.

Visit dependencies are defined by adding visitDependencies to a visit and specifying a precedingVisit:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M"
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H30M",
      "visitDependencies": [
        {
          "id": "Visit dependency B on A",
          "precedingVisit": "Visit A"
        }
      ]
    }
  ]
}

The Require visit dependency prerequisite assigned hard constraint is invoked when visits have preceding visits. The constraint adds a hard penalty to the dataset score when the preceding visit is not scheduled. The penalty is derived from the service duration of the visit and the preceding visit.

Visits will not be scheduled if they break this constraint.

1.1. Single visit dependency example

In this example, visitDependencies specifies Visit A is a precedingVisit of Visit B. Visit A must be completed before Visit B can start. Both visits are assigned: Visit A is completed first, and Visit B starts only after Visit A has ended.

visit dependency too early
Visits can depend on multiple visits. See Multiple visit dependencies for more information.
  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Visit dependencies example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M"
      },
      {
        "id": "Visit B",
        "location": [33.78767, -84.43887],
        "serviceDuration": "PT1H30M",
        "visitDependencies": [
          {
            "id": "Visit dependency B on A",
            "precedingVisit": "Visit A"
          }
        ]
      }
    ]
  }
}
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": "Visit dependencies example",
    "submitDateTime": "2024-08-05T06:24:59.072142988Z",
    "startDateTime": "2024-08-05T06:25:04.91066022Z",
    "activeDateTime": "2024-08-05T06:25:04.95066022Z",
    "completeDateTime": "2024-08-05T06:30:05.500772913Z",
    "shutdownDateTime": "2024-08-05T06:30:05.600772913Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-4171soft",
    "tags": null,
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:29:57Z",
                "startServiceTime": "2027-02-01T09:29:57Z",
                "departureTime": "2027-02-01T10:59:57Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT29M57S",
                "travelDistanceMetersFromPreviousStandstill": 31492,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:06:40Z",
                "startServiceTime": "2027-02-01T11:06:40Z",
                "departureTime": "2027-02-01T12:36:40Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT6M43S",
                "travelDistanceMetersFromPreviousStandstill": 4123,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H9M31S",
              "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
              "travelTimeBetweenVisits": "PT6M43S",
              "travelTimeFromLastVisitToEndLocation": "PT32M51S",
              "totalTravelDistanceMeters": 71177,
              "travelDistanceFromStartLocationToFirstVisitMeters": 31492,
              "travelDistanceBetweenVisitsMeters": 4123,
              "travelDistanceFromLastVisitToEndLocationMeters": 35562,
              "endLocationArrivalTime": "2027-02-01T13:09:31Z"
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT1H9M31S",
    "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
    "travelTimeBetweenVisits": "PT6M43S",
    "travelTimeFromLastVisitToEndLocation": "PT32M51S",
    "totalTravelDistanceMeters": 71177,
    "travelDistanceFromStartLocationToFirstVisitMeters": 31492,
    "travelDistanceBetweenVisitsMeters": 4123,
    "travelDistanceFromLastVisitToEndLocationMeters": 35562,
    "totalUnassignedVisits": 0
  }
}

2. Multiple visit dependencies

Visits can declare multiple dependencies. In the following example:

  • Visit C depends on Visit A and Visit B

  • Visit D depends on Visit C

  • Visit E depends on Visit C

visit multiple dependencies

The dependency is defined as follows:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M"
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H"
    },
    {
      "id": "Visit C",
      "location": [33.77432, -84.43844],
      "serviceDuration": "PT1H30M",
      "visitDependencies": [
        {
          "id": "Visit dependency C on A",
          "precedingVisit": "Visit A"
        },
        {
          "id": "Visit dependency C on B",
          "precedingVisit": "Visit B"
        }
      ]
    },
    {
      "id": "Visit D",
      "location": [33.77654, -84.43821],
      "serviceDuration": "PT1H",
      "visitDependencies": [
        {
          "id": "Visit dependency D on C",
          "precedingVisit": "Visit C"
        }
      ]
    },
    {
      "id": "Visit E",
      "location": [33.77322, -84.43934],
      "serviceDuration": "PT1H",
      "visitDependencies": [
        {
          "id": "Visit dependency E on C",
          "precedingVisit": "Visit C"
        }
      ]
    }
  ]
}

3. Visit dependency with delay

When Visit B depends on Visit A, you can specify a minimal or maximal delay (or both) between the end of Visit A and the start of Visit B.

Visit A might be a painting task and the follow-up Visit B task needs to wait at least three hours until the paint dries:

visit dependency min delay

Similarly, there might be a painting task where an undercoat is applied and the next coat must be added within a specific amount of time or the work would have to be done again:

visit dependency max delay

Minimum or maximum delays can be expressed in two ways:

  • Duration: a number of days/hours/minutes/seconds after the end of Visit A when Visit B can start the earliest (minimum) or the latest (maximum).

  • Point in time: a specified date and time relative to the end of Visit A when Visit B can start the earliest (minimum) or the latest (maximum). For example, next day (after Visit A ends) at 06:00.

The two ways of expressing delays (duration and point in time) are mutually exclusive, specifying both for either the minimum or the maximum delay of a single visit dependency results in a validation error.

The Require visit dependency delay hard constraint is invoked if a visit starts before the minimal delay or after the maximal delay after the departure time from the defined preceding visit. The constraint adds a hard penalty to the dataset score derived from the service durations of the visit and the preceding visit.

Visits will not be scheduled if they break this constraint.

3.1. Visit dependency with delay as duration

The minimum and maximum delay as duration is expressed as an ISO 8601 duration.

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M"
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H",
      "visitDependencies": [
        {
          "id": "Visit dependency B on A",
          "precedingVisit": "Visit A",
          "minDelay": "PT3H",
          "maxDelay": "PT6H"
        }
      ]
    }
  ]
}

3.2. Visit dependency with delay as a point in time

The minDelayTo and maxDelayTo objects of a visit dependency can be used to model the minimum and maximum delay for a visit dependency. While the naming of the attributes for these objects differs in the prefix (minStartDateAdjuster vs. maxStartDateAdjuster), the preceding visit’s departure time is adjusted in the same way for both of them. See the following sections for details.

3.2.1. Visit dependency with minimum delay as a point in time

The minimum delay as a point in time is expressed with minDelayTo with the following attributes:

  • minStartDateAdjuster: (required) the name of the function (see below) that adjusts the date part of the delayed visit’s minimum start. Example: NEXT_DAY, NEXT_MONDAY.

  • minStartDateAdjusterIncrement: (optional) the increment which determines how many times minStartDateAdjuster is applied. The minimum value is 1, because the minStartDateAdjuster has to be applied at least once.

  • minStartTime: (optional) the time part (ISO 8601 local datetime) of the delayed visit’s minimum start (inclusive). Being a local datetime, it does not include the time zone offset. The default value is midnight (start of day): 00:00.

  • timezone: (optional) the region-based TZDB identifier of the time zone which applies to the minStartDateAdjuster and minStartTime combination. Example: "America/New_York", "Europe/Brussels".

If the timezone is omitted, the zone offset of the visit dependency departureTime is used for the adjusted date. If DST changes need to be handled, the time zone must be supplied.

With the above, we can express the following conditions on the dependent visit’s minimum start time:

  • Visit B can start no sooner than midnight of the day after Visit A ends:

    • minStartDateAdjuster = NEXT_DAY

  • Visit B can start no sooner than 14:00 of the day after Visit A ends:

    • minStartDateAdjuster = NEXT_DAY

    • minStartTime = 14:00

  • Visit B can start no sooner than 12:00 of the next Monday after Visit A ends:

    • minStartDateAdjuster = NEXT_MONDAY

    • minStartTime = 12:00

  • Visit B can start no sooner than 12:00 of the day two days after Visit A ends:

    • minStartDateAdjuster = NEXT_DAY

    • minStartDateAdjusterIncrement = 2

    • minStartTime = 12:00

  • Visit B can start no sooner than midnight of the first day of the month, six months after visit A ends.

    • minStartDateAdjuster = NEXT_MONTH

    • minStartDateAdjusterIncrement = 6

  • Visit B can start no sooner than 12:00 of the first day of the next month after Visit A ends, taking a possible DST change in the America/New_York time zone into account:

    • minStartDateAdjuster = NEXT_MONTH

    • minStartTime = 12:00

    • timezone = America/New_York

The second situation is depicted in the image below:

visit dependency min delay to

The following example input shows how to model that Visit B can start no sooner than 08:00 on the Monday after Visit A has ended, taking a possible DST change in the America/New_York time zone into account:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M"
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H",
      "visitDependencies": [
        {
          "id": "Visit dependency B on A",
          "precedingVisit": "Visit A",
          "minDelayTo": {
            "minStartDateAdjuster": "NEXT_MONDAY",
            "minStartDateAdjusterIncrement" : 1,
            "minStartTime": "08:00",
            "timezone": "America/New_York"
          }
        }
      ]
    }
  ]
}

3.2.2. Visit dependency with maximum delay as a point in time

The maximum delay as a point in time is expressed with maxDelayTo with the following attributes:

  • maxStartDateAdjuster: (required) the name of the function (see below) that adjusts the date part of the delayed visit’s maximum start. Example: NEXT_DAY, NEXT_MONDAY.

  • maxStartDateAdjusterIncrement: (optional) the increment which determines how many times maxStartDateAdjuster is applied. The minimum value is 1, because the maxStartDateAdjuster has to be applied at least once.

  • maxStartTime: (optional) the time part (ISO 8601 local datetime) of the delayed visit’s maximum start (inclusive). Being a local datetime, it does not include the time zone offset. The default value is midnight (start of day): 00:00.

  • timezone: (optional) the region-based TZDB identifier of the time zone which applies to the maxStartDateAdjuster and maxStartTime combination. Example: "America/New_York", "Europe/Brussels".

If the timezone is omitted, the zone offset of the visit dependency departureTime is used for the adjusted date. If DST changes need to be handled, the time zone must be supplied.

With the above, we can express the following conditions on the dependent visit’s maximum start time:

  • Visit B can start no later than midnight of the day after Visit A ends:

    • maxStartDateAdjuster = NEXT_DAY

  • Visit B can start no later than 14:00 of the day after Visit A ends:

    • maxStartDateAdjuster = NEXT_DAY

    • maxStartTime = 14:00

  • Visit B can start no later than 12:00 of the next Monday after Visit A ends:

    • maxStartDateAdjuster = NEXT_MONDAY

    • maxStartTime = 12:00

  • Visit B can start no later than 12:00 of the day two days after Visit A ends:

    • maxStartDateAdjuster = NEXT_DAY

    • maxStartDateAdjusterIncrement = 2

    • maxStartTime = 12:00

  • Visit B can start no later than midnight of the first day of the month, six months after visit A ends.

    • maxStartDateAdjuster = NEXT_MONTH

    • maxStartDateAdjusterIncrement = 6

  • Visit B can start no later than 12:00 of the first day of the next month after Visit A ends, taking a possible DST change in the America/New_York time zone into account:

    • maxStartDateAdjuster = NEXT_MONTH

    • maxStartTime = 12:00

    • timezone = America/New_York

The second situation is depicted in the image below:

visit dependency max delay to

The following example input shows how to model that Visit B can start no later than 16:00 on the Monday after Visit A has ended, taking a possible DST change in the America/New_York time zone into account:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M"
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H",
      "visitDependencies": [
        {
          "id": "Visit dependency B on A",
          "precedingVisit": "Visit A",
          "maxDelayTo": {
            "maxStartDateAdjuster": "NEXT_MONDAY",
            "maxStartDateAdjusterIncrement" : 1,
            "maxStartTime": "16:00",
            "timezone": "America/New_York"
          }
        }
      ]
    }
  ]
}

3.2.3. Adjuster functions

The following is a list of available date adjusting functions (to be used in minStartDateAdjuster and maxStartDateAdjuster):

Name Description

SAME_DAY

Keeps the same day - useful when specifying just time adjustment, such as "at 16:00 of the same day".

NEXT_DAY

Adjusts the date to the next day.

NEXT_MONTH

Adjusts the date to the first day of the next month.

NEXT_MONDAY

Adjusts the date to the next Monday.

NEXT_TUESDAY

Adjusts the date to the next Tuesday.

NEXT_WEDNESDAY

Adjusts the date to the next Wednesday.

NEXT_THURSDAY

Adjusts the date to the next Thursday.

NEXT_FRIDAY

Adjusts the date to the next Friday.

NEXT_SATURDAY

Adjusts the date to the next Saturday.

NEXT_SUNDAY

Adjusts the date to the next Sunday.

NEXT_WORKING_DAY

Adjusts the date to the next working day. Working days default to Monday through Friday but may be customized by setting the workingDays field in the dateAdjusterConfiguration object of the modelInput. Holidays can be accounted for by setting the holidays field in the dateAdjusterConfiguration, which takes an array of ISO 8601 dates.

We have intentionally omitted NEXT_WEEK (adjusting the date to the first day of the next week) as you can achieve its behaviour using NEXT_MONDAY/NEXT_SUNDAY/etc. If you have a specific use-case demanding this functionality, please let us know.
If you need to specify a delay of a more specific amount of days/weeks, use the minStartDateAdjusterIncrement to apply the minStartDateAdjuster multiple times. Example: you need to delay two days, use NEXT_DAY and set the increment to 2.

4. Visit dependency with coordination

When Visit B depends on Visit A, you can specify additional conditions to hold between Visit A and Visit B by using visitDependency.coordination attribute.

To specify that Visit B has to be assigned to the same vehicle (not to the same vehicle shift) as Visit A, use "coordination": "SAME_VEHICLE". To specify that Visit B has to be assigned to the same vehicle shift as Visit A, use "coordination": "SAME_SHIFT":

visit dependency same vehicle

The Require the same vehicle for dependent visit or Require the same vehicle shift for dependent visit hard constraint is invoked when a visit required to be assigned to the same vehicle (or shift) as its preceding visit is assigned to a different vehicle (or shift). The constraints add a hard penalty to the dataset score derived from the service durations of the visit and the preceding visit.

Visits will not be scheduled if they break one of these constraints.

4.1. Visit dependency with coordination example

The following model input instructs Timefold to assign both Visit A and Visit B either to Carl or Beth. Both visits are assigned to the same technician, satisfying the coordination requirement.

  • 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]
{
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z"
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-2-1",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M"
      },
      {
        "id": "Visit B",
        "location": [33.78767, -84.43887],
        "serviceDuration": "PT1H",
        "visitDependencies": [
          {
            "id": "Visit dependency B on A",
            "precedingVisit": "Visit A",
            "coordination": "SAME_VEHICLE"
          }
        ]
      }
    ]
  }
}
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": "NAME",
    "submitDateTime": "2024-11-20T12:10:30.887916014Z",
    "startDateTime": "2024-11-20T12:10:37.59120911Z",
    "activeDateTime": "2024-11-20T12:10:37.69120911Z",
    "completeDateTime": "2024-11-20T12:15:38.024751909Z",
    "shutdownDateTime": "2024-11-20T12:15:38.124751909Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-75294soft",
    "tags": null,
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:29:56Z",
                "startServiceTime": "2027-02-01T09:29:56Z",
                "departureTime": "2027-02-01T10:59:56Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT29M56S",
                "travelDistanceMetersFromPreviousStandstill": 31493,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              },
              {
                "id": "Visit B",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T11:06:12Z",
                "startServiceTime": "2027-02-01T11:06:12Z",
                "departureTime": "2027-02-01T12:06:12Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT6M16S",
                "travelDistanceMetersFromPreviousStandstill": 4123,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H8M36S",
              "travelTimeFromStartLocationToFirstVisit": "PT29M56S",
              "travelTimeBetweenVisits": "PT6M16S",
              "travelTimeFromLastVisitToEndLocation": "PT32M24S",
              "totalTravelDistanceMeters": 71178,
              "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
              "travelDistanceBetweenVisitsMeters": 4123,
              "travelDistanceFromLastVisitToEndLocationMeters": 35562,
              "endLocationArrivalTime": "2027-02-01T12:38:36Z"
            }
          }
        ]
      },
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-2-1",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [],
            "metrics": {
              "totalTravelTime": "PT0S",
              "travelTimeFromStartLocationToFirstVisit": "PT0S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT0S",
              "totalTravelDistanceMeters": 0,
              "travelDistanceFromStartLocationToFirstVisitMeters": 0,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 0,
              "endLocationArrivalTime": null
            }
          }
        ]
      }
    ]
  },
  "kpis": {
    "totalTravelTime": "PT1H8M36S",
    "travelTimeFromStartLocationToFirstVisit": "PT29M56S",
    "travelTimeBetweenVisits": "PT6M16S",
    "travelTimeFromLastVisitToEndLocation": "PT32M24S",
    "totalTravelDistanceMeters": 71178,
    "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
    "travelDistanceBetweenVisitsMeters": 4123,
    "travelDistanceFromLastVisitToEndLocationMeters": 35562,
    "totalUnassignedVisits": 0
  }
}
Visit dependency coordination can be combined with the minDelay or minDelayTo attributes.

The available visit dependency coordination types are:

Name Description

NONE

No additional coordination requirements. (default)

SAME_VEHICLE

Both visit and its dependency have to be assigned to the same vehicle.

5. Visit dependency assignment type

By default, when Visit B depends on Visit A and assigning Visit A would prevent Visit B from being assigned, Visit A is also left unassigned. This ensures both visits remain unassigned rather than Visit A being assigned while Visit B cannot be.

For example, if Visit A is only available on Day 2 but Visit B’s time window closes at the end of Day 1, assigning Visit A would prevent Visit B from ever starting after it. In this situation, the default behavior keeps Visit A unassigned so that Visit B is not silently blocked.

However, in some cases it is desirable to allow Visit A to be assigned even when it prevents Visit B from being assigned; for instance, if Visit A is a high-priority task that must always be completed regardless of whether Visit B can follow.

To allow the preceding visit to be assigned even when it blocks the following visit, set "assignmentType": "ALLOW_TO_BLOCK_FOLLOWING" on the visit dependency.

Without assignmentType set (or with DISALLOW_TO_BLOCK_FOLLOWING), Visit A would also remain unassigned to avoid blocking Visit B.

The assignmentType attribute can be combined with minDelay, minDelayTo, maxDelay, maxDelayTo, and coordination attributes.

5.1. Visit dependency assignment type example

In this example, Visit A is only available on Day 2, while Visit B (optional) has a time window on Day 1 and depends on Visit A. Because Visit A must finish before Visit B can start, Visit B can never be assigned after Visit A (as you can see in the output below, this input produces a validation warning).

With ALLOW_TO_BLOCK_FOLLOWING, Visit A is still scheduled even though Visit B cannot follow.

  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Visit dependency assignment type example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-2",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-02T08:00:00Z",
            "maxEndTime": "2027-02-02T20:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H",
        "timeWindows": [
          {
            "minStartTime": "2027-02-02T08:00:00Z",
            "maxEndTime": "2027-02-02T20:00:00Z"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.78767, -84.43887],
        "serviceDuration": "PT1H",
        "priority": "opt-5",
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T08:00:00Z",
            "maxEndTime": "2027-02-01T20:00:00Z"
          }
        ],
        "visitDependencies": [
          {
            "id": "Visit dependency B on A",
            "precedingVisit": "Visit A",
            "assignmentType": "ALLOW_TO_BLOCK_FOLLOWING"
          }
        ]
      }
    ]
  }
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans/<ID>
{
  "metadata": {
    "id": "ID",
    "originId": "ID",
    "name": "Visit dependency assignment type example",
    "submitDateTime": "2026-06-03T15:04:38.044470506+02:00",
    "startDateTime": "2026-06-03T15:04:38.209268093+02:00",
    "activeDateTime": "2026-06-03T15:04:38.209941953+02:00",
    "completeDateTime": "2026-06-03T15:04:39.37503355+02:00",
    "shutdownDateTime": "2026-06-03T15:04:39.375047702+02:00",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-103640soft",
    "validationResult": {
      "summary": "WARNINGS",
      "warnings": [
        "Visit dependency (Visit dependency B on A) delay requirements can never be satisfied within related preceding visit (Visit A) and following visit (Visit B) time windows."
      ]
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-2",
            "startTime": "2027-02-02T08:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "arrivalTime": "2027-02-02T08:30:20Z",
                "startServiceTime": "2027-02-02T08:30:20Z",
                "departureTime": "2027-02-02T09:30:20Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT30M20S",
                "travelDistanceMetersFromPreviousStandstill": 25283,
                "minStartTravelTime": "2027-02-02T00:00:00Z",
                "kind": "VISIT"
              }
            ],
            "metrics": {
              "totalServiceDuration": "PT1H",
              "totalBreakDuration": "PT0S",
              "totalWaitingTime": "PT0S",
              "totalTravelTime": "PT1H40S",
              "travelTimeFromStartLocationToFirstVisit": "PT30M20S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT30M20S",
              "totalTravelDistanceMeters": 50566,
              "travelDistanceFromStartLocationToFirstVisitMeters": 25283,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 25283,
              "endLocationArrivalTime": "2027-02-02T10:00:40Z",
              "overtime": "PT0S",
              "availableOvertime": "PT0S"
            }
          }
        ],
        "metrics": {
          "activatedShifts": 1,
          "assignedVisits": 1,
          "totalShiftDuration": "PT2H40S",
          "totalServiceDuration": "PT1H",
          "totalTravelTime": "PT1H40S",
          "totalTravelDistanceMeters": 50566,
          "totalBreakTime": "PT0S",
          "totalWaitingTime": "PT0S",
          "totalOvertime": "PT0S",
          "availableOvertime": "PT0S"
        }
      }
    ],
    "unassignedVisits": [
      "Visit B"
    ]
  },
  "inputMetrics": {
    "vehicles": 1,
    "vehicleShifts": 1,
    "visits": 2,
    "mandatoryVisits": 1,
    "optionalVisits": 1,
    "pinnedVisits": 0,
    "visitsWithSla": 0,
    "visitGroups": 0,
    "visitDependencies": 1,
    "excludedVisits": 0,
    "movableVisits": 0
  },
  "kpis": {
    "averageTravelTimePerVisit": "PT1H40S",
    "totalTravelTime": "PT1H40S",
    "travelTimeFromStartLocationToFirstVisit": "PT30M20S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT30M20S",
    "averageTravelDistanceMetersPerVisit": 50566,
    "totalTravelDistanceMeters": 50566,
    "travelDistanceFromStartLocationToFirstVisitMeters": 25283,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 25283,
    "totalUnassignedVisits": 1,
    "totalAssignedVisits": 1,
    "assignedMandatoryVisits": 1,
    "unassignedMandatoryVisits": 0,
    "assignedOptionalVisits": 0,
    "unassignedOptionalVisits": 1,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalOvertime": "PT0S",
    "availableOvertime": "PT0S"
  },
  "run": {
    "id": "ID",
    "originId": "ID",
    "name": "Visit dependency assignment type example",
    "submitDateTime": "2026-06-03T15:04:38.044470506+02:00",
    "startDateTime": "2026-06-03T15:04:38.209268093+02:00",
    "activeDateTime": "2026-06-03T15:04:38.209941953+02:00",
    "completeDateTime": "2026-06-03T15:04:39.37503355+02:00",
    "shutdownDateTime": "2026-06-03T15:04:39.375047702+02:00",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-103640soft",
    "validationResult": {
      "summary": "WARNINGS",
      "warnings": [
        "Visit dependency (Visit dependency B on A) delay requirements can never be satisfied within related preceding visit (Visit A) and following visit (Visit B) time windows."
      ]
    }
  }
}
The default assignment type for all visit dependencies in a dataset can be set globally via config.model.overrides.defaultDependencyAssignmentType.

The available visit dependency assignment types are:

Name Description

DISALLOW_TO_BLOCK_FOLLOWING

The preceding visit is not assigned if it would prevent the following visit from being assigned. (default)

ALLOW_TO_BLOCK_FOLLOWING

The preceding visit can be assigned even if it prevents the following visit from being assigned.

6. Visit dependency pooling type

By default, other visits can be freely scheduled between the preceding and following visit of a dependency, and multiple dependency pairs can be interleaved with one another. Sometimes this "pooling" is undesirable. For instance, if Visit A is the pick-up of an item or a piece of equipment which is needed at Visit B, the technician performing Visit B should go there directly after Visit A, without first stopping for an unrelated job along the way. Conversely, some workflows require the opposite: a chain of dependent visits should only ever be pooled with other dependent visits, never with unrelated, one-off jobs.

The visitDependency.poolingType attribute controls whether other visits, or other preceding-following visit pairs, may be scheduled directly before Visit B in the assigned route.

Visit dependency pooling types are defined by adding poolingType to a visitDependencies entry:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M"
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H30M",
      "visitDependencies": [
        {
          "id": "Visit dependency B on A",
          "precedingVisit": "Visit A",
          "poolingType": "DISALLOW_POOLING"
        }
      ]
    }
  ]
}

With "poolingType": "DISALLOW_POOLING" set above, if Visit C is a nearby, unrelated visit, the solver may not schedule Visit C between Visit A and Visit B, even when doing so would otherwise shorten total travel time. Visit C can still be scheduled before Visit A or after Visit B in the same shift.

The Require visit dependency pooling type hard constraint is invoked whenever a visit dependency’s poolingType is set to anything other than ALLOW_POOLING_ALL and the visit scheduled directly before the following visit breaks the configured pooling type. The constraint adds a hard penalty to the dataset score equivalent to the effective service duration of the intermediate visit.

Visits will not be scheduled if they break this constraint.

If a visit dependency’s poolingType is set to anything other than ALLOW_POOLING_ALL, the visit dependency’s coordination is automatically set to SAME_SHIFT. It is recommended to explicitly set the coordination to SAME_SHIFT in the visit dependency definition to ensure the same behavior with future versions.

6.1. Visit dependency pooling type example

In this example, Visit A and B are connected with a visit dependency, making Visit B the following visit of Visit A. Due to some restrictions on this dependency, it can’t be pooled with other visits, so the dependency definition contains the pooling type DISALLOW_POOLING. Visit C is an unrelated visit, but lies on the way between Visit A and Visit B. From an optimized travel time perspective, it would be better to schedule Visit C between Visit A and Visit B.

Because of the DISALLOW_POOLING setting, Visit C cannot be scheduled between Visit A and Visit B, even though it would be more efficient to do so.

  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Visit dependency pooling type example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-2",
            "startLocation": [33.773013, -84.438385],
            "minStartTime": "2027-02-02T08:00:00Z",
            "maxEndTime": "2027-02-02T20:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.773013, -84.438385],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit B",
        "location": [33.771758, -84.371749],
        "serviceDuration": "PT1H",
        "visitDependencies": [
          {
            "id": "Visit dependency B on A",
            "precedingVisit": "Visit A",
            "poolingType": "DISALLOW_POOLING"
          }
        ]
      },
      {
        "id": "Visit C",
        "location": [33.773487, -84.422176],
        "serviceDuration": "PT1H"
      }
    ]
  }
}
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": "Visit dependency pooling type example",
    "submitDateTime": "2026-07-17T13:37:54.42633+02:00",
    "startDateTime": "2026-07-17T13:37:54.474106+02:00",
    "activeDateTime": "2026-07-17T13:37:54.476057+02:00",
    "completeDateTime": "2026-07-17T13:38:24.490679+02:00",
    "shutdownDateTime": "2026-07-17T13:38:24.490684+02:00",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-888soft",
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-2",
            "startTime": "2027-02-02T08:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "arrivalTime": "2027-02-02T08:00:00Z",
                "startServiceTime": "2027-02-02T08:00:00Z",
                "departureTime": "2027-02-02T09:00:00Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT0S",
                "travelDistanceMetersFromPreviousStandstill": 0,
                "minStartTravelTime": "2027-02-02T00:00:00Z",
                "kind": "VISIT"
              },
              {
                "id": "Visit B",
                "arrivalTime": "2027-02-02T09:07:24Z",
                "startServiceTime": "2027-02-02T09:07:24Z",
                "departureTime": "2027-02-02T10:07:24Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT7M24S",
                "travelDistanceMetersFromPreviousStandstill": 6161,
                "minStartTravelTime": "2027-02-02T00:00:00Z",
                "kind": "VISIT"
              },
              {
                "id": "Visit C",
                "arrivalTime": "2027-02-02T10:13:00Z",
                "startServiceTime": "2027-02-02T10:13:00Z",
                "departureTime": "2027-02-02T11:13:00Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT5M36S",
                "travelDistanceMetersFromPreviousStandstill": 4665,
                "minStartTravelTime": "2027-02-02T00:00:00Z",
                "kind": "VISIT"
              }
            ],
            "metrics": {
              "totalServiceDuration": "PT3H",
              "totalBreakDuration": "PT0S",
              "totalWaitingTime": "PT0S",
              "totalTravelTime": "PT14M48S",
              "travelTimeFromStartLocationToFirstVisit": "PT0S",
              "travelTimeBetweenVisits": "PT13M",
              "travelTimeFromLastVisitToEndLocation": "PT1M48S",
              "totalTravelDistanceMeters": 12325,
              "travelDistanceFromStartLocationToFirstVisitMeters": 0,
              "travelDistanceBetweenVisitsMeters": 10826,
              "travelDistanceFromLastVisitToEndLocationMeters": 1499,
              "endLocationArrivalTime": "2027-02-02T11:14:48Z",
              "overtime": "PT0S",
              "availableOvertime": "PT0S"
            }
          }
        ],
        "metrics": {
          "activatedShifts": 1,
          "assignedVisits": 3,
          "totalShiftDuration": "PT3H14M48S",
          "totalServiceDuration": "PT3H",
          "totalTravelTime": "PT14M48S",
          "totalTravelDistanceMeters": 12325,
          "totalBreakTime": "PT0S",
          "totalWaitingTime": "PT0S",
          "totalOvertime": "PT0S",
          "availableOvertime": "PT0S"
        }
      }
    ]
  },
  "kpis": {
    "averageTravelTimePerVisit": "PT4M56S",
    "totalTravelTime": "PT14M48S",
    "travelTimeFromStartLocationToFirstVisit": "PT0S",
    "travelTimeBetweenVisits": "PT13M",
    "travelTimeFromLastVisitToEndLocation": "PT1M48S",
    "averageTravelDistanceMetersPerVisit": 4108,
    "totalTravelDistanceMeters": 12325,
    "travelDistanceFromStartLocationToFirstVisitMeters": 0,
    "travelDistanceBetweenVisitsMeters": 10826,
    "travelDistanceFromLastVisitToEndLocationMeters": 1499,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 3,
    "assignedMandatoryVisits": 3,
    "unassignedMandatoryVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalOvertime": "PT0S",
    "availableOvertime": "PT0S"
  }
}
The default pooling type for all visit dependencies in a dataset can be set globally via config.model.overrides.defaultDependencyPoolingType.

The available visit dependency pooling types are:

Name Description

ALLOW_POOLING_ALL

The preceding-following visit pair can be pooled with other visits or other preceding-following visit pairs. (default)

ALLOW_POOLING_VISITS

The preceding-following visit pair can be pooled with other visits, but not with other preceding-following visit pairs.

ALLOW_POOLING_DEPENDENCIES

The preceding-following visit pair can be pooled with other preceding-following visit pairs, but not with other visits.

DISALLOW_POOLING

The preceding-following visit pair cannot be pooled with any other visits or preceding-following visit pairs.

poolingType can be combined with minDelay, minDelayTo, maxDelay, maxDelayTo, coordination, and assignmentType attributes.

7. Alternative visits

Alternative visits are currently available as a preview feature.

When visits can occur in multiple places, for instance, if a repair can be made on site or at a service center, both locations can be included in the input dataset and exclude each other so that if one visit is assigned the other is not.

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M",
      "exclusions": [
        {
          "id": "Visit B",
          "type": "VISIT"
        }
      ]
    },
    {
      "id": "Visit B",
      "location": [33.78767, -84.43887],
      "serviceDuration": "PT1H30M"
    }
  ]
}

exclusions includes an id and type.

id is the ID of the visit to be excluded. Where there are only two alternative visits, it is not necessary to add the exclusion to both visits.

type is the type of excluded item. Only the type VISIT is currently supported.

You can exclude multiple visits:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M",
      "exclusions": [
        {
          "id": "Visit B",
          "type": "VISIT"
        },
        {
          "id": "Visit C",
          "type": "VISIT"
        }
      ]
    }
  ]
}

When there are more than two alternative visits, you need to add the exclusions to each visit. In this example, if only Visit A excludes the other two visits, Visit B and Visit C might both be assigned instead of just one of them.

The Require no mutually exclusive visits assigned hard constraint is invoked when visits include exclusions. The constraint adds a hard penalty to the dataset score when excluded visits are assigned.

Visits will not be scheduled if they break this constraint.

7.1. Alternative visits example

In the following example, either Visit A or Visit B can be assigned, but not both.

Visit A is assigned and Visit B is left unassigned.

alternative visits
  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Alternative visits example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M",
        "exclusions": [
          {
            "id": "Visit B",
            "type": "VISIT"
          }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.78767, -84.43887],
        "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",
    "originId": "ID",
    "name": "Alternative visits example",
    "submitDateTime": "2026-01-26T04:46:27.612095044Z",
    "startDateTime": "2026-01-26T04:48:08.797659239Z",
    "activeDateTime": "2026-01-26T04:48:08.89053755Z",
    "completeDateTime": "2026-01-26T04:48:39.420614971Z",
    "shutdownDateTime": "2026-01-26T04:48:39.420620311Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-10000medium/-3624soft",
    "tags": [
      "system.type:from-request",
      "system.profile:Standard profile"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Visit A",
                "kind": "VISIT",
                "arrivalTime": "2027-02-01T09:30:34Z",
                "startServiceTime": "2027-02-01T09:30:34Z",
                "departureTime": "2027-02-01T11:00:34Z",
                "effectiveServiceDuration": "PT1H30M",
                "travelTimeFromPreviousStandstill": "PT30M34S",
                "travelDistanceMetersFromPreviousStandstill": 31493,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT1H24S",
              "travelTimeFromStartLocationToFirstVisit": "PT30M34S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT29M50S",
              "totalTravelDistanceMeters": 65500,
              "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 34007,
              "endLocationArrivalTime": "2027-02-01T11:30:24Z"
            }
          }
        ]
      }
    ],
    "unassignedVisits": [
      "Visit B"
    ]
  },
  "inputMetrics": {
    "visits": 2,
    "visitGroups": 0,
    "visitDependencies": 0,
    "mandatoryVisits": 2,
    "optionalVisits": 0,
    "vehicles": 1,
    "vehicleShifts": 1,
    "visitsWithSla": 0,
    "movableVisits": 0,
    "pinnedVisits": 0,
    "excludedVisits": 2
  },
  "kpis": {
    "averageTravelTimePerVisit": "PT1H24S",
    "totalTravelTime": "PT1H24S",
    "travelTimeFromStartLocationToFirstVisit": "PT30M34S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT29M50S",
    "averageTravelDistanceMetersPerVisit": 65500,
    "totalTravelDistanceMeters": 65500,
    "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 34007,
    "totalUnassignedVisits": 1,
    "totalAssignedVisits": 1,
    "assignedMandatoryVisits": 1,
    "unassignedMandatoryVisits": 1,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "unassignedExcludedVisits": 1
  },
  "run": {
    "id": "ID",
    "originId": "ID",
    "name": "Alternate visits example",
    "submitDateTime": "2026-01-26T04:46:27.612095044Z",
    "startDateTime": "2026-01-26T04:48:08.797659239Z",
    "activeDateTime": "2026-01-26T04:48:08.89053755Z",
    "completeDateTime": "2026-01-26T04:48:39.420614971Z",
    "shutdownDateTime": "2026-01-26T04:48:39.420620311Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-10000medium/-3624soft",
    "tags": [
      "system.type:from-request",
      "system.profile:Standard profile"
    ],
    "validationResult": {
      "summary": "OK"
    }
  }
}

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.

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

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