Days off per period
There are different techniques for managing employees' time off.
This guide shows you how to include days off per period. Days off per period can be specified per week, month, or the entire schedule.
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.
1. Define days off per period
Period rules are configured in contracts:
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "min2DaysOff",
"period": "WEEK",
"daysOffLimit":
{
"daysOffMin": 2
},
"satisfiability": "REQUIRED"
}
]
}
]
}
PeriodRules
must include an ID.
period
sets the period the rule applies to, for instance, DAY
, WEEK
, MONTH
, SCHEDULE
.
Further information about period
:
DAY
spans a single day and occurs every day in the schedule.
WEEK
spans 7 days and occurs every week (including partial weeks) in the schedule.
The default start of the week is Monday, but this can be overridden to any day in the week:
{
"scheduleParameterization": {
"weekStart": "THURSDAY"
}
}
MONTH
spans the entire month.
MONTH
has a variable number of days depending on the days in the month and occurs every month (including partial months) in the schedule.
SCHEDULE
spans the entire schedule.
You can define custom periods to apply to this rule in the following way: |
{
"scheduleParameterization": {
"periods": [
{
"id": "PAY_PERIOD",
"dateSpans": [
{
"start": "2023-01-01",
"end": "2023-01-15"
}
]
}
]
}
}
The start
and end
dates are inclusive.
The daysOffLimit
object must include daysOffMin
with the minimum number of days off.
You can optionally use includeDays
to specify the days of the week that count toward the total.
If includeDays
is not included, all days count toward the total.
To create a rule that only counts weekdays and excludes weekends:
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "min2DaysOff",
"period": "WEEK",
"daysOffLimit":
{
"daysOffMin": 2,
"includeDays": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"]
},
"satisfiability": "REQUIRED"
}
]
}
]
}
Each employee must specify which contracts apply to them:
{
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
]
}
The satisfiability
of the rule can be REQUIRED
or PREFERRED
.
If omitted, REQUIRED
is the default.
2. Required days off per period
When the satisfiability of the rule is REQUIRED
, the Days off per period not in required range for employee
hard constraint is invoked, which makes sure the number of days off in the period is not below the limit specified in daysOffMin
.
Shifts will be left unassigned if assigning them would break the Days off per period not in required range for employee
constraint.
In the following example, there are 7 shifts, but Ann’s contract has a daysOffMin
of 2.
She is assigned 5 shifts, 2 shifts are left unassigned, and Ann has 2 days 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": "Required days off per period example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "min2DaysOff",
"period": "WEEK",
"daysOffLimit":
{
"daysOffMin": 2
},
"satisfiability": "REQUIRED"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"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"
},
{
"id": "Sat",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17:00:00Z"
},
{
"id": "Sun",
"start": "2027-02-07T09:00:00Z",
"end": "2027-02-07T17: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": "Required days off per period example",
"submitDateTime": "2025-05-15T04:12:48.841418699Z",
"startDateTime": "2025-05-15T04:13:06.647742868Z",
"activeDateTime": "2025-05-15T04:13:06.879503897Z",
"completeDateTime": "2025-05-15T04:13:37.675762988Z",
"shutdownDateTime": "2025-05-15T04:13:37.960206521Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-2medium/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": "Ann"
},
{
"id": "Sat",
"employee": null
},
{
"id": "Sun",
"employee": null
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 7,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 2,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 5,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann not being assigned shifts on 2 days of the week.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 5,
"unassignedShifts": 2,
"activatedEmployees": 1,
"assignedMandatoryShifts": 5
}
3. Preferred days off per period
When the satisfiability of the rule is PREFERRED
, the Days off per period not in preferred range for employee
soft constraint is invoked.
With PREFERRED
satisfiability you can also use daysOffMax
to specify the maximum number of days off.
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "min2DaysOff",
"period": "WEEK",
"daysOffLimit":
{
"daysOffMin": 1,
"daysOffMax": 3
},
"satisfiability": "PREFERRED"
}
]
}
]
}
This constraint adds a soft penalty if the number of days off is below the limit set in daysoffMin
or above the limit set in daysOffMax
.
Timefold is incentivized to use the solution with the best score.
In the following example, there are 7 shifts, but Ann’s contract has a daysOffMin
of 1 and a daysOffMax
of 3.
She is assigned all 7 shifts, and a soft penalty is applied to the run score.

-
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 days off per period example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "min2DaysOff",
"period": "WEEK",
"daysOffLimit":
{
"daysOffMin": 1,
"daysOffMax": 3
},
"satisfiability": "PREFERRED"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"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"
},
{
"id": "Sat",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17:00:00Z"
},
{
"id": "Sun",
"start": "2027-02-07T09:00:00Z",
"end": "2027-02-07T17: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": "Preferred days off per period example",
"submitDateTime": "2025-05-15T09:43:13.255755752Z",
"startDateTime": "2025-05-15T09:43:25.755410242Z",
"activeDateTime": "2025-05-15T09:43:25.932572105Z",
"completeDateTime": "2025-05-15T09:43:56.770085733Z",
"shutdownDateTime": "2025-05-15T09:43:57.086570792Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-960soft",
"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": "Ann"
},
{
"id": "Sat",
"employee": "Ann"
},
{
"id": "Sun",
"employee": "Ann"
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 7,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 7,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 7,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with all 7 shifts assigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 7,
"activatedEmployees": 1,
"assignedMandatoryShifts": 7
}
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 managing employees' Time off.