Recommendations
Managing complex schedules for a large number of employees is a difficult task, and when an employee is suddenly unavailable to cover a shift they’ve been scheduled for, it can be difficult to know who to schedule in their place.
Timefold’s recommendations API can provide recommendations ordered by the best fit to cover a shift at the last minute.
This guide explains recommendations 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. Recommendations for a shift
In the following example, Ann and Beth have been assigned shifts, but Ann has called in sick for her shift.
Timefold’s recommendations API can provide recommendations for the best employee to assign to Ann’s shift.
1.1. The recommendations input dataset
A recommendation input dataset includes the following:
1.1.1. Max number of recommendations
The maximum number of employees to recommend for a shift:
{
"maxNumberOfRecommendations": 2
}
1.1.2. Fit shift ID
The ID of the shift the recommended employee will be assigned.
{
"fitShiftId": "2027-02-01"
}
1.1.3. Model input
The model input must include the employees and the shifts.
{
"modelInput": {
"employees": [
{
"id": "Ann",
"unavailableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z"
}
],
"availableTimeSpans": [
{
"start": "2027-02-02T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Beth",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Carl",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Dan",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Elsa",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Flo",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"employee": "Beth"
}
]
}
}
Ann is unavailable for her shift, so an unavailability time span has been added for Ann:
{
"id": "Ann",
"unavailableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z"
}
],
"availableTimeSpans": [
{
"start": "2027-02-02T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
}
Learn more about Employee availability.
Any shifts that are already assigned must include the employee ID of the employee they are assigned to.
Shifts that have been dropped by an employee do not include an employee ID.
In this case, shift 2027-02-01-a
was assigned to Ann, but Ann has called in sick, so no employee ID is included.
{
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"employee": "Beth"
}
]
}
1.2. Submit the input dataset
Submit the recommendations input dataset to the API endpoint: /v1/schedules/recommendations/recommend-employees
.
If you want individual constraint match justifications included in the response, you can specify an optional boolean includeJustifications
query parameter:
/v1/schedules/recommendations/recommend-employees?includeJustifications=true
.
The HTTP response provides the recommendations.
-
Input
-
Output (without justifications)
-
Output (with justifications)
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
To get recommendations without constraint match justifications (default):
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/recommendations/recommend-employees [email protected]
To get recommendations with constraint match justifications:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/recommendations/recommend-employees?includeJustifications=true [email protected]
{
"maxNumberOfRecommendations": 2,
"fitShiftId": "2027-02-01-a",
"modelInput": {
"employees": [
{
"id": "Ann",
"unavailableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z"
}
],
"availableTimeSpans": [
{
"start": "2027-02-02T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Beth",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Carl",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Dan",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Elsa",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Flo",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"employee": "Beth"
}
]
}
}
{
"recommendations": [
{
"employeeId": "Carl",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
]
},
{
"employeeId": "Dan",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
]
}
]
}
modelOutput
contains two recommendations to cover shift 2027-02-01-a
.
{
"recommendations": [
{
"employeeId": "Carl",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchesDiff": [
{
"score": "0hard/1medium/0soft",
"justification": {
"shift": "2027-02-01-a",
"description": "Mandatory shift '2027-02-01-a' at '2027-02-01T09:00Z - 2027-02-01T17:00Z' is unassigned."
}
}
],
"matchCountDiff": -1
}
]
},
{
"employeeId": "Dan",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchesDiff": [
{
"score": "0hard/1medium/0soft",
"justification": {
"shift": "2027-02-01-a",
"description": "Mandatory shift '2027-02-01-a' at '2027-02-01T09:00Z - 2027-02-01T17:00Z' is unassigned."
}
}
],
"matchCountDiff": -1
}
]
}
]
}
modelOutput
contains two recommendations to cover shift 2027-02-01-a
including constraint match justifications.
1.3. The recommendations
There are two recommendations for this input dataset: Dan and Carl.

Each recommendation includes the following:
-
The employee: The employee being recommended.
-
The scoreDiffs: The overall score difference implementing the recommendation will have.
-
The constraintDiffs: The score difference to individual constraints.
1.3.1. Constraint scores without justifications
The score for each constraint that has changed, for instance:
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
Which indicates the medium score has improved by 1, because the shift is no longer unassigned.
1.3.2. Constraint scores with justifications
The score and justifications for each constraint that has changed, for instance:
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchesDiff": [
{
"score": "0hard/1medium/0soft",
"justification": {
"shift": "2027-02-01-a",
"description": "Mandatory shift '2027-02-01-a' at '2027-02-01T09:00Z - 2027-02-01T17:00Z' is unassigned."
}
}
],
"matchCountDiff": -1
}
2. Recommendations and employee skills
In the previous example, any one of the available employees could have been assigned to the shift Ann dropped.
However, employees generally have different skills and availability which could help determine which employee is considered the best fit.
In the following example, Ann is a shift supervisor and the shift that needs to be filled must be filled by another shift supervisor.
-
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/recommendations/recommend-employees [email protected]
{
"maxNumberOfRecommendations": 2,
"fitShiftId": "2027-02-01-a",
"modelInput": {
"employees": [
{
"id": "Ann",
"skills": [
{
"id": "supervisor"
}
],
"unavailableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z"
}
],
"availableTimeSpans": [
{
"start": "2027-02-02T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Beth",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Carl",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Dan",
"skills": [
{
"id": "supervisor"
}
],
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Elsa",
"skills": [
{
"id": "supervisor"
}
],
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Flo",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"requiredSkills": [
"supervisor"
]
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"employee": "Beth"
}
]
}
}
{
"recommendations": [
{
"employeeId": "Dan",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
]
},
{
"employeeId": "Elsa",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
]
}
]
}
modelOutput
contains two recommendations to cover shift 2027-02-01-a
.
This time the recommendations are for Dan and Elsa who both have the "supervisor" skill.

3. Recommendations with employee preferences
Employees can define a range of preferences including unpreferred work times and who they prefer to work with.
In the following example, Dan prefers not to work on Mondays.
-
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/recommendations/recommend-employees [email protected]
{
"maxNumberOfRecommendations": 2,
"fitShiftId": "2027-02-01-a",
"modelInput": {
"employees": [
{
"id": "Ann",
"skills": [
{
"id": "supervisor"
}
],
"unavailableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z"
}
],
"availableTimeSpans": [
{
"start": "2027-02-02T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Beth",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Carl",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Dan",
"skills": [
{
"id": "supervisor"
}
],
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
],
"unpreferredTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-02T00:00:00Z"
}
]
},
{
"id": "Elsa",
"skills": [
{
"id": "supervisor"
}
],
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
},
{
"id": "Flo",
"availableTimeSpans": [
{
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-08T00:00:00Z"
}
]
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"requiredSkills": [
"supervisor"
]
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"employee": "Beth"
}
]
}
}
{
"recommendations": [
{
"employeeId": "Elsa",
"scoreDiff": "0hard/1medium/0soft",
"constraintDiffs": [
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
]
},
{
"employeeId": "Dan",
"scoreDiff": "0hard/1medium/-960soft",
"constraintDiffs": [
{
"score": "0hard/0medium/-960soft",
"constraintName": "Employee works during unpreferred time",
"matchCountDiff": 1
},
{
"score": "0hard/1medium/0soft",
"constraintName": "Unassigned mandatory shift",
"matchCountDiff": -1
}
]
}
]
}
modelOutput
contains two recommendations to cover shift 2027-02-01-a
.
In this scenario, Dan is still recommended because only he and Elsa have the required skill of supervisor, but this time, Elsa is recommended first as she is the best fit, and Dan is recommended second.
In the second recommendation, we see:
{
"score": "0hard/0medium/-960soft",
"constraintName": "Employee works during unpreferred time"
}
This shows a soft score penalty because the recommendation would involve Dan working during an unpreferred time.

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.