Employee contracts: Shift rules
Employees have an expectation that the terms of their contracts are honored in the shifts they are scheduled to work.
Employee contracts stipulate the conditions under which the employees work, including:
-
How many consecutive days employees work.
-
How much time should be between shifts.
-
What kind of shifts employees should not be assigned before or after a day off request.
-
When employees can be assigned overlapping shifts, for instance, when they are on call.
For any employee shift scheduling solution to be feasible, it must take into account the contractual obligations between the employer and the employee.
This guide explains how to manage employee contract shift rules with the following examples:
Prerequisites
To run the examples in this guide, you need to authenticate with a valid API key for the Employee Shift Scheduling model:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
Create a new API key or use an existing one. Ensure the list of models for the API key contains the Employee Shift Scheduling model.
In the examples, replace <API_KEY>
with the API Key you just copied.
1. Consecutive days worked rules
Beth is a full-time employee who works a maximum of 3 consecutive twelve-hour shifts.
Consecutive day worked rules are defined in contracts
.
{
"contracts": [
{
"id": "fullTimeContract",
"consecutiveDaysWorkedRules": [
{
"id": "Max3Consecutive12HourShifts",
"maximum": 3,
"satisfiability": "REQUIRED"
}
]
}
]
}
A consecutiveDaysWorkedRules
must include an id
.
maximum
defines the maximum number of shifts on consecutive days an employee with this contract can work.
satisfiability
can be REQUIRED
or PREFERRED
.
If omitted, REQUIRED
is the default.
1.1. Required satisfiability
When the satisfiability of the rule is REQUIRED
, the Consecutive days worked not in required range for employee
hard constraint is invoked, which makes sure the number of shifts on consecutive days does not exceed the limit specified in maximum
.
Shifts will be left unassigned if assigning them would break the Consecutive days worked not in required range for employee
constraint.

1.2. Preferred satisfiability
When the satisfiability of the rule is PREFERRED
, the Consecutive days worked not in preferred range for employee
soft constraint is invoked.
Beth might be assigned shifts on more days than the value specified in maximum
, but this constraint adds a soft penalty to the run score for any matches to the constraint, incentivising Timefold to find an alternative solution.
With a satisfiability of PREFERRED
a minimum
value can also be provided to set the minimum number of consecutive days an employee can be assigned shifts.
{
"contracts": [
{
"id": "fullTimeContract",
"consecutiveDaysWorkedRules": [
{
"id": "Max3Consecutive12HourShifts",
"maximum": 3,
"minimum": 2,
"satisfiability": "PREFERRED"
}
]
}
]
}
minimum can only be used with a satisfiability of PREFERRED .
|
1.3. Consecutive days worked rules example
In the following example, Beth has a contract that allows her to work shifts on a maximum of 3 consecutive days.
-
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": "Consecutive days worked rule example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"consecutiveDaysWorkedRules": [
{
"id": "Max3Consecutive12HourShifts",
"maximum": 3,
"satisfiability": "REQUIRED"
}
]
}
],
"employees": [
{
"id": "Beth",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon",
"start": "2027-02-01T08:00:00Z",
"end": "2027-02-01T20:00:00Z"
},
{
"id": "Tue",
"start": "2027-02-02T08:00:00Z",
"end": "2027-02-02T20:00:00Z"
},
{
"id": "Wed",
"start": "2027-02-03T08:00:00Z",
"end": "2027-02-03T20:00:00Z"
},
{
"id": "Thu",
"start": "2027-02-04T08:00:00Z",
"end": "2027-02-04T20:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Consecutive days worked rule example",
"submitDateTime": "2025-03-18T06:39:19.37242389Z",
"startDateTime": "2025-03-18T06:39:30.34936678Z",
"activeDateTime": "2025-03-18T06:39:30.58964911Z",
"completeDateTime": "2025-03-18T06:44:31.318789334Z",
"shutdownDateTime": "2025-03-18T06:44:31.541517086Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon",
"employee": "Beth"
},
{
"id": "Tue",
"employee": "Beth"
},
{
"id": "Wed",
"employee": "Beth"
},
{
"id": "Thu",
"employee": null
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 4,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 3,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the employee schedule with Beth assigned three consecutive shifts.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 3,
"unassignedShifts": 1
}
Consecutive days worked rules apply to the number of consecutive days worked, not the number of days worked over a specific period. To limit the number of days over a specific period (for instance, five days in a week), include a period rule. See combining contractual rules for an example of including multiple contract rules. |
2. Control when consecutive days worked rules apply
Consecutive days worked rules can be limited in the following ways:
-
Shifts with specific tags can be included or excluded from the rules.
-
Shift type tag categories can be used to apply the rules to sequences of shifts with the same tag.
2.1. Include or exclude shifts with shift tags
Shifts with specific tags can be included or excluded by the rule. Tags are defined in shifts:
{
"shifts": [
{
"id": "2027-02-01",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["ICU"]
}
]
}
Use includeShiftTags
to include shifts with specific tags or excludeShiftTags
to exclude shifts with specific tags.
shiftTagMatches
can be set to ALL
or ANY
.
The default behavior for shiftTagMatches
is ALL
, and if omitted, the default ALL
will be used.
2.1.1. Include shift tags
{
"includeShiftTags": ["ICU", "Cardiology"],
"shiftTagMatches": "ALL"
}
With shiftTagMatches
set to ALL
, all tags defined by the rule’s includeShiftTags
attribute must be present in the shift. With shiftTagMatches
set to ANY
, at least one tag defined by the rule’s includeShiftTags
attribute must be present in the shift.
2.1.2. Exclude shift tags
{
"excludeShiftTags": ["Part-time", "Weekend"],
"shiftTagMatches": "ALL"
}
With shiftTagMatches
set to ALL
, all tags defined by the rule’s excludeShiftTags
attribute cannot be present in the shift.
This is useful when you want to exclude things in combination with each other.
For instance, excluding the shift tags Part-time
and Weekend
with shiftTagMatches
set to All
, would exclude shifts that include the tags Part-time
and Weekend
from the rule.
Shifts tagged only Part-time
or only Weekend
will not be excluded.
With shiftTagMatches
set to ANY
, any of the tags defined by the rule’s excludeShiftTags
attribute cannot be present in the shift.
This is useful when you need to exclude tags regardless of their relationship to other tags.
For instance, excluding the shift tags Part-time
and Weekend
with shiftTagMatches
set to ANY
, would exclude any shift that includes the tags Part-time
or Weekend
, whether they occur together or not.
The rule can define either includeShiftTags
or excludeShiftTags
, but not both.
2.2. Limit consecutive shifts in a department
In the following example, Ann works a 5-day week, but is only allowed to work 2 consecutive days in a specific department, you can define a consecutive days worked rule of 2 days for that department.
Add includeShiftTags
to the rule and reference the tag that identifies the department’s shifts.
Ann is assigned shifts on Monday and Tuesday in department A, Wednesday in department B, and Thursday and Friday in department A again.
-
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": "Consecutive days worked rule example with tags"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"consecutiveDaysWorkedRules": [
{
"id": "Max2ConsecutiveDaysDepartmentA",
"maximum": 2,
"includeShiftTags":
[
"department A"
],
"shiftTagMatches": "ALL"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon department A",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Tue department A",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Wed department A",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Wed department B",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z",
"tags": [
"department B"
]
},
{
"id": "Thu department A",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Fri department A",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z",
"tags": [
"department 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/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Consecutive days worked rule example with tags",
"submitDateTime": "2024-10-21T09:49:11.055051606Z",
"startDateTime": "2024-10-21T09:49:15.855077369Z",
"activeDateTime": "2024-10-21T09:49:15.955077369Z",
"completeDateTime": "2024-10-21T09:54:16.060270963Z",
"shutdownDateTime": "2024-10-21T09:54:16.160270963Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon department A",
"employee": "Ann"
},
{
"id": "Tue department A",
"employee": "Ann"
},
{
"id": "Wed department A",
"employee": null
},
{
"id": "Wed department B",
"employee": "Ann"
},
{
"id": "Thu department A",
"employee": "Ann"
},
{
"id": "Fri department A",
"employee": "Ann"
}
]
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule with Ann assigned to Monday and Tuesday in department A, Wednesday in department B, and Thursday and Friday in department A again.
2.3. Limit consecutive shift types
shiftTypeTagCategories
can be used to limit consecutive days worked on shifts with specific tags, for instance, shifts tagged with day
or night
.
{
"consecutiveDaysWorkedRules": [
{
"id": "Max3Consecutive12HourShifts",
"maximum": 3,
"satisfiability": "REQUIRED",
"shiftTypeTagCategories": [
"day", "night"
]
}
]
}
With the maximum
set to 3
and shiftTypeTagCategories
including the tags day
and night
, employees will not be assigned more than 3 consecutive day shifts or 3 consecutive night shifts.
However, they could be assigned 3 consecutive day
shifts followed by 3 consecutive night
shifts, or vice versa.
Shifts can have the tag day
or night
but not both.
3. Minutes between shifts rules
Employees need time between shifts for their lives outside work.
Contractually, employees could be entitled to a minimum time between shifts, for instance, if an employee works a night shift, it might be included in their contract that they don’t work for the following 12 hours.
You can specify the minimum and maximum time between shifts with minutesBetweenShiftRules
in contracts
.
Additionally, you can provide a scope for the rule. The scope will limit which pairs of shifts the rule is applied to. The scope excludes shift pairs where the start of the later shift is after the end of the prior shift plus the scope duration.
{
"contracts": [
{
"id": "fullTimeContract",
"minutesBetweenShiftsRules": [
{
"id": "Minimum12HoursBetweenShiftsFullTime",
"minimumMinutesBetweenShifts": 720,
"maximumMinutesBetweenShifts": 1440,
"scope": {
"type": "duration",
"duration": "P1D"
},
"satisfiability": "REQUIRED"
}
]
}
]
}
minutesBetweenShiftsRules
must include an id
.
In this instance, the minimumMinutesBetweenShifts
is 720
minutes, which ensures employees have 12 hours between assigned shifts.
The maximumMinutesBetweenShifts
is 1440
minutes, which means the next shift should be assigned no more than 24 hours later.
satisfiability
can be REQUIRED
or PREFERRED
.
If omitted, REQUIRED
is the default.
3.1. Required satisfiability
When the satisfiability of the rule is REQUIRED
, the Minutes between shifts not in required range for employee
hard constraint is invoked, making sure the number of minutes between shifts assigned to an employee is not below the minimumMinutesBetweenShifts
or above the maximumMinutesBetweenShifts
.
This constraint will leave shifts unassigned if assigning the shift broke the constraint.
3.2. Required satisfiability and minimumConsecutivePriorShifts
The rule can optionally define a minimumConsecutivePriorShifts
attribute to work with a daily sequence of prior shifts.
minimumConsecutivePriorShifts
can be either 1
or 2
.
If the value is greater than 1, requiredPriorShiftTags
need to be specified to identify the prior sequence, for example a sequence of night shifts.
When the satisfiability of the rule is REQUIRED
, minimumConsecutivePriorShifts
is 2
, and the employee is assigned to a consecutive daily sequence of two shifts, the Minutes between shifts not in required range for employee
hard constraint is invoked, making sure the number of minutes between the end of the shift sequence and the next shift assigned to an employee is not below the minimumMinutesBetweenShifts
or above the maximumMinutesBetweenShifts
.
{
"contracts": [
{
"id": "fullTimeContract",
"minutesBetweenShiftsRules": [
{
"id": "Minimum12HoursBetweenShiftsFullTime",
"minimumMinutesBetweenShifts": 720,
"satisfiability": "REQUIRED",
"minimumConsecutivePriorShifts": 2,
"requiredPriorShiftTags": ["night"]
}
]
}
]
}
3.3. Preferred satisfiability
When the satisfiability of the rule is PREFERRED
, the Minutes between shifts not in preferred range for employee
soft constraint is invoked.
Employees might be assigned shifts that are below the minimumMinutesBetweenShifts
or above the maximumMinutesBetweenShifts
, but the constraint adds a soft penalty to the run score for any matches to the constraint, incentivising Timefold to find an alternative solution.
3.4. Minutes between shift rules example
If Carl works the night shift between 01:00 and 09:00, he will not be eligible for another shift until 12 hours later at or after 21:00.
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Minutes between shifts rules example",
"tags": []
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"minutesBetweenShiftsRules": [
{
"id": "Minimum12HoursBetweenShiftsFullTime",
"minimumMinutesBetweenShifts": 720,
"maximumMinutesBetweenShifts": 1440,
"scope": {
"type": "duration",
"duration": "P1D"
}
}
]
}
],
"employees": [
{
"id": "Carl",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T01:00:00Z",
"end": "2027-02-01T09:00:00Z"
},
{
"id": "Mon day",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Tue night",
"start": "2027-02-02T01:00:00Z",
"end": "2027-02-02T09:00:00Z"
},
{
"id": "Tue day",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "Wed night",
"start": "2027-02-03T01:00:00Z",
"end": "2027-02-03T09:00:00Z"
},
{
"id": "Wed day",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Minutes between shifts rules example",
"submitDateTime": "2025-03-19T09:18:47.323655439Z",
"startDateTime": "2025-03-19T09:19:01.715923815Z",
"activeDateTime": "2025-03-19T09:19:01.913383827Z",
"completeDateTime": "2025-03-19T09:24:02.756166615Z",
"shutdownDateTime": "2025-03-19T09:24:03.171847565Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-3medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon night",
"employee": "Carl"
},
{
"id": "Mon day",
"employee": null
},
{
"id": "Tue night",
"employee": "Carl"
},
{
"id": "Tue day",
"employee": null
},
{
"id": "Wed night",
"employee": "Carl"
},
{
"id": "Wed day",
"employee": null
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 6,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 3,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 3,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the employee schedule with Carl assigned shifts with more than 12 hours between each shift.
4. Avoid shift close to day off request rules
When Employees have days off, there are shifts it would be better not to assign them before or after the day off.
Days off are defined by employee availability. See Employee availability for details. |
Assigning an afternoon shift to an employee directly before a day off might make it difficult for the employee to use their day off as they intended. Similarly, employees might prefer not to have an early shift after a day off.
{
"contracts": [
{
"id": "fullTimeContract",
"avoidShiftCloseToDayOffRequestRules": [
{
"id": "noNightAndMorningShiftsNearDayOff",
"avoidPriorShiftTags": [ "afternoon" ],
"avoidAfterShiftTags": [ "morning" ],
"shiftTagMatches": "ANY",
"satisfiability": "PROHIBITED"
}
]
}
]
}
avoidShiftCloseToDayOffRequestRules
is defined in contracts
and must include an id
.
-
avoidPriorShiftTags
must include the tags for the shifts to exclude prior to a day off request, for instance,afternoon
. If no tags are provided, the rule has no effect on shifts the day prior to a day off request. -
avoidAfterShiftTags
must include the tags for the shifts to exclude after a day off request, for instance,morning
. If no tags are provided, the rule has no effect on shifts the day after a day off request.
shiftTagMatches
can be set to ALL
or ANY
.
shiftTagMatches
is optional and set to ALL
by default if omitted.
With shiftTagMatches
set to ALL
, all tags defined by the rule’s avoidPriorShiftTags
and avoidAfterShiftTags
attributes must be present in the shift.
With shiftTagMatches
set to ANY
, at least one tag defined by the rule’s avoidPriorShiftTags
and avoidAfterShiftTags
attributes must be present in the shift.
satisfiability
can be PROHIBITED
or UNPREFERRED
. If omitted PROHIBITED
is used by default.
4.1. Prohibited satisfiability
When the satisfiability of the rule is PROHIBITED
, the Employee has prohibited shift near day off request
hard constraint is invoked, which makes sure shifts with tags referenced in avoidPriorShiftTags
and avoidAfterShiftTags
are not assigned before or after a day off request respectively.
4.2. Unpreferred satisfiability
When the satisfiability of the rule is UNPREFERRED
, the Employee has unpreferred shift near day off request
soft constraint is invoked.
Shifts with tags referenced in avoidPriorShiftTags
and avoidAfterShiftTags
might be assigned before or after a day off request respectively, but this constraint adds a soft penalty to the run score for any matches to the constraint, incentivising Timefold to find an alternative solution.
4.3. Avoid shifts close to days off request rules example
In the following example, Ann can work morning, afternoon, or night shifts.
She has Wednesday off as defined by the employee unavailableTimeSpans
.
The unavailable timespan starts at 00:00 on Wednesday and ends at 00:00 on Thursday, and she cannot be assigned to any shifts that overlap this time period.
The avoidShiftCloseToDayOffRequestRules
prohibits Ann from working an afternoon shift the day before her day off and from working a morning shift the day after her day off.
-
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": "Avoid shifts close to days off example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"consecutiveDaysWorkedRules": [
{
"id": "Max5ConsecutiveDaysFullTime",
"maximum": 5
}
],
"periodRules": [
{
"id": "Max8HoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 480
},
{
"id": "Max40HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 2400
}
],
"minutesBetweenShiftsRules": [
{
"id": "Minimum12HoursBetweenShiftsFullTime",
"minimumMinutesBetweenShifts": 720
}
],
"avoidShiftCloseToDayOffRequestRules": [
{
"id": "noMorningShiftsNearDayOff",
"avoidPriorShiftTags": [
"afternoon"
],
"avoidAfterShiftTags": [
"morning"
],
"shiftTagMatches": "ANY",
"satisfiability": "PROHIBITED"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
],
"unavailableTimeSpans": [
{
"start": "2027-02-03T00:00:00-04:00",
"end": "2027-02-04T00:00:00-04:00"
}
]
}
],
"shifts": [
{
"id": "Mon morning",
"start": "2027-02-01T06:00:00-04:00",
"end": "2027-02-01T14:00:00-04:00",
"tags": [
"morning"
]
},
{
"id": "Mon afternoon",
"start": "2027-02-01T14:00:00-04:00",
"end": "2027-02-01T22:00:00-04:00",
"tags": [
"afternoon"
]
},
{
"id": "Mon night",
"start": "2027-02-01T22:00:00-04:00",
"end": "2027-02-02T06:00:00-04:00",
"tags": [
"night"
]
},
{
"id": "Tue morning",
"start": "2027-02-02T06:00:00-04:00",
"end": "2027-02-02T14:00:00-04:00",
"tags": [
"morning"
]
},
{
"id": "Tue afternoon",
"start": "2027-02-02T14:00:00-04:00",
"end": "2027-02-02T22:00:00-04:00",
"tags": [
"afternoon"
]
},
{
"id": "Tue night",
"start": "2027-02-02T22:00:00-04:00",
"end": "2027-02-03T06:00:00-04:00",
"tags": [
"night"
]
},
{
"id": "Wed morning",
"start": "2027-02-03T06:00:00-04:00",
"end": "2027-02-03T14:00:00-04:00",
"tags": [
"morning"
]
},
{
"id": "Wed afternoon",
"start": "2027-02-03T14:00:00-04:00",
"end": "2027-02-03T22:00:00-04:00",
"tags": [
"afternoon"
]
},
{
"id": "Wed night",
"start": "2027-02-03T22:00:00-04:00",
"end": "2027-02-04T06:00:00-04:00",
"tags": [
"night"
]
},
{
"id": "Thu morning",
"start": "2027-02-04T06:00:00-04:00",
"end": "2027-02-04T14:00:00-04:00",
"tags": [
"morning"
]
},
{
"id": "Thu afternoon",
"start": "2027-02-04T14:00:00-04:00",
"end": "2027-02-04T22:00:00-04:00",
"tags": [
"afternoon"
]
},
{
"id": "Thu night",
"start": "2027-02-04T22:00:00-04:00",
"end": "2027-02-05T06:00:00-04:00",
"tags": [
"night"
]
},
{
"id": "Fri morning",
"start": "2027-02-05T06:00:00-04:00",
"end": "2027-02-05T14:00:00-04:00",
"tags": [
"morning"
]
},
{
"id": "Fri afternoon",
"start": "2027-02-05T14:00:00-04:00",
"end": "2027-02-05T22:00:00-04:00",
"tags": [
"afternoon"
]
},
{
"id": "Fri night",
"start": "2027-02-05T22:00:00-04:00",
"end": "2027-02-06T06:00:00-04:00",
"tags": [
"night"
]
},
{
"id": "Sat morning",
"start": "2027-02-06T06:00:00-04:00",
"end": "2027-02-06T14:00:00-04:00",
"tags": [
"morning"
]
},
{
"id": "Sat afternoon",
"start": "2027-02-06T14:00:00-04:00",
"end": "2027-02-06T22:00:00-04:00",
"tags": [
"afternoon"
]
},
{
"id": "Sat night",
"start": "2027-02-06T22:00:00-04:00",
"end": "2027-02-07T06:00:00-04:00",
"tags": [
"night"
]
}
]
}
}
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>
{
"run": {
"id": "ID",
"name": "Avoid shifts close to days off example",
"submitDateTime": "2024-10-22T00:53:42.978851235Z",
"startDateTime": "2024-10-22T00:53:47.475829006Z",
"activeDateTime": "2024-10-22T00:53:47.575829006Z",
"completeDateTime": "2024-10-22T00:58:47.65514026Z",
"shutdownDateTime": "2024-10-22T00:58:47.75514026Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-13medium/0soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon morning",
"employee": "Ann"
},
{
"id": "Mon afternoon",
"employee": null
},
{
"id": "Mon night",
"employee": null
},
{
"id": "Tue morning",
"employee": "Ann"
},
{
"id": "Tue afternoon",
"employee": null
},
{
"id": "Tue night",
"employee": null
},
{
"id": "Wed morning",
"employee": null
},
{
"id": "Wed afternoon",
"employee": null
},
{
"id": "Wed night",
"employee": null
},
{
"id": "Thu morning",
"employee": null
},
{
"id": "Thu afternoon",
"employee": "Ann"
},
{
"id": "Thu night",
"employee": null
},
{
"id": "Fri morning",
"employee": null
},
{
"id": "Fri afternoon",
"employee": "Ann"
},
{
"id": "Fri night",
"employee": null
},
{
"id": "Sat morning",
"employee": null
},
{
"id": "Sat afternoon",
"employee": "Ann"
},
{
"id": "Sat night",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 13,
"workingTimeFairnessPercentage": 100.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee shift schedule with Ann enjoying her day off on Wednesday.
She is not assigned an afternoon shift on Tuesday or a morning shift on Thursday.
5. Overlapping shift rules
There are situations where employees need to be assigned to multiple shifts that occur at the same time, for instance, their regular shift and an on call shift that overlaps their regular shift.
Overlapping shifts rules are defined in contracts
.
{
"contracts": [
{
"id": "fullTimeContract",
"allowOverlappingShiftsRules": [
{
"id": "allowOverlapWithOnCallShift",
"includeShiftTags": [
"On call"
],
"shiftTagMatches": "ALL"
}
]
}
]
}
An allowOverlappingShiftsRules
must include an id
.
The Overlapping shifts
hard constraint makes sure only shifts with tags defined by the rule’s includeShiftTags
can overlap with other shifts.
Alternatively, only shifts without tags defined by the rule’s excludeShiftTags
can overlap with other shifts.
5.1. Include or exclude shifts with shift tags
Shifts with specific tags can be included or excluded by the rule. Tags are defined in shifts:
{
"shifts": [
{
"id": "2027-02-01",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["ICU"]
}
]
}
Use includeShiftTags
to include shifts with specific tags or excludeShiftTags
to exclude shifts with specific tags.
shiftTagMatches
can be set to ALL
or ANY
.
The default behavior for shiftTagMatches
is ALL
, and if omitted, the default ALL
will be used.
5.1.1. Include shift tags
{
"includeShiftTags": ["ICU", "Cardiology"],
"shiftTagMatches": "ALL"
}
With shiftTagMatches
set to ALL
, all tags defined by the rule’s includeShiftTags
attribute must be present in the shift. With shiftTagMatches
set to ANY
, at least one tag defined by the rule’s includeShiftTags
attribute must be present in the shift.
5.1.2. Exclude shift tags
{
"excludeShiftTags": ["Part-time", "Weekend"],
"shiftTagMatches": "ALL"
}
With shiftTagMatches
set to ALL
, all tags defined by the rule’s excludeShiftTags
attribute cannot be present in the shift.
This is useful when you want to exclude things in combination with each other.
For instance, excluding the shift tags Part-time
and Weekend
with shiftTagMatches
set to All
, would exclude shifts that include the tags Part-time
and Weekend
from the rule.
Shifts tagged only Part-time
or only Weekend
will not be excluded.
With shiftTagMatches
set to ANY
, any of the tags defined by the rule’s excludeShiftTags
attribute cannot be present in the shift.
This is useful when you need to exclude tags regardless of their relationship to other tags.
For instance, excluding the shift tags Part-time
and Weekend
with shiftTagMatches
set to ANY
, would exclude any shift that includes the tags Part-time
or Weekend
, whether they occur together or not.
The rule can define either includeShiftTags
or excludeShiftTags
, but not both.
5.2. Overlapping shift rules example
In the following example, Carl is assigned to a 24 hour on call shift that overlaps with his regular 8 hour shift.
-
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": "Overlapping shifts rules example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"allowOverlappingShiftsRules": [
{
"id": "allowOverlapWithOnCallShift",
"includeShiftTags": [
"On call"
],
"shiftTagMatches": "ALL"
}
]
}
],
"employees": [
{
"id": "Carl",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon on-call",
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z",
"tags": ["On call"]
},
{
"id": "Mon regular shift",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Overlapping shifts rules example",
"submitDateTime": "2025-03-21T04:48:22.193823297Z",
"startDateTime": "2025-03-21T04:48:33.625081572Z",
"activeDateTime": "2025-03-21T04:48:33.773882421Z",
"completeDateTime": "2025-03-21T04:53:34.494085854Z",
"shutdownDateTime": "2025-03-21T04:53:34.766113937Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon on-call",
"employee": "Carl"
},
{
"id": "Mon regular shift",
"employee": "Carl"
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 2,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 2,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with overlapping shifts assigned to Carl.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 2,
"unassignedShifts": 0
}
Next
-
Understand the constraints of the Employee Shift Scheduling model.
-
See the full API spec or try the online API.
-
Manage schedules with Time zones and Daylight Saving Time (DST) changes.
-
Working with Employee availability.