Shift assignments
Managing shift assignments is a complex task.
Ensuring the correct number of shifts are assigned to cover the workload can include scheduling enough shifts during a specific period and keeping the number of assigned shifts within a specific range. For instance, this could be used to limit the number of contractors who are assigned shifts on the same days.
When limited resources need to be factored into how many shifts can be assigned concurrently, for instance, in a warehouse if employees assigned order picking shifts require the use of a fork lift truck and there are only 2 fork lift trucks, it makes sense to limit the number of concurrent order picking shifts to 2.
There are also times when specific employees are preferred for specific shifts, while other employees might be unpreferred or prohibuted from being assigned specific shifts.
This guide explains how to manage shift assignments 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. Shifts worked per period
Rules to set a preferred number of shifts to be worked in a specific period are defined as global rules:
{
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "max2ShiftsPerDay",
"period": "DAY",
"satisfiability": "PREFERRED",
"shiftsWorkedMax": 2
}
]
}
}
minimumMaximumShiftsPerPeriod
must include an ID.
period
sets the period the rule applies to, for instance, DAY
, WEEK
, MONTH
, SCHEDULE
.
shiftsWorkedMax
sets the maximum number of shifts that can be worked in the specified period
.
satisfiability
can be REQUIRED
or PREFERRED
.
1.1. Required satisfiability
When the satisfiability
of the rule is REQUIRED
, the Shifts worked per period not in required range
hard constraint is invoked, which makes sure no more shifts are scheduled for the period than the limit specified in shiftsWorkedMax
.
Shifts will be left unassigned if assigning them would break the Shifts worked per period not in required range
constraint.
In the following example, there are 3 shifts that could be assigned and 3 employees who could take the shifts, however, the minimumMaximumShiftsPerPeriod
rule states a maximum of 2 shifts can be assigned.
1 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": "Shifts worked per period - required"
}
},
"modelInput": {
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "max2ShiftsPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"shiftsWorkedMax": 2
}
]
},
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 3",
"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": "Shifts worked per period - required",
"submitDateTime": "2025-04-24T05:19:36.331833692Z",
"startDateTime": "2025-04-24T05:19:48.545373777Z",
"activeDateTime": "2025-04-24T05:19:48.733080296Z",
"completeDateTime": "2025-04-24T05:20:19.614737754Z",
"shutdownDateTime": "2025-04-24T05:20:19.981922025Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": null
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with the maximum number of 2 shifts assigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 2,
"unassignedShifts": 1,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
}
1.2. Preferred satisfiability
With a satisfiability of PREFERRED
a shiftsWorkedMin
value can also be provided to set the minimum number of shifts to be assigned during the period.
When the satisfiability
of the rule is PREFERRED
, the Shifts worked per period not in preferred range
soft constraint is invoked, which adds a soft penalty to the run score when the number of assigned shifts is below the limit set in shiftsWorkedMin
or above the limit set in shiftsWorkedMax
.
Timefold is incentivized to use solutions with the best score.
{
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "min1ShiftMax2ShiftsPerDay",
"period": "DAY",
"satisfiability": "PREFERRED",
"shiftsWorkedMin": 1,
"shiftsWorkedMax": 2
}
]
}
}
In the following example, there are 3 shifts available. All 3 shifts are assigned, and a soft penalty is added 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": "Shifts worked per period - preferred"
}
},
"modelInput": {
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "min1ShiftMax2ShiftsPerDay",
"period": "DAY",
"satisfiability": "PREFERRED",
"shiftsWorkedMin": 1,
"shiftsWorkedMax": 2
}
]
},
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 3",
"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": "Shifts worked per period - preferred",
"submitDateTime": "2025-04-24T06:03:46.727458076Z",
"startDateTime": "2025-04-24T06:03:59.351172697Z",
"activeDateTime": "2025-04-24T06:03:59.581194734Z",
"completeDateTime": "2025-04-24T06:04:30.925369499Z",
"shutdownDateTime": "2025-04-24T06:04:31.177456774Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-480soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": "Carl"
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 3,
"assignedMandatoryShifts": 3,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with all 3 shifts assigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 3,
"activatedEmployees": 3,
"assignedMandatoryShifts": 3
}
1.3. Limit shifts worked by employee type
Rules to set a preferred number of shifts to be worked in a specific period can be limited to apply to only certain employee types by using includeEmployeeTags
and specifying the employee tags to include:
{
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "max1ContractorShiftPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"shiftsWorkedMax": 1,
"includeEmployeeTags": [
"Contractor"
]
}
]
}
}
Employees must be tagged:
{
"id": "Beth",
"tags": [ "Contractor" ]
}
In the following example, there are 3 shifts and 3 employees, however, there is a rule that specifies only 1 employee tagged with Contractor
can be assigned a shift during the period.
2 of the employees are contractors. 1 shift is assigned to a non-contractor employee and 1 shift is assigned to a contractor. 1 shift 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": "Shifts worked per period by employee type"
}
},
"modelInput": {
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "max1ContractorShiftPerDay",
"period": "DAY",
"satisfiability": "REQUIRED",
"shiftsWorkedMax": 1,
"includeEmployeeTags": [
"Contractor"
]
}
]
},
"employees": [
{
"id": "Ann"
},
{
"id": "Beth",
"tags": [ "Contractor" ]
},
{
"id": "Carl",
"tags": [ "Contractor" ]
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 3",
"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": "Shifts worked per period by employee type",
"submitDateTime": "2025-04-24T06:20:01.157549149Z",
"startDateTime": "2025-04-24T06:20:13.410510105Z",
"activeDateTime": "2025-04-24T06:20:13.564903212Z",
"completeDateTime": "2025-04-24T06:20:44.383732264Z",
"shutdownDateTime": "2025-04-24T06:20:44.684966153Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": null
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with 2 shifts assigned and 1 shift left unassigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 2,
"unassignedShifts": 1,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
}
1.4. Limit shifts worked by shift type
Rules to set a preferred number of shifts to be worked in a specific period can be limited to apply to only certain shift types by using includeShiftTags
and specifying shift tags to be included:
{
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "max1ReliefShift",
"period": "DAY",
"satisfiability": "REQUIRED",
"shiftsWorkedMax": 1,
"includeShiftTags": ["Relief"],
"shiftTagMatches": "ALL"
}
]
}
}
Shifts must be tagged:
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Relief"]
}
In the following example, there are 3 shifts and 3 employees, however, there is a rule that specifies only 1 shift tagged with Relief
can be assigned during the period.
2 of the 3 shifts are tagged Relief
.
The shift that is not tagged Relief
and one of the shifts that is tagged Relief
are assigned.
1 of the Relief
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": "Shifts worked per period by shift type"
}
},
"modelInput": {
"globalRules": {
"minimumMaximumShiftsPerPeriod": [
{
"id": "max1ReliefShift",
"period": "DAY",
"satisfiability": "REQUIRED",
"shiftsWorkedMax": 1,
"includeShiftTags": ["Relief"],
"shiftTagMatches": "ALL"
}
]
},
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Relief"]
},
{
"id": "Mon 3",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Relief"]
}
]
}
}
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": "Shifts worked per period by shift type",
"submitDateTime": "2025-04-30T04:21:02.30985523Z",
"startDateTime": "2025-04-30T04:21:19.162387598Z",
"activeDateTime": "2025-04-30T04:21:19.333522963Z",
"completeDateTime": null,
"shutdownDateTime": null,
"solverStatus": "SOLVING_ACTIVE",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": null
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with 2 shifts assigned and 1 shift left unassigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 2,
"unassignedShifts": 1,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
}
2. Concurrent shift rules
Rules to set the number of concurrent shifts to be worked in a specific period are defined as global rules:
{
"globalRules": {
"concurrentShiftsRules": [
{
"id": "max2OrderPickingShifts",
"includeShiftTags": ["Fork lift"],
"shiftTagMatches": "ALL",
"concurrentShiftsMax": 2
}
]
}
}
concurrentShiftsRules
must include an ID.
concurrentShiftsMax
sets the limit for the maximum number of concurrent shifts.
includeShiftTags
specifies which shifts to include in the rule based on the specified tags.
Alternatively, excludeShiftTags
can be used to specify which shifts to exclude in the rule based on the specified shift tags.
Tags are defined in shifts:
{
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Fork lift"]
}
]
}
shiftTagMatches
can be set to ALL
or ANY
.
The default behavior for shiftTagMatches
is ALL
, and if omitted, the default ALL
will be used.
When shiftTagMatches
is set to ALL
and used with includeShiftTags
, 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.
When shiftTagMatches
is set to ALL
and used with excludeShiftTags
, 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
would 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.
|
The Concurrent shifts worked not in required range
hard constraint makes sure no more concurrent shifts are scheduled the limit specified in concurrentShiftsMax
.
Shifts will be left unassigned if assigning them would break the Concurrent shifts worked not in required range
constraint.
In the following example, there are 3 shifts and 3 employees, but the concurrentShiftsRules
specifies only 2 shifts tagged with Fork lift
can be assigned concurrently.
1 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": "Concurrent shifts"
}
},
"modelInput": {
"globalRules": {
"concurrentShiftsRules": [
{
"id": "max2OrderPickingShifts",
"includeShiftTags": ["Fork lift"],
"shiftTagMatches": "ALL",
"concurrentShiftsMax": 2,
"ruleValidityDateTimeSpan": {
"start": "2027-02-01T00:00:00",
"end": "2027-02-02T00:00:00"
}
}
]
},
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Fork lift"]
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Fork lift"]
},
{
"id": "Mon 3",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": ["Fork lift"]
}
]
}
}
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": "Concurrent shifts",
"submitDateTime": "2025-04-30T05:01:54.341228273Z",
"startDateTime": "2025-04-30T05:02:05.982084163Z",
"activeDateTime": "2025-04-30T05:02:06.134227222Z",
"completeDateTime": "2025-04-30T05:07:06.984436139Z",
"shutdownDateTime": "2025-04-30T05:07:07.274253679Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": null
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with 2 shifts assigned and 1 shift unassigned.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
"assignedShifts": 2,
"unassignedShifts": 1,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
2.1. Validity time span
A time span that controls when the rule is applied can be configured by adding ruleValidityDateTimeSpan
with start
and end
times to the concurrentShiftsRules
.
If not provided, the rule is always valid in the period configured.
{
"globalRules": {
"concurrentShiftsRules": [
{
"id": "max2OrderPickingShifts",
"includeShiftTags": ["Fork lift"],
"shiftTagMatches": "ALL",
"concurrentShiftsMax": 2,
"ruleValidityDateTimeSpan": {
"start": "2027-02-01T00:00:00",
"end": "2027-02-08T00:00:00"
}
}
]
}
}
3. Preferred Employee
When specific employees are preferred for a specific shift, this preference is added to the shift’s preferredEmployees
:
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"preferredEmployees": ["Ann"]
}
The Preferred employee assigned
soft constraint is invoked when a preferred employee is assigned to a shift, adding a soft reward to the run score.
If a preferred employee cannot be assigned, the shift will be assigned to another employee.
In the following example, Ann is the preferred employee for 2 shifts. She is assigned 1 of the shifts where she is preferred employee and a soft reward is added to the run score. The other shifts are assigned to other employees.

-
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 employee"
}
},
"modelInput": {
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"preferredEmployees": ["Ann"]
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"preferredEmployees": ["Ann"]
},
{
"id": "Mon 3",
"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": "Preferred employee",
"submitDateTime": "2025-04-30T06:16:28.418040846Z",
"startDateTime": "2025-04-30T06:16:39.981929771Z",
"activeDateTime": "2025-04-30T06:16:40.143585558Z",
"completeDateTime": "2025-04-30T06:21:41.061626428Z",
"shutdownDateTime": "2025-04-30T06:21:41.330402481Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/960soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": "Carl"
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 3,
"assignedMandatoryShifts": 3,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Ann is assigned 1 of the shifts where she is a preferred employee.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 2,
"unassignedShifts": 1,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
}
4. Unpreferred Employee
When specific employees are not preferred for a specific shift, this preference is added to the shift’s unpreferredEmployees
:
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"unpreferredEmployees": ["Beth"]
}
The Unpreferred employee assigned
soft constraint is invoked when an unpreferred employee is assigned to a shift, adding a soft penalty to the run score.
Timefold is incentivized to use solutions with the best score.
In the following example, Beth is an unpreferred employee for all three shifts. Beth is assigned one of the shifts and a soft penalty is added 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": "Unpreferred employee"
}
},
"modelInput": {
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"unpreferredEmployees": ["Beth"]
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"unpreferredEmployees": ["Beth"]
},
{
"id": "Mon 3",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"unpreferredEmployees": ["Beth"]
}
]
}
}
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": "Unpreferred employee",
"submitDateTime": "2025-04-30T06:50:59.215676663Z",
"startDateTime": "2025-04-30T06:51:16.863976097Z",
"activeDateTime": "2025-04-30T06:51:17.011331875Z",
"completeDateTime": "2025-04-30T06:56:17.833607342Z",
"shutdownDateTime": "2025-04-30T06:56:18.091323158Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-960soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Carl"
},
{
"id": "Mon 3",
"employee": "Beth"
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 3,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 3,
"assignedMandatoryShifts": 3,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Beth assigned to a shift, even though she is an unpreferred employee for that shift.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 3,
"activatedEmployees": 3,
"assignedMandatoryShifts": 3
}
5. Prohibited Employee
When specific employees are prohibited for a specific shift, this preference is added to the shift’s prohibitedEmployees
:
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"prohibitedEmployees": ["Carl"]
}
The Prohibited employee assigned
hard constraint is invoked, which makes sure employees are not assigned to shifts where they are listed in prohibitedEmployee
.
Shifts will be left unassigned if only a prohibited employee is available.
In the following example, there are 3 employees and 3 shifts. Carl is listed as a prohibited employee on all 3 shifts and is not assigned a 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": "Prohibited employee"
}
},
"modelInput": {
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "Mon 1",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"prohibitedEmployees": ["Carl"]
},
{
"id": "Mon 2",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"prohibitedEmployees": ["Carl"]
},
{
"id": "Mon 3",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"prohibitedEmployees": ["Carl"]
}
]
}
}
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": "Prohibited employee",
"submitDateTime": "2025-04-30T07:23:07.956004888Z",
"startDateTime": "2025-04-30T07:23:19.755127437Z",
"activeDateTime": "2025-04-30T07:23:19.948102534Z",
"completeDateTime": "2025-04-30T07:28:20.858819064Z",
"shutdownDateTime": "2025-04-30T07:28:21.169653541Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-1medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon 1",
"employee": "Ann"
},
{
"id": "Mon 2",
"employee": "Beth"
},
{
"id": "Mon 3",
"employee": null
}
]
},
"inputMetrics": {
"employees": 3,
"shifts": 3,
"pinnedShifts": 0
},
"kpis": {
"assignedShifts": 2,
"unassignedShifts": 1,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2,
"assignedOptionalShifts": 0,
"assignedShiftGroups": null,
"unassignedShiftGroups": null,
"travelDistance": 0
}
}
modelOutput
contains the schedule with Carl not assigned to any shifts.
inputMetrics
provides a breakdown of the inputs in the input dataset.
KPIs
provides the KPIs for the output including:
{
"assignedShifts": 2,
"unassignedShifts": 1,
"activatedEmployees": 2,
"assignedMandatoryShifts": 2
}
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.
-
Manage Shift sequence patterns and Employee contracts.