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

Employee Shift Scheduling

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terms
      • Use case guide
      • Planning AI concepts
      • Constraints
      • Understanding the API
      • Demo datasets
      • Planning window
      • Time zones and Daylight Saving Time (DST)
      • Tags and tag types
      • Validation
      • Metrics and optimization goals
      • Score analysis
    • Employee resource constraints
      • Employee availability
      • Employee contracts
      • Pairing employees
      • Shift travel and locations
      • Employee activation
      • 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
        • 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
        • Single day shift sequence patterns
        • Minimize gaps between shifts
        • Multi-day shift sequence patterns
        • Daily shift pairings
        • Overlapping shifts
        • Shift start times differences
        • Minutes between shifts
      • Shift type diversity
        • Shift types worked per period
        • Unique tags per period
      • Fairness
        • Balance time worked
        • Balance shift count
    • Shift service constraints
      • Alternative shifts
      • Cost management
      • Demand-based scheduling
      • Mandatory and optional shifts
      • Shift assignments
      • Skills and risk factors
    • Recommendations
    • Real-time planning
    • Real-time planning (preview)
    • Scenarios
      • Configuring labor law compliance
    • Changelog
    • Upgrade to the latest version
    • 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.

This guide explains shift rotations with the following examples:

  • Defining shift rotation rules

  • Required weekly shift rotations

  • Preferred weekly shift rotations

  • Required custom shift rotations

  • Preferred custom shift rotations

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",
          "rotationPeriod": {
            "type": "WEEKLY"
          },
          "rotationGroups": [
            {
              "id": "morningShifts",
              "includeShiftTags": [
                "Morning"
              ],
              "shiftTagMatches": "ANY"
            },
            {
              "id": "afternoonShifts",
              "includeShiftTags": [
                "Afternoon"
              ],
              "shiftTagMatches": "ANY"
            }
          ],
          "satisfiability": "REQUIRED"
        }
      ]
    }
  ]
}

shiftRotationRules must include an id.

rotationPeriod specifies the length each rotation lasts for and must include the type. Currently, WEEKLY and CUSTOM are supported.

rotationGroups contains multiple groups. Each group must include an id and the shift tags to be included in the group for a rotation.

Shifts are included or excluded from the rotations groups with includeShiftTags or excludeShiftTags. The rule can define either includeShiftTags or excludeShiftTags, not both.

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 weekly shift rotations

When the satisfiability of the rule is REQUIRED and the rotation period is WEEKLY, 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 weekly 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 weekly shift rotations example"
    }
  },
  "modelInput": {
    "contracts": [
      {
        "id": "fullTimeContract",
        "periodRules": [
          {
            "id": "Max8HoursPerDay",
            "period": "DAY",
            "satisfiability": "REQUIRED",
            "minutesWorkedMax": 480
          }
        ],
        "shiftRotationRules": [
          {
            "id": "rotateMorningAndAfternoonShiftsWeekly",
            "rotationPeriod": {
              "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>
{
  "metadata": {
    "id": "ID",
    "parentId": null,
    "originId": "ID",
    "name": "Required weekly 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
  }
}

3. Preferred weekly shift rotations

When the satisfiability of the rule is PREFERRED and the rotation period is WEEKLY, the Preferred shift rotation not met for employee soft constraint is invoked.

By default, every constraint has a weight of 1, meaning that all constraints are equally important.

Learn about changing the weight of this constraint:

Use the constraint configuration’s preferredShiftRotationNotMetForEmployeeWeight attribute to update the weight of the constraint. 0 disables the constraint. A constraint weight of 10, means the constraint is 10 times more important than a constraint with a weight of 1.

"overrides": { "preferredShiftRotationNotMetForEmployeeWeight": 10 }

When employees are assigned shifts that break the shiftRotationRules, this constraint adds a soft penalty to the dataset score, incentivizing Timefold to find an alternative solution.

This rule is more likely to be satisfied for employees with a higher employee priority.
{
  "contracts": [
    {
      "id": "fullTimeContract",
      "shiftRotationRules": [
        {
          "id": "rotateMorningAndAfternoonShiftsWeekly",
          "rotationPeriod": {
            "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 weekly 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 weekly shift rotations example"
    }
  },
  "modelInput": {
    "contracts": [
      {
        "id": "fullTimeContract",
        "shiftRotationRules": [
          {
            "id": "rotateMorningAndAfternoonShiftsWeekly",
            "rotationPeriod": {
              "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>
{
  "metadata": {
    "id": "ID",
    "parentId": null,
    "originId": "ID",
    "name": "Preferred weekly 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
  }
}

4. Required custom shift rotations

When the satisfiability of the rule is REQUIRED and the rotation period is CUSTOM, the Custom rotation duration in days exceeds required maximum 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. The constraint defines the maximum length of the custom period.

Shifts will be left unassigned if assigning them would break the Custom rotation duration in days exceeds required maximum for employeee constraint.

requiredRotationPeriodMaxSizeInDays specifies the maximum duration of the rotation period in days.

With requiredRotationPeriodMaxSizeInDays set to 30, employees will be assigned shifts from one of the rotationGroups for periods of at most 30 days.

{
  "rotationPeriod": {
    "type": "CUSTOM",
    "requiredRotationPeriodMaxSizeInDays": 30
  }
}

4.1. Length of the maximum rotation period

Consider the following shifts matching either rotation group morningShifts, or rotation group afternoonShifts:

  • Shift 1 morning: 1st Jan 2025

  • Shift 2 morning: 2nd Jan 2025

  • Shift 3 morning: 3rd Jan 2025

  • Shift 4 morning: 5th Jan 2025

  • Shift 5 afternoon: 8th Jan 2025

  • Shift 6 afternoon: 10th Jan 2025

  • Shift 7 afternoon: 11th Jan 2025

  • Shift 8 afternoon: 13th Jan 2025

Rotation period for group morningShifts:

  • starts on 1st Jan 2025 (Shift 1)

  • ends on 5th Jan 2025 (Shift 4)

  • has length of 5 days.

Rotation period for group afternoonShifts:

  • starts on 8th Jan 2025 (Shift 5)

  • ends on 13th Jan 2025 (Shift 8)

  • has length of 6 days.

Free days in between different rotation periods (6th Jan 2025, 7th Jan 2025) are not included in the period’s length.

In the following example, shiftRotationRules specifies that employees work a maximum of 3 days (a custom period) of morning shifts followed by 3 days of afternoon shifts. The rule also specifies a preferred minimum of 3 days.

There are 2 employees and 12 shifts over 6 days.

Ann is assigned 3 days of morning shifts followed by 3 days of afternoon shifts. Beth is assigned 3 days of afternoon shifts followed by 3 days of morning shifts.

required custom 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 custom shift rotations example"
    }
  },
  "modelInput": {
    "contracts": [
      {
        "id": "fullTimeContract",
        "periodRules": [
          {
            "id": "Max8HoursPerDay",
            "period": "DAY",
            "satisfiability": "REQUIRED",
            "minutesWorkedMax": 480
          }
        ],
        "shiftRotationRules": [
          {
            "id": "rotateMorningAndAfternoonShiftsCustom",
            "rotationPeriod": {
              "type": "CUSTOM",
              "requiredRotationPeriodMaxSizeInDays": 3,
              "preferredRotationPeriodMinSizeInDays": 3
            },
            "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 AM",
        "start": "2027-02-01T06:00:00Z",
        "end": "2027-02-01T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Mon PM",
        "start": "2027-02-01T14:00:00Z",
        "end": "2027-02-01T22:00:00Z",
        "tags": ["Afternoon"]
      },
      {
        "id": "Tue AM",
        "start": "2027-02-02T06:00:00Z",
        "end": "2027-02-02T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Tue PM",
        "start": "2027-02-02T14:00:00Z",
        "end": "2027-02-02T22:00:00Z",
        "tags": ["Afternoon"]
      },
      {
        "id": "Wed AM",
        "start": "2027-02-03T06:00:00Z",
        "end": "2027-02-03T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Wed PM",
        "start": "2027-02-03T14:00:00Z",
        "end": "2027-02-03T22:00:00Z",
        "tags": ["Afternoon"]
      },
      {
        "id": "Thu AM",
        "start": "2027-02-04T06:00:00Z",
        "end": "2027-02-04T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Thu PM",
        "start": "2027-02-04T14:00:00Z",
        "end": "2027-02-04T22:00:00Z",
        "tags": ["Afternoon"]
      },
      {
        "id": "Fri AM",
        "start": "2027-02-05T06:00:00Z",
        "end": "2027-02-05T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Fri PM",
        "start": "2027-02-05T14:00:00Z",
        "end": "2027-02-05T22:00:00Z",
        "tags": ["Afternoon"]
      },
      {
        "id": "Sat AM",
        "start": "2027-02-06T06:00:00Z",
        "end": "2027-02-06T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Sat PM",
        "start": "2027-02-06T14:00:00Z",
        "end": "2027-02-06T22: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>
{
  "metadata": {
    "id": "ID",
    "parentId": null,
    "originId": "ID",
    "name": "Required custom shift rotations example",
    "submitDateTime": "2025-10-22T06:31:14.366941858Z",
    "startDateTime": "2025-10-22T06:31:33.903102421Z",
    "activeDateTime": "2025-10-22T06:31:33.965856682Z",
    "completeDateTime": "2025-10-22T06:32:04.604713595Z",
    "shutdownDateTime": "2025-10-22T06:32:04.604724836Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/0soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "shifts": [
      {
        "id": "Mon AM",
        "employee": "Ann"
      },
      {
        "id": "Mon PM",
        "employee": "Beth"
      },
      {
        "id": "Tue AM",
        "employee": "Ann"
      },
      {
        "id": "Tue PM",
        "employee": "Beth"
      },
      {
        "id": "Wed AM",
        "employee": "Ann"
      },
      {
        "id": "Wed PM",
        "employee": "Beth"
      },
      {
        "id": "Thu AM",
        "employee": "Beth"
      },
      {
        "id": "Thu PM",
        "employee": "Ann"
      },
      {
        "id": "Fri AM",
        "employee": "Beth"
      },
      {
        "id": "Fri PM",
        "employee": "Ann"
      },
      {
        "id": "Sat AM",
        "employee": "Beth"
      },
      {
        "id": "Sat PM",
        "employee": "Ann"
      }
    ]
  },
  "inputMetrics": {
    "employees": 2,
    "shifts": 12,
    "pinnedShifts": 0
  },
  "kpis": {
    "assignedShifts": 12,
    "unassignedShifts": 0,
    "disruptionPercentage": 0,
    "activatedEmployees": 2,
    "assignedMandatoryShifts": 12,
    "assignedOptionalShifts": 0
  },
  "run": {
    "id": "a3a28f95-a2f7-4ce8-87f4-12df6ad23645",
    "parentId": null,
    "originId": "a3a28f95-a2f7-4ce8-87f4-12df6ad23645",
    "name": "Required custom shift rotations example",
    "submitDateTime": "2025-10-22T06:31:14.366941858Z",
    "startDateTime": "2025-10-22T06:31:33.903102421Z",
    "activeDateTime": "2025-10-22T06:31:33.965856682Z",
    "completeDateTime": "2025-10-22T06:32:04.604713595Z",
    "shutdownDateTime": "2025-10-22T06:32:04.604724836Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/0soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  }
}

5. Preferred custom shift rotations

When the satisfiability of the rule is PREFERRED and the rotation period is CUSTOM, the Custom rotation duration in days not in preferred range for employee soft constraint is invoked, which makes sure employees are assigned shifts from 1 rotationGroups during the first period, then a different rotationGroups the next period. The constraint defines the maximum length of the custom period.

By default, every constraint has a weight of 1, meaning that all constraints are equally important.

Learn about changing the weight of this constraint:

Use the constraint configuration’s customRotationDurationInDaysNotInPreferredRangeForEmployeeWeight attribute to update the weight of the constraint. 0 disables the constraint. A constraint weight of 10, means the constraint is 10 times more important than a constraint with a weight of 1.

"overrides": { "customRotationDurationInDaysNotInPreferredRangeForEmployeeWeight": 10 }

When employees are assigned shifts that break the shiftRotationRules, this constraint adds a soft penalty to the dataset score, incentivizing Timefold to find an alternative solution.

This rule is more likely to be satisfied for employees with a higher employee priority.

preferredRotationPeriodMinSizeInDays specifies the preferred minimum duration of the rotation period in days.

preferredRotationPeriodMaxSizeInDays specifies the preferred maximum duration of the rotation period in days.

With preferredRotationPeriodMinSizeInDays set to 7, employees will be preferably assigned shifts from one of the rotationGroups for periods of at least 7 days.

With preferredRotationPeriodMaxSizeInDays set to 14, employees will be preferably assigned shifts from one of the rotationGroups for periods of at most 14 days.

{
  "rotationPeriod": {
    "type": "CUSTOM",
    "preferredRotationPeriodMinSizeInDays": 7,
    "preferredRotationPeriodMaxSizeInDays": 14
  }
}

5.1. Length of the maximum rotation period

Consider the following shifts matching either rotation group morningShifts, or rotation group afternoonShifts:

  • Shift 1 morning: 1st Jan 2025

  • Shift 2 morning: 2nd Jan 2025

  • Shift 3 morning: 3rd Jan 2025

  • Shift 4 morning: 5th Jan 2025

  • Shift 5 afternoon: 8th Jan 2025

  • Shift 6 afternoon: 10th Jan 2025

  • Shift 7 afternoon: 11th Jan 2025

  • Shift 8 afternoon: 13th Jan 2025

Rotation period for group morningShifts:

  • starts on 1st Jan 2025 (Shift 1)

  • ends on 5th Jan 2025 (Shift 4)

  • has length of 5 days.

Rotation period for group afternoonShifts:

  • starts on 8th Jan 2025 (Shift 5)

  • ends on 13th Jan 2025 (Shift 8)

  • has length of 6 days.

Free days in between different rotation periods (6th Jan 2025, 7th Jan 2025) are not included in the period’s length.

5.2. Length of the minimum rotation period

Consider the following shifts matching either rotation group A, or rotation group B:

  • Shift 1 morning: 1st Jan 2025

  • Shift 2 morning: 2nd Jan 2025

  • Shift 3 morning: 3rd Jan 2025

  • Shift 4 morning: 5th Jan 2025

  • Shift 5 afternoon: 8th Jan 2025

  • Shift 6 afternoon: 10th Jan 2025

  • Shift 7 afternoon: 11th Jan 2025

  • Shift 8 afternoon: 13th Jan 2025

Rotation period for group morningShifts:

  • starts on 1st Jan 2025 (Shift1)

  • ends on 7th Jan 2025 (free day)

  • has length of 7 days.

Rotation period for group afternoonShifts:

  • starts on 6th Jan 2025 (free day)

  • ends on 12th Jan 2025 (Shift 8)

  • has length of 8 days.

Free days in between different rotation periods (6th Jan 2025, 7th Jan 2025) are included in the period’s length.

In the following example, a shiftRotationRules specifies that employees work a preferred minimum of 3 days of morning shifts followed by a perferred minimum of 3 days of afternoon shifts. There is 1 employee and 6 shifts over 6 days. All the shifts are morning shifts.

Ann is assigned 6 days of morning shifts, which breaks the soft constraint and a soft score penalty is applied to the dataset score.

preferred custom 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 custom shift rotations example"
    }
  },
  "modelInput": {
    "contracts": [
      {
        "id": "fullTimeContract",
        "periodRules": [
          {
            "id": "Max8HoursPerDay",
            "period": "DAY",
            "satisfiability": "REQUIRED",
            "minutesWorkedMax": 480
          }
        ],
        "shiftRotationRules": [
          {
            "id": "rotateMorningAndAfternoonShiftsCustom",
            "rotationPeriod": {
              "type": "CUSTOM",
              "preferredRotationPeriodMaxSizeInDays": 3
            },
            "rotationGroups": [
              {
                "id": "morningShifts",
                "includeShiftTags": [
                  "Morning"
                ],
                "shiftTagMatches": "ANY"
              },
              {
                "id": "afternoonShifts",
                "includeShiftTags": [
                  "Afternoon"
                ],
                "shiftTagMatches": "ANY"
              }
            ],
            "satisfiability": "PREFERRED"
          }
        ]
      }
    ],
    "employees": [
      {
        "id": "Ann",
        "contracts": [
          "fullTimeContract"
        ]
      }
    ],
    "shifts": [
      {
        "id": "Mon AM",
        "start": "2027-02-01T06:00:00Z",
        "end": "2027-02-01T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Tue AM",
        "start": "2027-02-02T06:00:00Z",
        "end": "2027-02-02T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Wed AM",
        "start": "2027-02-03T06:00:00Z",
        "end": "2027-02-03T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Thu AM",
        "start": "2027-02-04T06:00:00Z",
        "end": "2027-02-04T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Fri AM",
        "start": "2027-02-05T06:00:00Z",
        "end": "2027-02-05T14:00:00Z",
        "tags": ["Morning"]
      },
      {
        "id": "Sat AM",
        "start": "2027-02-06T06:00:00Z",
        "end": "2027-02-06T14: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>
{
  "metadata": {
    "id": "ID",
    "parentId": null,
    "originId": "ID",
    "name": "Preferred custom shift rotations example",
    "submitDateTime": "2025-10-10T08:25:30.307602826Z",
    "startDateTime": "2025-10-10T08:25:42.332272749Z",
    "activeDateTime": "2025-10-10T08:25:42.376289928Z",
    "completeDateTime": "2025-10-10T08:26:13.00948503Z",
    "shutdownDateTime": "2025-10-10T08:26:13.00950799Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-288000soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "shifts": [
      {
        "id": "Mon AM",
        "employee": "Ann"
      },
      {
        "id": "Tue AM",
        "employee": "Ann"
      },
      {
        "id": "Wed AM",
        "employee": "Ann"
      },
      {
        "id": "Thu AM",
        "employee": "Ann"
      },
      {
        "id": "Fri AM",
        "employee": "Ann"
      },
      {
        "id": "Sat AM",
        "employee": "Ann"
      }
    ]
  },
  "inputMetrics": {
    "employees": 1,
    "shifts": 6,
    "pinnedShifts": 0
  },
  "kpis": {
    "assignedShifts": 6,
    "unassignedShifts": 0,
    "disruptionPercentage": 0.0,
    "activatedEmployees": 1,
    "assignedMandatoryShifts": 6,
    "assignedOptionalShifts": 0
  },
  "run": {
    "id": "6379939e-8e16-4efa-bcc1-ba0bc217c6e3",
    "parentId": null,
    "originId": "6379939e-8e16-4efa-bcc1-ba0bc217c6e3",
    "name": "Preferred custom shift rotations example",
    "submitDateTime": "2025-10-10T08:25:30.307602826Z",
    "startDateTime": "2025-10-10T08:25:42.332272749Z",
    "activeDateTime": "2025-10-10T08:25:42.376289928Z",
    "completeDateTime": "2025-10-10T08:26:13.00948503Z",
    "shutdownDateTime": "2025-10-10T08:26:13.00950799Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-288000soft",
    "tags": [
      "system.type:from-request",
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  }
}

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