Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Employee Shift Scheduling
  • Shift service constraints
  • Shift assignments

Employee Shift Scheduling

    • Introduction
    • Planning AI concepts
    • Metrics and optimization goals
    • Getting started with employee shift scheduling
    • Understanding the API
    • Employee shift scheduling user guide
    • Employee resource constraints
      • Employee availability
      • Employee contracts
      • Employee contracts: period rules
      • Employee contracts: shift rules
      • Fairness
      • Pairing employees
      • Shift travel and locations
    • Shift service constraints
      • Alternative shifts
      • Demand and supply
      • Mandatory and optional visits
      • Shift assignments
      • Shift sequence patterns: single day shifts
      • Shift sequence patterns: multi-day shifts
      • Shift sequence patterns: daily shift pairings
      • Skills and risk factors
    • Recommendations
    • Real-time planning
    • Time zones and Daylight Saving Time (DST)
    • New and noteworthy
    • Upgrading to the latest versions
    • Feature requests

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:

  • Shifts worked per period

  • Limit shifts worked by employee type

  • Limit shifts worked by shift type

  • Concurrent shift rules

  • Preferred Employee

  • Unpreferred Employee

  • Prohibited Employee

Prerequisites

To run the examples in this guide, you need to authenticate with a valid API key for the Employee Shift Scheduling model:

  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.

  3. 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.

shifts worked per period required
  • 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.

shifts worked per period preferred
  • 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.

shifts worked per period by employee required
  • 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.

shifts worked per period by shift required
  • 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.

concurrent 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": "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.

preferred employee
  • 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.

unpreferred employee
  • 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.

prohibited employee
  • 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

  • See the Employee shift scheduling user guide

  • 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.

  • © 2025 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default