Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Field Service Routing
  • Tags

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 costs
      • Technician ratings
    • Visit service constraints
      • Time windows and opening hours
      • Skills
      • Visit dependencies
      • Visit requirements, area affinity, and tags
      • 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

Tags

Tags can be used to model additional requirements that are not covered by the existing features of the model.

For instance, technicians' skills can be rated at different levels of proficiency, but sometimes, there are yes or no requirements that also need to be modeled that apply to the technician and their vehicle. Tags can be used in these situations. For instance:

  • Has Ann signed an NDA with a specific company?

  • Is Beth approved to work on site at schools?

  • Is Carl certified to handle hazardous material on site?

1. Defining tags

Tags are defined in vehicle shifts:

{
  "vehicles": [
    {
      "id": "Beth",
      "shifts": [
        {
          "id": "Beth-2027-02-01",
          "startLocation": [33.68786, -84.18487],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z",
          "tags": ["NDA"]
        }
      ]
    }
  ]
}

You can define multiple tags:

{
  "vehicles": [
    {
      "id": "Beth",
      "shifts": [
        {
          "id": "Beth-2027-02-01",
          "startLocation": [33.68786, -84.18487],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z",
          "tags": ["NDA", "Task A"]
        }
      ]
    }
  ]
}

Tags must also be defined for the visits that require them:

{
  "visits": [
    {
      "id": "Visit A",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT1H30M",
      "requiredTags": ["NDA"]
    }
  ]
}

requiredTags specifies the tags that a technician’s shift must contain for the visit to be assigned to the technician’s shift.

In addition to adding the technicians' tags to their shifts and the requiredTags to the visits, tags must also be declared as part of the modelInput.

{
  "tags": [ "NDA", "Task A" ]
}

Failure to declare tags will result in a validation error.

Declaring tags ensures only declared tags are considered in the solution and prevents similar, but different tags from being included, for instance, Emergency and mErgency.

The require tags hard constraint makes sure visits are assigned to vehicle shifts that match their required tags. This constraint is invoked for any visit assigned to a vehicle shift with at least 1 missing tag that is required by the visit.

Visits will not be assigned if assigning them would break this constraint.

2. Tag example

In the following example, visit A has requiredTags NDA. Beth and Carl are both available, however, only Beth has the NDA tag.

The visit is assigned to Beth.

  • 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": "Matching tags with visits example"
    }
  },
  "modelInput": {
    "vehicles": [
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z",
            "tags": ["NDA"]
          }
        ]
      },
      {
        "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",
        "requiredTags": ["NDA"]
      }
    ],
    "tags": [
      {
        "name": "NDA"
      }
    ]
  }
}
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": "Matching tags with visits example",
    "submitDateTime": "2025-11-26T07:29:57.031225811Z",
    "startDateTime": "2025-11-26T07:30:04.206154587Z",
    "activeDateTime": "2025-11-26T07:30:04.294104034Z",
    "completeDateTime": "2025-11-26T07:30:19.844405955Z",
    "shutdownDateTime": "2025-11-26T07:30:19.844411595Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-3789soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Beth",
        "shifts": [
          {
            "id": "Beth-2027-02-01",
            "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": 31493,
                "minStartTravelTime": "2027-02-01T00:00:00Z"
              }
            ],
            "metrics": {
              "totalTravelTime": "PT59M29S",
              "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT29M32S",
              "totalTravelDistanceMeters": 65477,
              "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 33984,
              "endLocationArrivalTime": "2027-02-01T11:29:29Z"
            }
          }
        ]
      },
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-02-01",
            "startTime": "2027-02-01T09: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": "PT59M29S",
    "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT29M32S",
    "totalTravelDistanceMeters": 65477,
    "travelDistanceFromStartLocationToFirstVisitMeters": 31493,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 33984,
    "totalUnassignedVisits": 0,
    "totalAssignedVisits": 1,
    "assignedMandatoryVisits": 1,
    "assignedOptionalVisits": 0,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 0
  },
  "run": {
    "id": "ID",
    "originId": "ID",
    "name": "Matching tags with visits example",
    "submitDateTime": "2025-11-26T07:29:57.031225811Z",
    "startDateTime": "2025-11-26T07:30:04.206154587Z",
    "activeDateTime": "2025-11-26T07:30:04.294104034Z",
    "completeDateTime": "2025-11-26T07:30:19.844405955Z",
    "shutdownDateTime": "2025-11-26T07:30:19.844411595Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-3789soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  }
}

3. Temporary tags

Tags can be temporary to make sure they only apply for a specific period of time. For instance, if certifications must be renewed periodically, the tag can be defined as a temporary tag and include start and end dates and times to indicate when the certification is valid.

Temporary tags are matched against visits' requiredTags for the period of time they are valid.

Temporary tags are defined in temporaryTagSets in shifts:

{
  "vehicles": [
    {
      "id": "Beth",
      "shifts": [
        {
          "id": "Beth-2027-02-01",
          "startLocation": [33.68786, -84.18487],
          "minStartTime": "2027-02-01T09:00:00Z",
          "maxEndTime": "2027-02-01T17:00:00Z",
          "temporaryTagSets": [
            {
              "start": "2027-02-01T00:00:00Z",
              "end": "2028-02-01T00:00:00Z",
              "tags": ["First-aid"]
            }
          ]
        }
      ]
    }
  ]
}

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.

  • Send technicians with the right skills to visits.

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