Employee contracts: Shift rules
Employee contracts stipulate the conditions under which the employees work, such as their working hours, the number of hours worked per month, week, or day, and the amount of time that must occur between scheduled shifts.
Contracts can specify shifts to control how many consecutive days employees work, how long they should have between shifts, and avoid assigning shifts directly before or after the employee has a day off request.
Employees have an expectation that the terms of their contracts are honored in the shifts they are scheduled to work. 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:
1. Consecutive days worked rule
Contracts can include rules about the maximum or minimum number of consecutive days employees work.
In the following example, Beth works 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
needs to include an id
for the rule, and a maximum
or minimum
number of days that can be worked consecutively.
satisifiability
is optional and REQUIRED
by default if omitted.
With a satisfiability of REQUIRED
, breaking the rule would break a hard constraint and result in a hard penalty being applied to the solution score.
With a satisfiability of PREFERRED
, breaking the rule would break a soft constraint and result in a soft penalty being applied to the solution score.
In this instance, the rule states a maximum of 3 consecutive days can be worked.
The maximum and minimum can be combined, for instance, a minimum of 2 consecutive days and 3 maximum consecutive days. Such a rule would result in an employee being assigned between 2 and 3 days inclusive in the shift schedule.
In the following example, Beth works 3 consecutive twelve-hour shifts.
-
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
}
]
}
],
"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": "2024-10-21T09:41:26.684484561Z",
"startDateTime": "2024-10-21T09:41:31.446186159Z",
"activeDateTime": "2024-10-21T09:41:31.546186159Z",
"completeDateTime": "2024-10-21T09:46:31.654904532Z",
"shutdownDateTime": "2024-10-21T09:46:31.754904532Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon",
"employee": "Beth"
},
{
"id": "Tue",
"employee": "Beth"
},
{
"id": "Wed",
"employee": "Beth"
},
{
"id": "Thu",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule with Beth assigned three consecutive shifts.
Consecutive days worked rules applies 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 a week), include a period rule. See combining contractual rules for an example of including multiple contract rules. |
1.1. Filtering consecutive days worked with tags
Tags can be added to consecutiveDaysWorkedRule
and shifts
to control which shifts the consecutive days worked rules apply to.
For instance, if an employee works a five-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.
If shifts have multiple tags, you decide whether to match ALL of the tags or ANY of the tags based on the period rule by including shiftTagMatches
and setting it to ALL
or ANY
. If omitted, the default is ALL
.
{
"consecutiveDaysWorkedRules": [
{
"id": "Max2ConsecutiveDaysDepartmentA",
"maximum": 2,
"includeShiftTags": [
"department A"
],
"shiftTagMatches": "ALL"
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-01T08:00:00Z",
"tags": [
"department A"
]
}
]
}
In the following example, Ann is a full time employee who works five days per week, but can only work two consecutive days in department A. She is assigned to 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. 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 the following day shift.
You can specify the minimum or 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"
}
]
}
]
}
The minimum time between shifts is defined in contracts
.
minutesBetweenShiftsRules
needs to include an id
for the rule and the time between shifts in minutes.
In this instance, the time between shifts is 720
minutes, which ensures employees have 12 hours between assigned shifts.
satisifiability
is optional and REQUIRED
by default if omitted.
With a satisfiability of REQUIRED
, breaking the rule would break a hard constraint and result in a hard penalty being applied to the solution score.
With a satisfiability of PREFERRED
, breaking the rule would break a soft constraint and result in a soft penalty being applied to the solution score.
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": "Contract shift minutes between shifts 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"
},
{
"id": "Thu night",
"start": "2027-02-04T01:00:00Z",
"end": "2027-02-04T09:00:00Z"
},
{
"id": "Thu day",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z"
},
{
"id": "Fri night",
"start": "2027-02-05T01:00:00Z",
"end": "2027-02-05T09:00:00Z"
},
{
"id": "Fri day",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17: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": "Contract shift minutes between shifts example",
"submitDateTime": "2024-10-21T09:55:47.978869443Z",
"startDateTime": "2024-10-21T09:55:52.675396602Z",
"activeDateTime": "2024-10-21T09:55:52.775396602Z",
"completeDateTime": "2024-10-21T10:00:52.874551078Z",
"shutdownDateTime": "2024-10-21T10:00:52.974551078Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-5medium/0soft",
"tags": [],
"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
},
{
"id": "Thu night",
"employee": "Carl"
},
{
"id": "Thu day",
"employee": null
},
{
"id": "Fri night",
"employee": "Carl"
},
{
"id": "Fri day",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 5,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule with Carl assigned shifts with more than 12 hours between each shift.
3. 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
for the rule.
-
avoidPriorShiftTags
must include the tags for the shifts to excluded 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.
satisifiability
is optional and PROHIBITED
by default if omitted.
With a satisfiability of PROHIBITED
, breaking the rule would break a hard constraint and result in a hard penalty being applied to the solution score.
With a satisfiability of UNPREFERRED
, breaking the rule would break a soft constraint and result in a soft penalty being applied to the solution score.
In the following example, Ann can work morning, afternoon, or night shifts.
She has Wednesday off as defined by the employee unavailabletimeSpan
.
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.
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.