Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Employee Shift Scheduling
  • Employee resource constraints
  • Employee contracts: shift rules

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
      • Employee contracts: period rules
      • Employee contracts: shift rules
      • Fairness
      • Pairing employees
      • Shift travel and locations
    • Shift service constraints
      • Alternative shifts
      • Demand and supply
      • Mandatory and optional visits
      • Shift assignments
      • Shift sequence patterns: single day shifts
      • Shift sequence patterns: multi-day shifts
      • Shift sequence patterns: daily shift pairings
      • Skills and risk factors
    • Recommendations
    • Real-time planning
    • Time zones and Daylight Saving Time (DST)
    • New and noteworthy
    • Upgrading to the latest versions
    • Feature requests

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 rules related to how many hours employees work, see Employee contracts: period rules.

For rules related to where employees work, see Shift travel and locations.

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:

  • Consecutive days worked rules

  • Control when consecutive days worked rules apply

  • Minutes between shifts rules

  • Avoid shift close to day off request rules

  • Overlapping shift rules

To learn how to define a contract, see Employee contracts.

Prerequisites

To run the examples in this guide, you need to authenticate with a valid API key for the Employee Shift Scheduling model:

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

consecutive days worked rule

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, incentivizing 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:

  1. Shifts with specific tags can be included or excluded from the rules.

  2. 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.1.3. Example include shift tags

{
  "contracts": [
    {
      "id": "fullTimeContract",
      "consecutiveDaysWorkedRules": [
        {
          "id": "Max3Consecutive12HourShifts",
          "maximum": 3,
          "satisfiability": "REQUIRED",
          "includeShiftTags": ["ICU"],
          "shiftTagMatches": "ALL"
        }
      ]
    }
  ]
}

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

shiftTypesTagCategories 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",
      "shiftTypesTagCategories": [
        "day", "night"
      ]
    }
  ]
}

With the maximum set to 3 and shiftTypesTagCategories 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, incentivizing 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, incentivizing 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

  • See the Employee shift scheduling user guide

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

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