Shift sequence patterns: daily shift pairings
Daily shift pairing rules are used to pair shifts together.
For instance, when employees are required to work weekends, there could be a preference that they work both Saturday and Sunday, but that they don’t work two consecutive weekends.
Shift pairing rules can also be used to prohibit certain shift pairings, for instance, an employee shouldn’t work a late shift followed by an early shift the following day.
This guide explains how to manage daily shift pairing rules with the following examples:
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. Daily shift pairing rules
Daily shift pairing rules are configured in contracts:
{
"contracts": [
{
"id": "fullTimeContract",
"dailyShiftPairingRules": [
{
"id": "sundayAndSaturdayShift",
"shiftTags": [
"Saturday"
],
"pairedShiftTags": [
"Sunday"
],
"satisfiability": "PREFERRED",
"shiftTagMatches": "ALL",
"dayOffset": 1
}
]
}
]
}
dailyShiftPairingRules
must include an ID.
shiftTags
must include a tag that is associated with the first shift that will be paired.
pairedShiftTags
must include a tag that is associated with the second shift that will be paired.
shiftTagMatches
can be set to ALL
or ANY
.
The default behavior for shiftTagMatches
is ALL
, and if omitted, the default ALL
will be used.
dayOffSet
is the difference in days between start day of the shift matched by shiftTags
and the start day of the shift matched by pairedShiftTags
.
dayOffSet
can be both positive or negative.
The default value is 0
.
When pairing shifts on subsequent days, set dayOffSet
to 1
.
Shifts must include tags:
{
"id": "Mon 0300",
"start": "2027-02-01T03:00:00Z",
"end": "2027-02-01T07:00:00Z",
"tags": ["Early"]
}
satisfiability
of the pattern can be PREFERRED
, PROHIBITED
, or UNPREFERRED
.
1.1. Preferred satisfiability
When the satisfiability
of the rule is PREFERRED
, the Employee does not have preferred daily shift pairing
soft constraint is invoked, which adds a soft penalty to the run score when an employee is to a shift that matches the shiftTags
but is not assigned to a shift that matches the corresponding pairShiftTags
, incentivizing Timefold to use solutions with the best score.
In the following example, there are 4 shifts and 2 employees.
There is a single dailyShiftPairingRules
that specifies that shifts tagged Saturday
should be paired with shifts tagged Sunday
.
A global rule makes sure the shifts are assigned fairly. See the fairness guide for more details about fairness.
Ann is assigned the shifts tagged Thursday
and Friday
, and Beth is assigned the shifts tagged Saturday
and Sunday
.

-
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": "Daily shift pairing rules - preferred"
}
},
"modelInput": {
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime"
}
]
},
"contracts": [
{
"id": "fullTimeContract",
"dailyShiftPairingRules": [
{
"id": "sundayAndSaturdayShift",
"shiftTags": [
"Saturday"
],
"pairedShiftTags": [
"Sunday"
],
"satisfiability": "PREFERRED",
"shiftTagMatches": "ALL",
"dayOffset": 1
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
},
{
"id": "Beth",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Thu",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z",
"tags": ["Thursday"]
},
{
"id": "Fri",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z",
"tags": ["Friday"]
},
{
"id": "Sat",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17:00:00Z",
"tags": ["Saturday"]
},
{
"id": "Sun",
"start": "2027-02-07T09:00:00Z",
"end": "2027-02-07T17:00:00Z",
"tags": ["Sunday"]
}
]
}
}
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": "Daily shift pairing rules - preferred",
"submitDateTime": "2025-04-15T05:17:41.594630886Z",
"startDateTime": "2025-04-15T05:17:58.30896309Z",
"activeDateTime": "2025-04-15T05:17:58.514501331Z",
"completeDateTime": "2025-04-15T05:18:29.561996949Z",
"shutdownDateTime": "2025-04-15T05:18:29.905517566Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Thu",
"employee": "Ann"
},
{
"id": "Fri",
"employee": "Ann"
},
{
"id": "Sat",
"employee": "Beth"
},
{
"id": "Sun",
"employee": "Beth"
}
]
},
"inputMetrics": {
"employees": 2,
"shifts": 4,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 4,
"unassignedShifts": 0,
"assignedShiftGroups": 0,
"unassignedShiftGroups": 0,
"workingTimeFairnessPercentage": 100.0,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 4,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann assigned the shifts tagged Thursday
and Friday
and Beth assigned the shifts tagged Saturday
and Sunday
.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 4,
"workingTimeFairnessPercentage": 100.0,
"activatedEmployees": 2,
"assignedMandatoryShifts": 4
}
1.2. Unpreferred satisfiability
When the satisfiability
of the rule is UNPREFERRED
, the Employee has unpreferred daily shift pairing
soft constraint is invoked.
{
"dailyShiftPairingRules": [
{
"id": "noConsecutiveWeekends",
"shiftTags": [
"Weekend"
],
"pairedShiftTags": [
"Weekend"
],
"satisfiability": "UNPREFERRED",
"shiftTagMatches": "ALL",
"dayOffset": 7
}
]
}
This constraint adds a soft penalty to the run score when an employee is assigned a shift that matches the rules’s shiftTags
and they are assigned to a shift that matches the rule’s pairedShiftTags
on a day defined by the rule’s dayOffset
attribute.
The soft score penalty incentivizes Timefold to find an alternative solution.
In the following example, there are 4 weekend shifts over 2 weeks and only 1 employee.
There is a single dailyShiftPairingRules
that specifies that shifts tagged Weekend
that occur 7 days apart should preferrably not be assigned to the same employee.
Ann is assigned all four shifts and the solution is penalized with a negative soft 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": "Daily shift pairing rules - unpreferred"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"dailyShiftPairingRules": [
{
"id": "noConsecutiveWeekends",
"shiftTags": [
"Weekend"
],
"pairedShiftTags": [
"Weekend"
],
"satisfiability": "UNPREFERRED",
"shiftTagMatches": "ALL",
"dayOffset": 7
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Sat 1",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17:00:00Z",
"tags": ["Weekend"]
},
{
"id": "Sun 1",
"start": "2027-02-07T09:00:00Z",
"end": "2027-02-07T17:00:00Z",
"tags": ["Weekend"]
},
{
"id": "Sat 2",
"start": "2027-02-13T09:00:00Z",
"end": "2027-02-13T17:00:00Z",
"tags": ["Weekend"]
},
{
"id": "Sun 2",
"start": "2027-02-14T09:00:00Z",
"end": "2027-02-14T17:00:00Z",
"tags": ["Weekend"]
}
]
}
}
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": "Daily shift pairing rules - unpreferred",
"submitDateTime": "2025-04-15T06:19:36.737780839Z",
"startDateTime": "2025-04-15T06:19:48.000200061Z",
"activeDateTime": "2025-04-15T06:19:48.179594336Z",
"completeDateTime": "2025-04-15T06:20:19.1089142Z",
"shutdownDateTime": "2025-04-15T06:20:19.369215721Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-1920soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Sat 1",
"employee": "Ann"
},
{
"id": "Sun 1",
"employee": "Ann"
},
{
"id": "Sat 2",
"employee": "Ann"
},
{
"id": "Sun 2",
"employee": "Ann"
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 4,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 4,
"unassignedShifts": 0,
"assignedShiftGroups": 0,
"unassignedShiftGroups": 0,
"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 all 4 weekend shifts.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 4,
"activatedEmployees": 1,
"assignedMandatoryShifts": 4
}
1.3. Prohibited satisfiability
When the satisfiability
of the rule is PROHIBITED
, the Employee has prohibited daily shift pairing
hard constraint is invoked.
{
"contracts": [
{
"id": "fullTimeContract",
"dailyShiftPairingRules": [
{
"id": "noEarlyShiftAfterLate",
"shiftTags": [
"Late"
],
"pairedShiftTags": [
"Early"
],
"satisfiability": "PROHIBITED",
"shiftTagMatches": "ALL",
"dayOffset": 1
}
]
}
]
}
Shifts will be left unassigned if assigning them would break Employee has prohibited daily shift pairing
constraint.
In the following example, there is one dailyShiftPairingRules
:
Shifts tagged "Late" and "Early" are prohibited from being assigned together to individual employees when the "Early" shift is the day after the "Late" shift.
Ann is assigned the shift tagged "Late", but the shift tagged "Early" is left unassigned to avoid breaking the hard constraint.

-
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": "Daily shift pairing rules - prohibited"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"dailyShiftPairingRules": [
{
"id": "noEarlyShiftAfterLate",
"shiftTags": [
"Late"
],
"pairedShiftTags": [
"Early"
],
"satisfiability": "PROHIBITED",
"shiftTagMatches": "ALL",
"dayOffset": 1
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon Late",
"start": "2027-02-01T16:00:00Z",
"end": "2027-02-02T00:00:00Z",
"tags": ["Late"]
},
{
"id": "Tue Early",
"start": "2027-02-02T00:00:00Z",
"end": "2027-02-02T08:00:00Z",
"tags": ["Early"]
}
]
}
}
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": "Daily shift pairing rules - prohibited",
"submitDateTime": "2025-04-15T08:03:38.67979918Z",
"startDateTime": "2025-04-15T08:03:49.9208183Z",
"activeDateTime": "2025-04-15T08:03:50.015353229Z",
"completeDateTime": "2025-04-15T08:04:20.916004482Z",
"shutdownDateTime": "2025-04-15T08:04:21.106144893Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon Late",
"employee": "Ann"
},
{
"id": "Tue Early",
"employee": null
}
]
},
"inputMetrics": {
"employees": 1,
"shifts": 2,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 1,
"unassignedShifts": 1,
"assignedShiftGroups": 0,
"unassignedShiftGroups": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 1,
"assignedMandatoryShifts": 1,
"assignedOptionalShifts": 0,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann assigned to the shift tagged "Late" and the shift tagged "Early" left unassigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 1,
"unassignedShifts": 1,
"activatedEmployees": 1,
"assignedMandatoryShifts": 1
}
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.