Shift rotations
Shift rotations occur when employees work one type of shift for a set period followed by a shift of a different type for a set period, for instance, 1 week of morning shifts followed by 1 week of afternoon shifts.
Prerequisites
Learn how to configure an API Key to run the examples in this guide:
-
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. Defining shift rotation rules
shiftRotationRules
are defined in contracts
:
{
"contracts": [
{
"id": "fullTimeContract",
"shiftRotationRules": [
{
"id": "rotateMorningAndAfternoonShiftsWeekly",
"builtInRotationPeriod": {
"type": "WEEKLY"
},
"rotationGroups": [
{
"id": "morningShifts",
"includeShiftTags": [
"Morning"
],
"shiftTagMatches": "ANY"
},
{
"id": "afternoonShifts",
"includeShiftTags": [
"Afternoon"
],
"shiftTagMatches": "ANY"
}
],
"satisfiability": "REQUIRED"
}
]
}
]
}
shiftRotationRules
must include an id
.
builtInRotationPeriod
specifies the length each rotation lasts for and must includes the type
. Currently, only WEEKLY
is supported.
rotationGroups
contains multiple groups.
Each group must include an id
and the shift tags to be included in the group for a rotation.
shiftTagMatches
for each rotation group must be set to ANY
to ensure only one of the tags in a rotation group is required.
For instance, if a rotation group includes the tags Morning
and Afternoon
, only one of the tags should be required to assign a shift to an employee.
The satisfiability
of the rule can be REQUIRED
or PREFERRED
. If omitted, REQUIRED
is the default.
2. Required shift rotations
When the satisfiability of the rule is REQUIRED
, the Required shift rotation not met for employee
hard constraint is invoked, which makes sure employees are assigned shifts from 1 rotationGroups
during the first period, then a different rotationGroups
the next period.
Shifts will be left unassigned if assigning them would break the Required shift rotation not met for employee
constraint.
In the following example, shiftRotationRules
specifies that employees work 1 week of morning shifts followed by 1 week of afternoon shifts.
There are 2 employees and 20 shifts over a 2 week period.
Ann is assigned 1 week of morning shifts followed by 1 week of afternoon shifts. Beth is assigned 1 week of afternoon shifts followed by 1 week of morning 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": "Required shift rotations example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"minutesWorkedMax": 480
}
],
"shiftRotationRules": [
{
"id": "rotateMorningAndAfternoonShiftsWeekly",
"builtInRotationPeriod": {
"type": "WEEKLY"
},
"rotationGroups": [
{
"id": "morningShifts",
"includeShiftTags": [
"Morning"
],
"shiftTagMatches": "ANY"
},
{
"id": "afternoonShifts",
"includeShiftTags": [
"Afternoon"
],
"shiftTagMatches": "ANY"
}
],
"satisfiability": "REQUIRED"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
},
{
"id": "Beth",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon 1 AM",
"start": "2027-02-01T06:00:00Z",
"end": "2027-02-01T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Mon 1 PM",
"start": "2027-02-01T14:00:00Z",
"end": "2027-02-01T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Tue 1 AM",
"start": "2027-02-02T06:00:00Z",
"end": "2027-02-02T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Tue 1 PM",
"start": "2027-02-02T14:00:00Z",
"end": "2027-02-02T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Wed 1 AM",
"start": "2027-02-03T06:00:00Z",
"end": "2027-02-03T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Wed 1 PM",
"start": "2027-02-03T14:00:00Z",
"end": "2027-02-03T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Thu 1 AM",
"start": "2027-02-04T06:00:00Z",
"end": "2027-02-04T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Thu 1 PM",
"start": "2027-02-04T14:00:00Z",
"end": "2027-02-04T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Fri 1 AM",
"start": "2027-02-05T06:00:00Z",
"end": "2027-02-05T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Fri 1 PM",
"start": "2027-02-05T14:00:00Z",
"end": "2027-02-05T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Mon 2 AM",
"start": "2027-02-08T06:00:00Z",
"end": "2027-02-08T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Mon 2 PM",
"start": "2027-02-08T14:00:00Z",
"end": "2027-02-08T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Tue 2 AM",
"start": "2027-02-09T06:00:00Z",
"end": "2027-02-09T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Tue 2 PM",
"start": "2027-02-09T14:00:00Z",
"end": "2027-02-09T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Wed 2 AM",
"start": "2027-02-10T06:00:00Z",
"end": "2027-02-10T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Wed 2 PM",
"start": "2027-02-10T14:00:00Z",
"end": "2027-02-10T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Thu 2 AM",
"start": "2027-02-11T06:00:00Z",
"end": "2027-02-11T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Thu 2 PM",
"start": "2027-02-11T14:00:00Z",
"end": "2027-02-11T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Fri 2 AM",
"start": "2027-02-12T06:00:00Z",
"end": "2027-02-12T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Fri 2 PM",
"start": "2027-02-12T14:00:00Z",
"end": "2027-02-12T22:00:00Z",
"tags": ["Afternoon"]
}
]
}
}
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",
"parentId": null,
"originId": "ID",
"name": "Required shift rotations example",
"submitDateTime": "2025-06-19T06:39:05.474547181Z",
"startDateTime": "2025-06-19T06:39:20.839372083Z",
"activeDateTime": "2025-06-19T06:39:21.017286426Z",
"completeDateTime": "2025-06-19T06:39:51.974011807Z",
"shutdownDateTime": "2025-06-19T06:39:52.299836975Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1 AM",
"employee": "Ann"
},
{
"id": "Mon 1 PM",
"employee": "Beth"
},
{
"id": "Tue 1 AM",
"employee": "Ann"
},
{
"id": "Tue 1 PM",
"employee": "Beth"
},
{
"id": "Wed 1 AM",
"employee": "Ann"
},
{
"id": "Wed 1 PM",
"employee": "Beth"
},
{
"id": "Thu 1 AM",
"employee": "Ann"
},
{
"id": "Thu 1 PM",
"employee": "Beth"
},
{
"id": "Fri 1 AM",
"employee": "Ann"
},
{
"id": "Fri 1 PM",
"employee": "Beth"
},
{
"id": "Mon 2 AM",
"employee": "Beth"
},
{
"id": "Mon 2 PM",
"employee": "Ann"
},
{
"id": "Tue 2 AM",
"employee": "Beth"
},
{
"id": "Tue 2 PM",
"employee": "Ann"
},
{
"id": "Wed 2 AM",
"employee": "Beth"
},
{
"id": "Wed 2 PM",
"employee": "Ann"
},
{
"id": "Thu 2 AM",
"employee": "Beth"
},
{
"id": "Thu 2 PM",
"employee": "Ann"
},
{
"id": "Fri 2 AM",
"employee": "Beth"
},
{
"id": "Fri 2 PM",
"employee": "Ann"
}
]
},
"inputMetrics": {
"employees": 2,
"shifts": 20,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 20,
"unassignedShifts": 0,
"disruptionPercentage": 0.0,
"activatedEmployees": 2,
"assignedMandatoryShifts": 20,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with the shifts with Ann and Beth assigned shifts that match the shift rotation rules.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 20,
"activatedEmployees": 2,
"assignedMandatoryShifts": 20
}
3. Preferred shift rotations
When the satisfiability of the rule is PREFERRED
, the Preferred shift rotation not met for employee
soft constraint is invoked.
When employees are assigned shifts that break the shiftRotationRules
, this constraint adds a soft penalty to the run score, incentivizing Timefold to find an alternative solution.
{
"contracts": [
{
"id": "fullTimeContract",
"shiftRotationRules": [
{
"id": "rotateMorningAndAfternoonShiftsWeekly",
"builtInRotationPeriod": {
"type": "WEEKLY"
},
"rotationGroups": [
{
"id": "morningShifts",
"includeShiftTags": [
"Morning"
],
"shiftTagMatches": "ANY"
},
{
"id": "afternoonShifts",
"includeShiftTags": [
"Afternoon"
],
"shiftTagMatches": "ANY"
}
],
"satisfiability": "PREFERRED"
}
]
}
]
}
In the following example, a shiftRotationRules
specifies that employees work 1 week of morning shifts followed by 1 week of afternoon shifts.
There is 1 employee and 10 shifts over a 2-week period.
All the shifts are morning shifts.
Ann is assigned 2 weeks of morning shifts, which breaks the soft constraint.

-
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 rotations example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"shiftRotationRules": [
{
"id": "rotateMorningAndAfternoonShiftsWeekly",
"builtInRotationPeriod": {
"type": "WEEKLY"
},
"rotationGroups": [
{
"id": "morningShifts",
"includeShiftTags": [
"Morning"
],
"shiftTagMatches": "ANY"
},
{
"id": "afternoonShifts",
"includeShiftTags": [
"Afternoon"
],
"shiftTagMatches": "ANY"
}
],
"satisfiability": "PREFERRED"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon 1 AM",
"start": "2027-02-01T06:00:00Z",
"end": "2027-02-01T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Tue 1 AM",
"start": "2027-02-02T06:00:00Z",
"end": "2027-02-02T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Wed 1 AM",
"start": "2027-02-03T06:00:00Z",
"end": "2027-02-03T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Thu 1 AM",
"start": "2027-02-04T06:00:00Z",
"end": "2027-02-04T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Fri 1 AM",
"start": "2027-02-05T06:00:00Z",
"end": "2027-02-05T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Mon 2 AM",
"start": "2027-02-08T06:00:00Z",
"end": "2027-02-08T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Tue 2 AM",
"start": "2027-02-09T06:00:00Z",
"end": "2027-02-09T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Wed 2 AM",
"start": "2027-02-10T06:00:00Z",
"end": "2027-02-10T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Thu 2 AM",
"start": "2027-02-11T06:00:00Z",
"end": "2027-02-11T14:00:00Z",
"tags": ["Morning"]
},
{
"id": "Fri 2 AM",
"start": "2027-02-12T06:00:00Z",
"end": "2027-02-12T14:00:00Z",
"tags": ["Morning"]
}
]
}
}
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",
"parentId": null,
"originId": "ID",
"name": "Preferred shift rotations example",
"submitDateTime": "2025-06-19T07:11:29.173961222Z",
"startDateTime": "2025-06-19T07:11:40.3443435Z",
"activeDateTime": "2025-06-19T07:11:40.503308602Z",
"completeDateTime": "2025-06-19T07:12:11.278117228Z",
"shutdownDateTime": "2025-06-19T07:12:11.521967195Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-24000soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1 AM",
"employee": "Ann"
},
{
"id": "Tue 1 AM",
"employee": "Ann"
},
{
"id": "Wed 1 AM",
"employee": "Ann"
},
{
"id": "Thu 1 AM",
"employee": "Ann"
},
{
"id": "Fri 1 AM",
"employee": "Ann"
},
{
"id": "Mon 2 AM",
"employee": "Ann"
},
{
"id": "Tue 2 AM",
"employee": "Ann"
},
{
"id": "Wed 2 AM",
"employee": "Ann"
},
{
"id": "Thu 2 AM",
"employee": "Ann"
},
{
"id": "Fri 2 AM",
"employee": "Ann"
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 10,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 10,
"unassignedShifts": 0,
"disruptionPercentage": 0.0,
"activatedEmployees": 1,
"assignedMandatoryShifts": 10,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann assigned to all 10 morning shifts.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 10,
"activatedEmployees": 1,
"assignedMandatoryShifts": 10
}
Next
-
See the full API spec or try the online API.
-
Learn more about employee shift scheduling from our YouTube playlist.
-
See other options for Shift rotations and patterns.