Shift tag types
Shifts can be assigned to employees based on tag.
Tags provide additional information about employees and shifts. For example, a tag could be a country such as "Belgium", a department "Accounting", an employment type "Contractor", or a task "Reporting".
Tags can be grouped into tag types. A tag type could be "Country", "Department", "Employment Type", or "Task".
When declaring Tag Types and Tags, tagTypes must be declared before tags.
|
{
"modelInput": {
"tagTypes": [
{
"id": "Task"
}
],
"tags": [
{
"id": "Task A",
"tagType": "Tasks"
},
{
"id": "Task B",
"tagType": "Tasks"
},
{
"id": "Task C",
"tagType": "Tasks"
}
]
}
}
Shift tag match rules allow you to specify if matching the tags is required or preferred. In the case of preferred matches you can define multipliers for different tag types when they are matched, these multipliers allow you give more weight to some matches over others.
This is useful when it is preferable to match tag types in order of preference, for instance, match tag type 1 first, if this isn’t possible, match tag type 2 second, and so on.
For employees that are organized into teams and departments, it might be preferable to assign employees to shifts with their team, and if that’s not possible assign them to shifts in their department.
This guide explains assigning employees to shifts with shift tags:
1. Assign employees to shifts with required shift tags
Learn how to configure an API Key to run the examples in this guide:
In the examples, replace |
For an organization that is divided into departments where employees are only assigned shifts to the department they belong to, we define a tag hierarchy:
{
"tagTypes": [
{
"id": "Department"
}
],
"tags": [
{
"id": "Department A",
"tagType": "Department"
},
{
"id": "Department B",
"tagType": "Department"
}
]
}
Shifts must reference the department tag they belong to:
{
"shifts": [
{
"id": "Mon A",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Department A" ]
},
{
"id": "Mon B",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Department B" ]
}
]
}
Employees must specify which department they can work in with the requiredShiftTags attribute:
{
"employees": [
{
"id": "Ann",
"requiredShiftTags": [ "Department A" ]
},
{
"id": "Beth",
"requiredShiftTags": [ "Department B" ]
}
]
}
Shift tag match rules are defined as part of the global rules configuration.
{
"modelInput": {
"globalRules": {
"shiftTagMatchRules": [
{
"id": "matchDepartment",
"satisfiability": "REQUIRED"
}
]
}
}
}
When the satisfiability of the shift tag match rule is Required the Employee works shifts with non-matching required shift tags hard constraint makes sure the employees are assigned to shifts with the all the required shift tags.
If any tags are not matched a hard penalty of 1 is applied to the dataset score per unmatched tag.
If tagTypeMatchMultipliers is defined, the penalty is multiplied by the configured multiplier.
Shifts wil not be assigned if assigning them breaks this constraint.
1.1. Assign employees to shifts with required shift tags example
In the following example, there are 2 shifts (1 in Department A and 1 in Department B), and 2 employees. Ann can work in Department A, and Beth can work in Department B.
Both employees are assigned to shifts in the correct department.
-
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/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Required shift tags example"
}
},
"modelInput": {
"tagTypes": [
{
"id": "Department"
}
],
"tags": [
{
"id": "Department A",
"tagType": "Department"
},
{
"id": "Department B",
"tagType": "Department"
}
],
"globalRules": {
"shiftTagMatchRules": [
{
"id": "matchDepartment",
"satisfiability": "REQUIRED"
}
]
},
"employees": [
{
"id": "Ann",
"requiredShiftTags": [ "Department A" ]
},
{
"id": "Beth",
"requiredShiftTags": [ "Department B" ]
}
],
"shifts": [
{
"id": "Mon A",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Department A" ]
},
{
"id": "Mon B",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Department B" ]
}
]
}
}
| 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/employee-scheduling/v1/schedules/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Required shift tags example",
"submitDateTime": "2026-01-05T07:30:01.960482446Z",
"startDateTime": "2026-01-05T07:30:09.103799638Z",
"activeDateTime": "2026-01-05T07:30:09.19116669Z",
"completeDateTime": "2026-01-05T07:30:39.497218983Z",
"shutdownDateTime": "2026-01-05T07:30:39.497223503Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.type:from-request",
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon A",
"employee": "Ann"
},
{
"id": "Mon B",
"employee": "Beth"
}
],
"employees": [
{
"id": "Ann",
"metrics": {
"assignedShifts": 1,
"durationWorked": "PT8H"
}
},
{
"id": "Beth",
"metrics": {
"assignedShifts": 1,
"durationWorked": "PT8H"
}
}
]
},
"inputMetrics": {
"employees": 2,
"shifts": 2,
"pinnedShifts": 0,
"mandatoryShifts": 2,
"optionalShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 0,
"disruptionPercentage": 0,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
},
"run": {
"id": "ID",
"originId": "ID",
"name": "Required shift tags example",
"submitDateTime": "2026-01-05T07:30:01.960482446Z",
"startDateTime": "2026-01-05T07:30:09.103799638Z",
"activeDateTime": "2026-01-05T07:30:09.19116669Z",
"completeDateTime": "2026-01-05T07:30:39.497218983Z",
"shutdownDateTime": "2026-01-05T07:30:39.497223503Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.type:from-request",
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
}
}
1.2. Assign employees to shifts with required shift tag expressions
Shift tag expressions allow employees to define complex tag requirements using AND/OR boolean logic.
For example, a hospital employee may be required to work shifts tagged with a specific department AND one of several wards.
Use requiredShiftTagsExpression to define required tags on employee objects.
In the following example, Ann is only assigned to shifts tagged with "Emergency" AND either "Wing East" or "Wing West". If no shift satisfies the expression, Ann remains unassigned.
{
"employees": [
{
"id": "Ann",
"requiredShiftTagsExpression": {
"type": "NODE",
"operator": "AND",
"operands": [
{
"type": "LEAF",
"tagId": "Emergency"
},
{
"type": "NODE",
"operator": "OR",
"operands": [
{
"type": "LEAF",
"tagId": "Wing East"
},
{
"type": "LEAF",
"tagId": "Wing West"
}
]
}
]
}
}
]
}
When using tag type multipliers with required expressions, the operator affects how the penalty is calculated:
-
AND: Each operand is evaluated independently. The penalty is the sum of the multipliers of all non-matching tags.
-
OR: At least one operand must match. If none match, the penalty is the lowest multiplier among the non-matching tags.
For example, consider an expression (Emergency OR Intensive Care) AND (Wing East OR Wing West) where the "Department" tag type has a multiplier of 1 and the "Location" tag type has a multiplier of 1000.
If a shift is tagged only with "Intensive Care", the "Department" OR group is satisfied (no penalty), but neither "Wing East" nor "Wing West" is present, so the "Location" OR group contributes the lowest multiplier (1000).
The AND operator sums the results: 0 + 1000 = 1000.
The expression expects a node with three fields:
-
typeto specify it is aNODE. -
operatorto specify how theoperandswill be evaluated, asANDorOR. -
operandsto specify a list of operands. An operand’s type can either beNODEorLEAF.
NODE creates nested AND/OR structures.
LEAF has two fields:
-
typeto specify it isLEAF. -
tagIdto specify a tag’s ID.
| The maximum depth of nodes is two, i.e. the expression is allowed to specify a node as an operand, but that node isn’t allowed to specify other nodes as operands. |
| An employee can either define a required shift tags expression or required shift tags. They cannot be used at the same time for an employee, but they can be used across multiple employees. |
2. Assign employees to shifts with preferred shift tags
When shifts are assigned to employees with cascading priorities, the satisfiability of the shift tag match rules can be set to Preferred.
This makes it possible to assign employees to shifts in the following way:
-
If possible assign the employee to a shift with a specific team.
-
If the team assignment is not possible, assign the employee to a specific department.
-
If the team and department assignment are not possible, assign the employee to a shift in a specific location.
The following tag hierarchy would be used for these cascading priorities:
{
"tagTypes": [
{
"id": "Department"
},
{
"id": "Team"
},
{
"id": "Location"
}
],
"tags": [
{
"id": "Team X",
"tagType": "Team"
},
{
"id": "Team Y",
"tagType": "Team"
},
{
"id": "Department A",
"tagType": "Department"
},
{
"id": "Department B",
"tagType": "Department"
},
{
"id": "Location 1",
"tagType": "Location"
},
{
"id": "Location 2",
"tagType": "Location"
}
]
}
When the shift tag match rule’s satisfiability is PREFERRED, employees define preferredShiftTags:
{
"employees": [
{
"id": "Ann",
"preferredShiftTags": [ "Department A", "Team X", "Location 1" ]
},
{
"id": "Beth",
"preferredShiftTags": [ "Department A", "Team X", "Location 1" ]
},
{
"id": "Carl",
"preferredShiftTags": [ "Department A", "Team X", "Location 1" ]
}
]
}
Shifts include the shifts tag:
{
"shifts": [
{
"id": "Mon A",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Team X" ]
},
{
"id": "Mon B",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Department A" ]
},
{
"id": "Mon C",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Location 1" ]
}
]
}
The shift tag match rule includes a tagTypeMatchMultipliers for each tag type:
{
"shiftTagMatchRules": [
{
"id": "MatchOrganization",
"satisfiability": "PREFERRED",
"tagTypeMatchMultipliers": {
"Team": 100000,
"Department": 10000,
"Location": 1000
}
}
]
}
The Employee works shift with preferred shift tags soft constraint is invoked when the shift tag match rule’s satisfiability is PREFERRED, an employee with preferredShiftTags is assigned to a shift, and the shift contains one or more of those preferred tags.
The constraint adds a soft reward to the dataset score that is calculated by summing the tagTypeMatchMultipliers for each matched preferred tag, using a default multiplier of 1 for tag types without an explicit multiplier.
The result is then multiplied by the employee priority multiplier.
This incentivizes Timefold to assign employees to shifts that match the highest-priority preferred tag types.
If tagTypeMatchMultipliers is defined, the reward is multiplied by the configured tag type match multiplier.
Shifts will still be assigned even if assigning them breaks this constraint.
Tags of a tag type with a higher tagTypeMatchMultipliers will be matched first, as Timefold is incentivized to use solutions with the best score.
2.1. Assign employees to shifts with preferred shift tags example
In the following example, there are 3 employees with the same preferred shift tags. There are 3 shifts, each shift has a different shift tag type. All three shifts are assigned and a soft reward of is added to the dataset score for each shift tag that is matched:
-
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/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Preferred shift tags example"
}
},
"modelInput": {
"tagTypes": [
{
"id": "Department"
},
{
"id": "Team"
},
{
"id": "Location"
}
],
"tags": [
{
"id": "Team X",
"tagType": "Team"
},
{
"id": "Team Y",
"tagType": "Team"
},
{
"id": "Department A",
"tagType": "Department"
},
{
"id": "Department B",
"tagType": "Department"
},
{
"id": "Location 1",
"tagType": "Location"
},
{
"id": "Location 2",
"tagType": "Location"
}
],
"globalRules": {
"shiftTagMatchRules": [
{
"id": "MatchOrganization",
"satisfiability": "PREFERRED",
"tagTypeMatchMultipliers": {
"Team": 100000,
"Department": 10000,
"Location": 1000
}
}
]
},
"employees": [
{
"id": "Ann",
"preferredShiftTags": [ "Department A", "Team X", "Location 1" ]
},
{
"id": "Beth",
"preferredShiftTags": [ "Department A", "Team X", "Location 1" ]
},
{
"id": "Carl",
"preferredShiftTags": [ "Department A", "Team X", "Location 1" ]
}
],
"shifts": [
{
"id": "Mon A",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Team X" ]
},
{
"id": "Mon B",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Department A" ]
},
{
"id": "Mon C",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [ "Location 1" ]
}
]
}
}
| 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/employee-scheduling/v1/schedules/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Preferred shift tags example",
"submitDateTime": "2026-01-07T08:17:39.699372725Z",
"startDateTime": "2026-01-07T08:17:46.753541571Z",
"activeDateTime": "2026-01-07T08:17:46.86171546Z",
"completeDateTime": "2026-01-07T08:18:17.290267207Z",
"shutdownDateTime": "2026-01-07T08:18:17.290277227Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/222000soft",
"tags": [
"system.type:from-request",
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon A",
"employee": "Ann"
},
{
"id": "Mon B",
"employee": "Beth"
},
{
"id": "Mon C",
"employee": "Carl"
}
],
"employees": [
{
"id": "Ann",
"metrics": {
"assignedShifts": 1,
"durationWorked": "PT8H",
"durationOfTimePreferencesMet": "PT8H"
}
},
{
"id": "Beth",
"metrics": {
"assignedShifts": 1,
"durationWorked": "PT8H",
"durationOfTimePreferencesMet": "PT8H"
}
},
{
"id": "Carl",
"metrics": {
"assignedShifts": 1,
"durationWorked": "PT8H",
"durationOfTimePreferencesMet": "PT8H"
}
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0,
"mandatoryShifts": 3,
"optionalShifts": 0
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 0,
"disruptionPercentage": 0,
"averageDurationOfEmployeesPreferencesMet": "PT8H",
"minimumDurationOfPreferencesMetAcrossEmployees": "PT8H",
"activatedEmployees": 3,
"assignedMandatoryShifts": 3
},
"run": {
"id": "ID",
"originId": "ID",
"name": "Preferred shift tags example",
"submitDateTime": "2026-01-07T08:17:39.699372725Z",
"startDateTime": "2026-01-07T08:17:46.753541571Z",
"activeDateTime": "2026-01-07T08:17:46.86171546Z",
"completeDateTime": "2026-01-07T08:18:17.290267207Z",
"shutdownDateTime": "2026-01-07T08:18:17.290277227Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/222000soft",
"tags": [
"system.type:from-request",
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
}
}
2.2. Assign employees to shifts with preferred shift tag expressions
Shift tag expressions allow employees to define complex tag preferences using AND/OR boolean logic.
For example, an employee may prefer to work shifts tagged with a specific department AND one of several floors.
Use preferredShiftTagsExpression to define preferred tags on employee object.
In the following example, Bob prefers shifts tagged with "Oncology" AND either "Floor 1" or "Floor 2". The solver will try to assign Bob to shifts matching these tags, but it is not a hard requirement.
{
"employees": [
{
"id": "Bob",
"preferredShiftTagsExpression": {
"type": "NODE",
"operator": "AND",
"operands": [
{
"type": "LEAF",
"tagId": "Oncology"
},
{
"type": "NODE",
"operator": "OR",
"operands": [
{
"type": "LEAF",
"tagId": "Floor 1"
},
{
"type": "LEAF",
"tagId": "Floor 2"
}
]
}
]
}
}
]
}
When using tag type multipliers with preferred expressions, the operator affects how the reward is calculated:
-
AND: Each operand is evaluated independently. The reward is the sum of the multipliers of all matching tags.
-
OR: The reward is the highest multiplier among the matching tags.
For example, consider an expression (Oncology OR Cardiology) AND (Floor 1 OR Floor 2) where the "Department" tag type has a multiplier of 1 and the "Location" tag type has a multiplier of 1000.
If a shift is tagged with "Oncology" and "Floor 1", the "Department" OR group picks the highest match (1 for "Oncology") and the "Location" OR group picks the highest match (1000 for "Floor 1").
The AND operator sums the results: 1 + 1000 = 1001.
The expression expects a node with three fields:
-
typeto specify it is aNODE. -
operatorto specify how theoperandswill be evaluated, asANDorOR. -
operandsto specify a list of operands. An operand’s type can either beNODEorLEAF.
NODE creates nested AND/OR structures.
LEAF has two fields:
-
typeto specify it isLEAF. -
tagIdto specify a tag’s ID.
| The maximum depth of nodes is two, i.e. the expression is allowed to specify a node as an operand, but that node isn’t allowed to specify other nodes as operands. |
| An employee can either define a preferred shift tags expression or preferred shift tags. They cannot be used at the same time for an employee, but they can be used across multiple employees. |
Next
-
See the full API spec or try the online API.
-
Learn more about employee shift scheduling from our YouTube playlist.
-
See other options related to Shift type diversity.