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, |
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.