Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Employee Shift Scheduling
  • Shift service constraints
  • Shift assignments
  • Shift selection
  • latest
    • latest
    • 1.22.x

Employee Shift Scheduling

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terminology
      • Use case guide
      • Planning AI concepts
      • Integration
      • Constraints
      • Understanding the API
      • Demo datasets
      • Input datasets
        • Model configuration
        • Model input
        • Planning window
      • Planning window
      • Time zones and Daylight Saving Time (DST)
      • Tags and tag types
      • Input validation
      • Output datasets
        • Metadata
        • Model output
        • Input metrics
        • Key performance indicators (KPIs)
      • Metrics and optimization goals
      • Score analysis
      • Visualizations
    • Employee resource constraints
      • Employee contracts
      • Employee availability
      • Employee priority
      • Pairing employees
      • Shift travel and locations
      • Shift Breaks
      • Employee activation
      • Work limits
        • Minutes worked per period
        • Minutes worked in a rolling window
        • Minutes logged per period
        • Days worked per period
        • Days worked in a rolling window
        • Consecutive days worked
        • Shifts worked per period
        • Shifts worked in a rolling window
        • Weekend minutes worked per period
        • Weekends worked per period
        • Weekends worked in a rolling window
        • Consecutive weekends worked
      • Time off
        • Days off per period
        • Consecutive days off per period
        • Consecutive days off in a rolling window
        • Consecutive minutes off in a rolling window
        • Shifts to avoid close to day off requests
        • Consecutive weekends off per period
      • Shift rotations and patterns
        • Shift rotations
        • Single day shift sequence patterns
        • Minimize gaps between shifts
        • Multi-day shift sequence patterns
        • Daily shift pairings
        • Overlapping shifts
        • Shift start times differences
        • Minutes between shifts
      • Shift type diversity
        • Shift tag types
        • Shift types worked per period
        • Unique tags per period
      • Fairness
        • Balance time worked
        • Balance shift count
    • Shift service constraints
      • Alternative shifts
      • Cost management
        • Cost groups
        • Employee rates
      • Demand-based scheduling
      • Mandatory and optional shifts
      • Skills and risk factors
      • Shift assignments
        • Shift selection
        • Employee selection
    • Manual intervention
    • Recommendations
    • Real-time planning
    • Real-time planning (preview)
    • Scenarios
      • Configuring labor law compliance
      • Configuring employee well-being
    • Changelog
    • Upgrade to the latest version
    • Feature requests

Shift selection

Assigning employees to the correct number of shifts during specific periods is an important aspect of employee shift scheduling. This guide explains how to assign the correct number of shifts for:

  • 1. Define shifts worked per period
  • 2. Required shifts worked per period
  • 3. Preferred shifts worked per period
  • 4. Limit shifts worked by employee type
  • 5. Limit shifts worked by shift type
  • 6. Concurrent shift rules

1. Define shifts worked per period

Learn how to configure an API Key to run the examples in this guide:
  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your tenant, and from the drop-down menu select Manage tenant, 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.

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.

1.1. Rule satisfiability

The satisfiability of the rule can be REQUIRED or PREFERRED.

2. Required shifts worked per period

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 three shifts that could be assigned and three employees who could take the shifts, however, the minimumMaximumShiftsPerPeriod rule states a maximum of two shifts can be assigned.

One 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>
{
  "metadata": {
    "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
  }
}

3. Preferred shifts worked per period

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

Every soft constraint has a weight that can be configured to change the relative importance of the constraint compared to other constraints.

Learn about constraint weights.

{
  "globalRules": {
    "minimumMaximumShiftsPerPeriod": [
      {
        "id": "min1ShiftMax2ShiftsPerDay",
        "period": "DAY",
        "satisfiability": "PREFERRED",
        "shiftsWorkedMin": 1,
        "shiftsWorkedMax": 2
      }
    ]
  }
}

In the following example, there are three shifts available. All three shifts are assigned, and a soft penalty is added to the dataset 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>
{
  "metadata": {
    "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
  }
}

4. Limit shifts worked by employee type

Rules to set a required 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 three shifts and three employees, however, there is a rule that specifies only one employee tagged with Contractor can be assigned a shift during the period.

Two of the employees are contractors. One shift is assigned to a non-contractor employee and one shift is assigned to a contractor. One 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>
{
  "metadata": {
    "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
  }
}

5. Limit shifts worked by shift type

Rules to set a required 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"]
}
Further information about including or excluding shifts with shift tags:

Shifts with specific tags can be included or excluded by the rule. Tags are defined in shifts:

{
  "shifts": [
    {
      "id": "2027-02-01",
      "start": "2027-02-01T09:00:00Z",
      "end": "2027-02-01T17:00:00Z",
      "tags": ["Part-time"]
    }
  ]
}

Use includeShiftTags to include shifts with specific tags or excludeShiftTags to exclude shifts with specific tags.

shiftTagMatches can be set to ALL or ANY. The default behavior for shiftTagMatches is ALL, and if omitted, the default ALL will be used.

The rule can define either includeShiftTags or excludeShiftTags, but not both.

{
  "includeShiftTags": ["Part-time", "Weekend"],
  "shiftTagMatches": "ALL"
}

With shiftTagMatches set to ALL, 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.

{
  "excludeShiftTags": ["Part-time", "Weekend"],
  "shiftTagMatches": "ALL"
}

With shiftTagMatches set to ALL, 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, will exclude shifts that include the tags Part-time and Weekend from the rule. Shifts tagged only Part-time or only Weekend will 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, will exclude any shift that includes the tags Part-time or Weekend, whether they occur together or not.

In the following example, there are three shifts and three employees, however, there is a rule that specifies only one shift tagged with Relief can be assigned during the period.

Two of the three shifts are tagged Relief. The shift that is not tagged Relief and one of the shifts that is tagged Relief are assigned. One 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>
{
  "metadata": {
    "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
  }
}

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

Further information about including or excluding shifts with shift tags:

Shifts with specific tags can be included or excluded by the rule. Tags are defined in shifts:

{
  "shifts": [
    {
      "id": "2027-02-01",
      "start": "2027-02-01T09:00:00Z",
      "end": "2027-02-01T17:00:00Z",
      "tags": ["Part-time"]
    }
  ]
}

Use includeShiftTags to include shifts with specific tags or excludeShiftTags to exclude shifts with specific tags.

shiftTagMatches can be set to ALL or ANY. The default behavior for shiftTagMatches is ALL, and if omitted, the default ALL will be used.

The rule can define either includeShiftTags or excludeShiftTags, but not both.

{
  "includeShiftTags": ["Part-time", "Weekend"],
  "shiftTagMatches": "ALL"
}

With shiftTagMatches set to ALL, 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.

{
  "excludeShiftTags": ["Part-time", "Weekend"],
  "shiftTagMatches": "ALL"
}

With shiftTagMatches set to ALL, 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, will exclude shifts that include the tags Part-time and Weekend from the rule. Shifts tagged only Part-time or only Weekend will 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, will exclude any shift that includes the tags Part-time or Weekend, whether they occur together or not.

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 three shifts and three employees, but the concurrentShiftsRules specifies only two shifts tagged with Fork lift can be assigned concurrently.

One 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>
{
  "metadata": {
    "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
  }
}

6.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"
        }
      }
    ]
  }
}

Next

  • See the full API spec or try the online API.

  • Learn more about employee shift scheduling from our YouTube playlist.

  • Manage Shift rotations and patterns and Employee contracts.

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