Employee contracts: Period rules
Employees have an expectation that the terms of their contracts are honored in the shifts they are assigned to work.
Employee contracts stipulate the conditions under which the employees work, including:
-
How many minutes, days, shifts, and shift types they work in a given period.
-
How many unpaid breaks are included in a shift.
For rules related to when employees work, see Employee contracts: Shift 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 period rules with the following examples:
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:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
Create a new API key or use an existing one. Ensure the list of models for the API key contains the Employee Shift Scheduling model.
In the examples, replace <API_KEY>
with the API Key you just copied.
Counting days in period rules
By default, period rules count shifts within the defined period if the shift starts within the defined period. If you define a rule that an employee can only work 1 shift a day, a night shift that started on the previous day but ends the following morning will not be counted and another shift might be assigned.
To change this behavior change the field periodShiftOverlapKind
from START_ONLY
to START_AND_END
.
{
"periodRules": [
{
"id": "Max8HoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 480,
"satisfiability": "REQUIRED",
"periodShiftOverlapKind": "START_AND_END"
}
}
1. Contractual hours: minutes worked
Period rules can define how many minutes employees work in a given period. For instance, 480 minutes (8 hours) per day or 1,920 minutes (32 hours) per week.
Each employee must specify which contracts apply to them:
{
"employees": [
{
"id": "Ann",
"contracts": [
"partTimeContract"
]
}
]
}
Period rules are configured in contracts:
{
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"minutesWorkedMax": 480
},
{
"id": "Max32HoursPerWeek",
"period": "WEEK",
"satisfiability": "REQUIRED",
"minutesWorkedMax": 1920
}
]
}
]
}
PeriodRules
must include an ID.
period
specifies the timeframe the rule applies to.
minutesWorkedMax
sets the limit on how many minutes the employee can work in the specified period.
In some situations it is necessary to specify rules that apply to both the day and the week. If a rule for the week is included that sets a maximum number of hours per week at 40, but no rule is included for the day period, an employee could be assigned continuous shifts for 40 hours and then nothing for the rest of the week. Similarly, if a rule is included that sets a maximum number of hours per day at 8, but no rule is included for the week, an employee could be assigned an eight-hour shift every single day with no days off. |
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 Minutes worked per period not in required range for employee
hard constraint is invoked, which makes sure the number of minutes worked does not exceed the limit specified in minutesWorkedMax
.
Shifts will be left unassigned if assigning them would break the Minutes worked per period not in required range for employee
constraint.

1.2. Preferred satisfiability
When the satisfiability of the rule is PREFERRED
, the Minutes worked per period not in preferred range for employee
soft constraint is invoked.
Shifts will be assigned to employees even if the shifts take the employees minutes worked over the limit specified in minutesWorkedMax
, however, 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 minutesWorkedMin
value can also be provided to set the preferred minimum number of minutes worked.
{
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDay",
"period": "DAY",
"satisfiability": "PREFERRED",
"minutesWorkedMin": 120,
"minutesWorkedMax": 480
},
{
"id": "Max32HoursPerWeek",
"period": "WEEK",
"satisfiability": "PREFERRED",
"minutesWorkedMin": 960,
"minutesWorkedMax": 1920
}
]
}
]
}
1.3. Minutes worked rules example
In the following example, Ann works a maximum of 480 minutes (8 hours) per day and a maximum of 1,920 minutes (32 hours) per week. There are 5 shifts of 8 hours each, as this would take Ann over her 32-hour maximum, one of the shifts is left unassigned.
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Contract period rules minutes worked example"
}
},
"modelInput": {
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"minutesWorkedMax": 480
},
{
"id": "Max32HoursPerWeek",
"period": "WEEK",
"satisfiability": "REQUIRED",
"minutesWorkedMax": 1920
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"partTimeContract"
]
}
],
"shifts": [
{
"id": "Mon",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Tue",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "Wed",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "Thu",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z"
},
{
"id": "Fri",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Contract period rules minutes worked example",
"submitDateTime": "2025-03-31T06:35:05.908682739Z",
"startDateTime": "2025-03-31T06:35:21.38159037Z",
"activeDateTime": "2025-03-31T06:35:21.634093236Z",
"completeDateTime": "2025-03-31T06:40:22.536142384Z",
"shutdownDateTime": "2025-03-31T06:40:22.832585412Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon",
"employee": "Ann"
},
{
"id": "Tue",
"employee": "Ann"
},
{
"id": "Wed",
"employee": "Ann"
},
{
"id": "Thu",
"employee": "Ann"
},
{
"id": "Fri",
"employee": null
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 5,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 4,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 4,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann assigned to 1,920 minutes.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 4,
"unassignedShifts": 1,
"activatedEmployees": 1,
"assignedMandatoryShifts": 4
}
2. Logged time and period rules
Sometimes, a shift might include unpaid breaks that do not count toward the limit stipulated by a period rule.
Setting a shift loggedTime
attribute makes it possible to differentiate between the scheduled time, determined by shift start
and end
, and the actual working time, specified by loggedTime
.
For instance, consider the following two rules:
-
Max8LoggedHoursPerDayFullTime
limits the amount of shift logged time to 8 hours a day. -
Max10ScheduledHoursPerDayFullTime
limits the number of scheduled work hours to 10 hours a day, but does not include a limit for shift logged time.
{
"periodRules": [
{
"id": "Max8LoggedHoursPerDayFullTime",
"period": "DAY",
"minutesLoggedMax": 480
},
{
"id": "Max10ScheduledHoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 600
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-01T08:00:00Z",
"loggedTime": "PT6H30M"
}
]
}
When assigned to an employee with the above period rules, the Mon night
shift contributes to the limits as follows:
-
6 hours and 30 minutes to
Max8LoggedHoursPerDayFullTime
. -
8 hours to
Max10ScheduledHoursPerDayFullTime
.
2.1. Required satisfiability
When the satisfiability of the rule is REQUIRED
, the Logged minutes worked per period not in required range for employee
hard constraint is invoked, which makes sure the number of minutes logged does not exceed the limit specified in minutesLoggedMax
.
2.2. Preferred satisfiability
When the satisfiability of the rule is PREFERRED
, the Logged minutes worked per period not in preferred range for employee
soft constraint is invoked.
With PREFERRED
satisfiability you can also use minutesLoggedMin
to specify the minimum amount of shift logged time.
{
"periodRules": [
{
"id": "Max8LoggedHoursPerDayFullTime",
"period": "DAY",
"minutesLoggedMin": 120
}
]
}
The Minutes logged per period not in preferred range for employee
soft constraint adds a soft penalty to the run score when the number of minutes logged by the employee in a period is below the value of the period rule’s minutesLoggedMin
or above the value of the period rule’s minutesLoggedMax
, incentivizing Timefold to find an alternative solution.
3. Contractual hours: days worked
Period rules can define how many days employees work in a given period. For instance, a maximum of 4 days per week.
Each employee must specify which contracts apply to them:
{
"employees": [
{
"id": "Ann",
"contracts": [
"partTimeContract"
]
}
]
}
Period rules are configured in contracts:
{
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Max4DaysPerWeek",
"satisfiability": "REQUIRED",
"period": "WEEK",
"daysWorkedMax": 4
}
]
}
]
}
PeriodRules
must include an ID.
period
specifies the timeframe the rule applies to.
daysWorkedMax
sets the maximum number of days the employee can work in the specified period.
satisfiability
can be REQUIRED
or PREFERRED
.
REQUIRED
is the default if omitted.
3.1. Required satisfiability
When the satisfiability
of the rule is REQUIRED
, the Days worked per period not in required range for employee
hard constraint is invoked, which makes sure the number of days worked does not exceed the limit specified in daysWorkedMax
.
Shifts will be left unassigned if assigning them would break the Days worked per period not in required range for employee
constraint.
3.2. Preferred satisfiability
When the satisfiability
of the rule is PREFERRED
, the Days worked per period not in preferred range for employee
soft constraint is invoked.
With a satisfiability
of PREFERRED
a daysWorkedMin
value can also be provided to set the preferred minimum number of days worked.
{
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Min2DaysMax4DaysPerWeek",
"satisfiability": "PREFERRED",
"period": "WEEK",
"daysWorkedMin": 2,
"daysWorkedMax": 4
}
]
}
]
}
This constraint adds a soft penalty to the run score when the number of assigned days is below the limit defined by daysWorkedMin
or above the limit defined by daysWorkedMax
, incentivizing Timefold to find an alternative solution.
3.3. Days worked rules example
In the following example, Ann and Beth both have contracts with a preferred minimum of 2 days and a preferred maximum of 4 days per week. There are 5 shifts on 5 days. Beth is assigned 2 shift and Ann is assigned 3 shifts.

-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Contract period rules days worked example"
}
},
"modelInput": {
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Min2DaysMax4DaysPerWeek",
"period": "WEEK",
"satisfiability": "PREFERRED",
"daysWorkedMin": 2,
"daysWorkedMax": 4
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"partTimeContract"
]
},
{
"id": "Beth",
"contracts": [
"partTimeContract"
]
}
],
"shifts": [
{
"id": "Mon",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Tue",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "Wed",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "Thu",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z"
},
{
"id": "Fri",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Contract period rules days worked example",
"submitDateTime": "2025-03-31T08:15:18.068850927Z",
"startDateTime": "2025-03-31T08:15:28.940042351Z",
"activeDateTime": "2025-03-31T08:15:29.019242953Z",
"completeDateTime": "2025-03-31T08:20:30.167581606Z",
"shutdownDateTime": "2025-03-31T08:20:30.330498606Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon",
"employee": "Ann"
},
{
"id": "Tue",
"employee": "Ann"
},
{
"id": "Wed",
"employee": "Beth"
},
{
"id": "Thu",
"employee": "Ann"
},
{
"id": "Fri",
"employee": "Beth"
}
]
},
"inputMetrics": {
"employees": 2,
"shifts": 5,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 5,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann and Beth assigned to shifts on days within the range of 2 to 4 days per week.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 5,
"unassignedShifts": 0,
"activatedEmployees": 2,
"assignedMandatoryShifts": 5
}
4. Contractual hours: shifts worked
Period rules can define how many shifts employees work in a given period. For instance, a maximum of 4 shifts per week.
"Days worked rules" and "shifts worked rules" are similar, however, many industries schedule employees multiple shifts per day or spilt shifts. Days worked rules are appropriate when employees are never assigned more than a single shift in a day, however, shifts worked rules are suitable when multiple shifts might be assigned on the same day. |
Each employee must specify which contracts apply to them:
{
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
]
}
Period rules are configured in contracts:
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max4ShiftsPerWeek",
"satisfiability": "REQUIRED",
"period": "WEEK",
"shiftsWorkedMax": 4
}
]
}
]
}
PeriodRules
must include an ID.
period
specifies the timeframe the rule applies to.
shiftsWorkedMax
sets the maximum number of shifts the employee can work in the specified period.
satisfiability
can be REQUIRED
or PREFERRED
.
REQUIRED
is the default if omitted.
4.1. Required satisfiability
When the satisfiability
of the rule is REQUIRED
, the Shifts worked per period not in required range for employee
hard constraint is invoked, which makes sure the number of shifts worked does not exceed the limit specified in shiftsWorkedMax
.
Shifts will be left unassigned if assigning them would break the Shifts worked per period not in required range for employee
constraint.
4.2. Preferred satisfiability
When the satisfiability
of the rule is PREFERRED
, the Shifts worked per period not in preferred range for employee
soft constraint is invoked.
With a satisfiability
of PREFERRED
a shiftsWorkedMin
value can also be provided to set the preferred minimum number of shifts worked.
{
"contracts": [
{
"id": "partTimeContract",
"periodRules": [
{
"id": "Min2ShiftsMax4ShiftsPerWeek",
"satisfiability": "PREFERRED",
"period": "WEEK",
"shiftsWorkedMin": 2,
"shiftsWorkedMax": 4
}
]
}
]
}
This constraint adds a soft penalty to the run score when the number of assigned days is below the limit defined by shiftsWorkedMin
or above the limit defined by shiftsWorkedMax
, incentivizing Timefold to find an alternative solution.
4.3. Shifts worked rules example
In the following example, Ann and Beth both work a preferred minimum of 2 shifts and a preferred maximum of 4 shifts per week. There are 10 shifts over 5 days. Ann is assigned 6 shifts and Beth is assigned 4 shifts.

-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Contract period rules shifts worked example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Min2ShiftsMax4ShiftsPerWeek",
"period": "WEEK",
"satisfiability": "PREFERRED",
"shiftsWorkedMin": 2,
"shiftsWorkedMax": 4
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
},
{
"id": "Beth",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon AM",
"start": "2027-02-01T06:00:00Z",
"end": "2027-02-01T11:00:00Z"
},
{
"id": "Mon PM",
"start": "2027-02-01T15:00:00Z",
"end": "2027-02-01T20:00:00Z"
},
{
"id": "Tue AM",
"start": "2027-02-02T06:00:00Z",
"end": "2027-02-02T11:00:00Z"
},
{
"id": "Tue PM",
"start": "2027-02-02T15:00:00Z",
"end": "2027-02-02T20:00:00Z"
},
{
"id": "Wed AM",
"start": "2027-02-03T06:00:00Z",
"end": "2027-02-03T11:00:00Z"
},
{
"id": "Wed PM",
"start": "2027-02-03T15:00:00Z",
"end": "2027-02-03T20:00:00Z"
},
{
"id": "Thu AM",
"start": "2027-02-04T06:00:00Z",
"end": "2027-02-04T11:00:00Z"
},
{
"id": "Thu PM",
"start": "2027-02-04T15:00:00Z",
"end": "2027-02-04T20:00:00Z"
},
{
"id": "Fri AM",
"start": "2027-02-05T06:00:00Z",
"end": "2027-02-05T11:00:00Z"
},
{
"id": "Fri PM",
"start": "2027-02-05T15:00:00Z",
"end": "2027-02-05T20:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Contract period rules shifts worked example",
"submitDateTime": "2025-03-31T11:31:34.839626724Z",
"startDateTime": "2025-03-31T11:31:45.574253759Z",
"activeDateTime": "2025-03-31T11:31:45.699778641Z",
"completeDateTime": "2025-03-31T11:36:46.699637795Z",
"shutdownDateTime": "2025-03-31T11:36:46.907201752Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-1920soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon AM",
"employee": "Ann"
},
{
"id": "Mon PM",
"employee": "Ann"
},
{
"id": "Tue AM",
"employee": "Beth"
},
{
"id": "Tue PM",
"employee": "Beth"
},
{
"id": "Wed AM",
"employee": "Ann"
},
{
"id": "Wed PM",
"employee": "Ann"
},
{
"id": "Thu AM",
"employee": "Beth"
},
{
"id": "Thu PM",
"employee": "Beth"
},
{
"id": "Fri AM",
"employee": "Ann"
},
{
"id": "Fri PM",
"employee": "Ann"
}
]
},
"inputMetrics": {
"employees": 2,
"shifts": 10,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 10,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 10,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann and Beth assigned to shifts within the range of 2 to 4 shifts per week.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 5,
"unassignedShifts": 0,
"activatedEmployees": 2,
"assignedMandatoryShifts": 5
}
5. Shift types
Period rules can define how many different types of shifts employees work in a given period. For instance, for shifts at different times of day, in different departments, or different job functions.
Shifts must be tagged with this additional information:
{
"shifts": [
{
"id": "Mon AM",
"start": "2027-02-01T06:00:00Z",
"end": "2027-02-01T14:00:00Z",
"tags": ["Early"]
}
]
}
Period rules to define the maximum number of shift types worked per period are defined in contracts:
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max2ShiftTypesPerWeek",
"period": "WEEK",
"satisfiability": "REQUIRED",
"shiftTypesTagCategories": ["Early", "Afternoon", "Night"],
"shiftTypesWorkedMax": 2
}
]
}
]
}
PeriodRules
must include an ID.
period
specifies the timeframe the rule applies to.
shiftTypesWorkedMax
sets the maximum number of shift types the employee can work in the specified period.
shiftTypesTagCategories
specifies which tags the rule applies to.
Shifts can only be tagged with one of the tags specified by shiftTypesTagCategories
.
satisfiability
can be REQUIRED
or PREFERRED
.
REQUIRED
is the default if omitted.
5.1. Required satisfiability
When the satisfiability
of the rule is REQUIRED
, the Shift types worked per period not in required range for employee
hard constraint is invoked, which makes sure the number of shift types worked does not exceed the limit specified in shiftTypesWorkedMax
.
Shifts will be left unassigned if assigning them would break the Shift types worked per period not in required range for employee
constraint.
5.2. Preferred satisfiability
When the satisfiability
of the rule is PREFERRED
, the Shift types worked per period not in preferred range for employee
soft constraint is invoked.
With a satisfiability
of PREFERRED
a shiftTypesWorkedMin
value can also be provided to set the preferred minimum number of shifts worked.
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max2ShiftTypesPerWeek",
"period": "WEEK",
"satisfiability": "PREFERRED",
"shiftTypesTagCategories": ["Early", "Afternoon", "Night"],
"shiftTypesWorkedMIN": 2,
"shiftTypesWorkedMax": 3
}
]
}
]
}
This constraint adds a soft penalty to the run score when the number of assigned shift types is below the limit defined by shiftTypesWorkedMin
or above the limit defined by shiftTypesWorkedMax
, incentivizing Timefold to find an alternative solution.
5.3. Shift Types worked rules example
In the following example, Ann and Beth have a contract that limits their hours to 8 per day and two different shift types in a week. There are 11 shifts over 5 days with three different shift types. Ann and Beth are only assigned to 2 different shift types each. 2 shifts are left unassigned.

-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Contract period rules shift types example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"minutesWorkedMax": 480
},
{
"id": "Max2ShiftTypesPerWeek",
"period": "WEEK",
"satisfiability": "REQUIRED",
"shiftTypesTagCategories": ["Early", "Afternoon", "Night"],
"shiftTypesWorkedMax": 2
}
]
}
],
"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": ["Early"]
},
{
"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": ["Early"]
},
{
"id": "Tue PM",
"start": "2027-02-02T14:00:00Z",
"end": "2027-02-02T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Tue Night",
"start": "2027-02-02T22:00:00Z",
"end": "2027-02-03T06:00:00Z",
"tags": ["Night"]
},
{
"id": "Wed PM",
"start": "2027-02-03T14:00:00Z",
"end": "2027-02-03T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Wed Night",
"start": "2027-02-03T22:00:00Z",
"end": "2027-02-04T06:00:00Z",
"tags": ["Night"]
},
{
"id": "Thu AM",
"start": "2027-02-04T06:00:00Z",
"end": "2027-02-04T14:00:00Z",
"tags": ["Early"]
},
{
"id": "Thu PM",
"start": "2027-02-04T14:00:00Z",
"end": "2027-02-04T22:00:00Z",
"tags": ["Afternoon"]
},
{
"id": "Thu Night",
"start": "2027-02-04T22:00:00Z",
"end": "2027-02-05T06:00:00Z",
"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": "Contract period rules shift types example",
"submitDateTime": "2025-04-01T06:22:34.128322433Z",
"startDateTime": "2025-04-01T06:22:47.854671277Z",
"activeDateTime": "2025-04-01T06:22:48.101053008Z",
"completeDateTime": "2025-04-01T06:27:49.015637737Z",
"shutdownDateTime": "2025-04-01T06:27:49.319329116Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-2medium/0soft",
"tags": [
"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": "Tue Night",
"employee": null
},
{
"id": "Wed PM",
"employee": "Ann"
},
{
"id": "Wed Night",
"employee": "Beth"
},
{
"id": "Thu AM",
"employee": "Ann"
},
{
"id": "Thu PM",
"employee": "Beth"
},
{
"id": "Thu Night",
"employee": null
}
]
},
"inputMetrics": {
"employees": 2,
"shifts": 10,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 8,
"unassignedShifts": 2,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 8,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann and Beth assigned to only 2 shifts types.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 8,
"unassignedShifts": 2,
"activatedEmployees": 2,
"assignedMandatoryShifts": 8
}
6. Control when period rules apply
Shifts with specific tags can be included or excluded from the rules.
6.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.
6.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.
6.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.
7. Use tag types with period rules
Tags and TagTypes can be used to assign shifts to employees.
Tags can be grouped into tag types, for example, tag types could be Country
, Department
, or Employment type
.
PeriodRules
can include tag types, effectively filtering out all other tags and tag types.
In the following example, the tags Department A
and Department B
are of tag type Department
.
{
"tagTypes": [
{
"id": "Department"
}
],
"tags": [
{
"id": "Department A",
"tagType": "Department"
},
{
"id": "Department B",
"tagType": "Department"
}
]
}
PeriodRules
can include uniqueTagsLimit
to limit the number of tags that can be used to assign individual employees to shifts.
{
"id": "periodRuleContract",
"periodRules": [
{
"id": "max2DepartmentsRule",
"period": "WEEK",
"satisfiability": "REQUIRED",
"uniqueTagsLimit": {
"tagsMax": 2,
"includeTagTypes": ["Department"]
}
}
]
}
PeriodRules
must include an ID.
period
specifies the timeframe the rule applies to.
uniqueTagsLimit
must include tagsMax
and includeTagTypes
.
tagsMax
specifies the maximum number of tags that can be used to assign individual employees to shifts.
includeTagTypes
specific which tagType to include in the rule.
satisfiability
can be REQUIRED
or PREFERRED
. REQUIRED
is the default if omitted.
7.1. Required satisfiability
When the satisfiability
of the rule is REQUIRED
, the Unique tags per period not in required range for employee
hard constraint is invoked, which makes sure the number of tags used to assign shifts to individual employees does not exceed the limit specified in tagsMax
.
Shifts will be left unassigned if assigning them would break the Unique tags per period not in required range for employee
constraint.
7.2. Preferred satisfiability
When the satisfiability
of the rule is PREFERRED
, the Unique tags per period not in preferred range for employee
soft constraint is invoked.
{
"id": "periodRuleContract",
"periodRules": [
{
"id": "max2DepartmentsRule",
"period": "WEEK",
"satisfiability": "PREFERRED",
"uniqueTagsLimit": {
"tagsMax": 2,
"includeTagTypes": ["Department"]
}
}
]
}
This constraint adds a soft penalty to the run score when the number of tags used to assign shifts to an individual employee exceeds the limit set in tagsMax
, incentivizing Timefold to find an alternative solution.
7.3. Unique tags example
In the following example, there are 5 shifts which are tagged for 3 different departments.
The tagsMax
limit is set to 2, so shifts in 1 of the departments is left unassigned.

-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Contract period unique tags example"
}
},
"modelInput": {
"tagTypes": [
{
"id": "Department"
}
],
"tags": [
{
"id": "Department A",
"tagType": "Department"
},
{
"id": "Department B",
"tagType": "Department"
},
{
"id": "Department C",
"tagType": "Department"
}
],
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max2DepartmentsRule",
"period": "WEEK",
"uniqueTagsLimit": {
"tagsMax": 2,
"includeTagTypes": [
"Department"
]
}
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon AM",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Department A"]
},
{
"id": "Tue",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z",
"tags": ["Department B"]
},
{
"id": "Wed",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z",
"tags": ["Department C"]
},
{
"id": "Thu",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z",
"tags": ["Department A"]
},
{
"id": "Fri",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z",
"tags": ["Department B"]
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Contract period unique tags example",
"submitDateTime": "2025-04-02T05:01:50.72460623Z",
"startDateTime": "2025-04-02T05:02:01.381568012Z",
"activeDateTime": "2025-04-02T05:02:01.511395777Z",
"completeDateTime": "2025-04-02T05:07:02.332743181Z",
"shutdownDateTime": "2025-04-02T05:07:02.478167535Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon AM",
"employee": "Ann"
},
{
"id": "Tue",
"employee": "Ann"
},
{
"id": "Wed",
"employee": null
},
{
"id": "Thu",
"employee": "Ann"
},
{
"id": "Fri",
"employee": "Ann"
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 5,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 4,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 4,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann assigned to shifts in 2 departments.
Shifts in 1 department are left unassigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 4,
"unassignedShifts": 1,
"activatedEmployees": 1,
"assignedMandatoryShifts": 4
}
8. Combining contract rules example
Contract rules provide the most control over the schedules when they are used in combination with each other.
For instance, in the consecutive days work rule example, Beth worked 3 consecutive twelve-hour shifts. If we don’t also define a period rule for the number of hours per week, Beth could be assigned a twelve-hour shift on Monday, Tuesday, and Wednesday, have no shift assigned on Thursday, and then be assign three more consecutive shifts on Friday, Saturday, and Sunday. If the intention is for Beth to work 3 consecutive shifts per week, we also need to include a period rule to prevent more shifts being assigned after those 3 days and the following day.
The following example defines a contract with the following rules:
-
Employees work a maximum of 8 hours per day.
-
Employees work a maximum of 40 hours per week.
-
Employees work a maximum of 5 consecutive days.
-
Employees have 12 hours between shifts.
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Contract shift times example",
"tags": []
}
},
"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
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T01:00:00Z",
"end": "2027-02-01T09:00:00Z"
},
{
"id": "Mon day",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Tue night",
"start": "2027-02-02T01:00:00Z",
"end": "2027-02-02T09:00:00Z"
},
{
"id": "Tue day",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "Wed night",
"start": "2027-02-03T01:00:00Z",
"end": "2027-02-03T09:00:00Z"
},
{
"id": "Wed day",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "Thu night",
"start": "2027-02-04T01:00:00Z",
"end": "2027-02-04T09:00:00Z"
},
{
"id": "Thu day",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z"
},
{
"id": "Fri night",
"start": "2027-02-05T01:00:00Z",
"end": "2027-02-05T09:00:00Z"
},
{
"id": "Fri day",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z"
},
{
"id": "Sat night",
"start": "2027-02-06T01:00:00Z",
"end": "2027-02-06T09:00:00Z"
},
{
"id": "Sat day",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Contract shift times example",
"submitDateTime": "2024-10-21T10:05:02.930407315Z",
"startDateTime": "2024-10-21T10:05:07.964859924Z",
"activeDateTime": "2024-10-21T10:05:08.064859924Z",
"completeDateTime": "2024-10-21T10:10:08.215530452Z",
"shutdownDateTime": "2024-10-21T10:10:08.315530452Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-7medium/0soft",
"tags": [],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon night",
"employee": "Ann"
},
{
"id": "Mon day",
"employee": null
},
{
"id": "Tue night",
"employee": "Ann"
},
{
"id": "Tue day",
"employee": null
},
{
"id": "Wed night",
"employee": "Ann"
},
{
"id": "Wed day",
"employee": null
},
{
"id": "Thu night",
"employee": "Ann"
},
{
"id": "Thu day",
"employee": null
},
{
"id": "Fri night",
"employee": "Ann"
},
{
"id": "Fri day",
"employee": null
},
{
"id": "Sat night",
"employee": null
},
{
"id": "Sat day",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 7,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule which conforms with the contract rules in the modelInput.
Next
-
Understand the constraints of the Employee Shift Scheduling model.
-
See the full API spec or try the online API.
-
Manage schedules with Time zones and Daylight Saving Time (DST) changes.
-
Working with Employee availability.