Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Employee Shift Scheduling
  • Employee resource constraints
  • Shift rotations and patterns
  • Shift rotations

Employee Shift Scheduling

    • Introduction
    • Planning AI concepts
    • Metrics and optimization goals
    • Getting started with employee shift scheduling
    • Understanding the API
    • Employee shift scheduling user guide
    • Employee resource constraints
      • Employee availability
      • Employee contracts
      • Work limits
        • Work limits
        • Minutes worked per period
        • Minutes worked in a rolling window
        • Minutes logged per period
        • Days worked per period
        • Days worked in a rolling window
        • Consecutive days worked
        • Shifts worked per period
        • Shifts worked in a rolling window
        • Weekend minutes worked per period
        • Weekends worked per period
        • Weekends worked in a rolling window
        • Consecutive weekends worked
      • Time off
        • Time off
        • Days off per period
        • Consecutive days off per period
        • Consecutive days off in a rolling window
        • Consecutive minutes off in a rolling window
        • Shifts to avoid close to day off requests
      • Shift rotations and patterns
        • Shift rotations and patterns
        • Shift rotations
        • Single day shift sequence patterns
        • Multi-day shift sequence patterns
        • Daily shift pairings
        • Overlapping shifts
        • Shift start times differences
        • Minutes between shifts
      • Shift type diversity
        • Shift type diversity
        • Shift types worked per period
        • Unique tags per period
      • Fairness
        • Fairness
        • Balance time worked
        • Balance shift count
      • Pairing employees
      • Shift travel and locations
    • Shift service constraints
      • Alternative shifts
      • Cost management
      • Demand-based scheduling
      • Mandatory and optional shifts
      • Shift assignments
      • Skills and risk factors
    • Recommendations
    • Real-time planning
    • Time zones and Daylight Saving Time (DST)
    • Changelog
    • Upgrading to the latest versions
    • Feature requests

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:
  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.

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

required shift rotations

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

preferred shift rotations
  • 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.

  • © 2025 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default