This guide explains how to implement and work with Shifts in Bessy using the API.
This guide focuses on the API implementation of shifts. The full API documentation can be found here: 👉 https://www.bessy.academy/reference/my-requests
For a broader functional overview, including how shifts behave in the platform, recurrence concepts, and activation, see: 👉 https://www.bessy.academy/docs/shifts
Please note: the shift API guide and documentation are still in development. Shift planning is a new feature in Bessy that we continue to expand and optimize. Because of this, endpoints, response formats, examples, and descriptions in this guide may change over time. We therefore recommend checking this documentation once in a while to stay up to date with the latest changes and improvements.
📘 Shifts – Functional Overview
Shifts form the foundation of scheduling within Bessy.
They determine:
- which user is available
- when they are available
- what type of tasks they can perform
All appointments are scheduled within these shifts.
A shift is composed of:
- a user
- a date and time
- a shift template
Optionally, a shift can include:
- recurrence
Planbot AI uses these inputs to determine:
- which appointments can be scheduled
- how routes are optimized
- how time, including breaks and travel time, is allocated
🧩 Shift Structure
[Shift Breaks]
Define pause windows
↓
[Planning Configurations]
Define planning rules (travel, limits, breaks)
↓
[Shift Templates]
Define reusable shift configurations
↓
[Base Shifts]
Define schedule (when, who, duration, recurrence)
↓
[Generated Shifts]
Actual shift instances used for planning (read-only)
📚 Overview
Shifts are built from a set of reusable components:
| Component | Description | Documentation |
|---|---|---|
| Shift Breaks | Define pause windows | Dutch docs - Pauzes |
| Planning Configs | Define planning rules | Dutch docs - Planning configuraties |
| Shift Templates | Define reusable shift configurations | Dutch docs - Templates |
| Base Shifts | Define the source schedule for shifts, including user assignment, timing, duration, and recurrence rules | Dutch docs - Shifts |
| Shifts | Generated, read-only instances of base shifts used for planning and scheduling | Dutch docs - Shifts |
🚀 End-to-End Implementation Flow
A typical implementation flow looks like this:
1. Create shift breaks
2. Create planning configurations and link breaks
3. Create shift templates and link planning configurations
4. Create base shifts for users using templates
5. Retrieve generated shifts for a date range
6. Update base shifts or use date exceptions to change the generated schedule
In short:
Breaks → Configs → Templates → Base Shifts → Generated Shifts
Note: This Developer Guide explains shifts, starting from the beginning. In practice, some steps (like creating shift templates) may be handled directly in the platform rather than via the API, so the actual implementation flow can vary.
🚀 Getting Started
1. Create Shift Breaks (optional)
Shift breaks define when and how long a pause can occur within a shift (e.g. lunch breaks). Using breaks is optional.
They are not fixed appointments, but time windows in which the system (Planbot AI) can schedule a break.
Each break consists of:
- a time range: when the break can occur
- a duration: how long the break lasts
Breaks are linked to planning configurations. When a planning configuration includes breaks, Planbot AI can take them into account when generating the route and available working time.
Endpoint
POST /shift-break/create
Example (JSON)
{
"name": "Lunch break",
"description": "Lunch break",
"rangeStartTime": 43200000,
"rangeEndTime": 46800000,
"duration": 1800000,
"archived": false
}Related endpoints
-
GET /shift-break/get -
GET /shift-break/list -
POST /shift-break/update -
POST /shift-break/remove
Docs: Dutch docs - Pauzes
A shift break cannot be removed when it is linked to one or more planning configurations. In that case, archive it instead.
2. Create Planning Configurations
Planning configurations define how the system schedules work within a shift.
They act as the ruleset for Planbot AI, controlling things like:
- how much travel time is allowed
- how many tasks can be scheduled
- whether travel time is taken into account
- when and how breaks are applied
A planning configuration does not belong to a single shift, but is reusable across multiple shift templates.
When a shift is created, the selected planning configuration determines:
- how routes are optimized
- how many appointments fit into a shift
- how breaks are inserted into the schedule
Endpoint
POST /planning-config/create
Example (JSON)
{
"name": "Default planning config",
"description": "Standard configuration for field service shifts",
"skipTravelTimeStartDay": false,
"skipTravelTimeEndDay": false,
"maxTravelTimeFromBase": 1800000,
"maxTravelTimeToBase": 1800000,
"maxDeltaTravelTime": 900000,
"maxTravelTime": 3600000,
"speedModifier": 1,
"maxTasks": 10,
"breakIds": [
"breakId1",
"breakId2"
],
"archived": false
}Keyfields
| Field | Description |
|---|---|
| skipTravelTimeStartDay | Indicates whether travel time at the start of the day is skipped |
| skipTravelTimeEndDay | Indicates whether travel time at the end of the day is skipped |
| maxTravelTimeFromBase | Maximum allowed travel time from the base location to the first appointment, in milliseconds |
| maxTravelTimeToBase | Maximum allowed travel time from the last appointment back to the base location, in milliseconds |
| maxDeltaTravelTime | Maximum allowed increase in route travel time, in milliseconds |
| maxTravelTime | Maximum allowed total route travel time, in milliseconds |
| speedModifier | Factor used to adjust the estimated time required to execute a task |
| maxTasks | Maximum number of appointments/tasks allowed within a shift |
| breakIds | Shift breaks linked to this planning configuration |
Related endpoints
-
GET /planning-config/get -
GET /planning-config/list -
POST /planning-config/update -
POST /planning-config/remove
Docs: Dutch docs - Planning configuraties
A planning configuration cannot be removed when it is linked to one or more shift templates or base shifts. In that case, archive it instead.
3. Create Shift Templates
Shift templates define the structure and constraints of a shift.
They act as a blueprint for shifts and determine:
- which users are available
- which task types can be scheduled
- which planning configurations can be used
- optional tags
- optional base location settings
- optional visual settings such as icon and color
A template itself does not create any shifts. It only defines the reusable configuration that can be used when creating base shifts.
Endpoint
POST /shift-template/create
Example (JSON)
{
"name": "Day shift - Field Service",
"shiftPlanningConfigIds": [
"planningConfigId1",
"planningConfigId2"
],
"userIds": [
"userId1",
"userId2"
],
"taskTypeIds": [
"taskTypeInstallation",
"taskTypeMaintenance"
],
"tagIds": [
"region-1",
"team-2"
],
"startBaseLocationId": "locationStartId",
"endBaseLocationId": "locationEndId",
"iconName": "truck",
"color": "#3B82F6",
"archived": false
}💡 Multiple Planning Configurations
Templates can reference multiple planning configurations.
The active configuration is determined when creating the base shift via shiftPlanningConfigId.
If no configuration is specified when creating the base shift, the system uses the default configuration, typically the first planning configuration linked to the template.
This allows flexibility, for example:
- different configurations per user
- different configurations per day or scenario
- switching between stricter and more flexible planning rules
Related endpoints
-
GET /shift-template/get -
GET /shift-template/list -
POST /shift-template/update -
POST /shift-template/remove
Docs: Dutch docs - Templates
A shift template cannot be removed when it is linked to one or more base shifts. In that case, archive it instead.
4. Create Base Shifts
Base shifts define when a shift actually takes place and who is assigned to it.
They combine:
- a user
- a template
- a time window
- optional recurrence rules
- optional planning configuration override
Base shifts can be configured as:
- one-time shifts
- recurring shifts (daily, weekly, etc.)
From these base shifts, the system generates actual shifts used for planning.
Endpoint
POST /base-shift/create
Example (Weekly Recurring Base Shift)
{
"userId": "userId1",
"startDate": 1746057600000,
"duration": 28800000,
"shiftTemplateId": "shiftTemplateId1",
"shiftPlanningConfigId": "planningConfigId1",
"archived": false,
"frequency": "weekly",
"interval": 1,
"byWeekDay": [
"MO",
"WE",
"FR"
],
"endType": "endDate",
"endDate": 1751328000000
}
Example (Single / Non-recurring Shift)
{
"userId": "userId1",
"startDate": 1746057600000,
"duration": 28800000,
"shiftTemplateId": "shiftTemplateId1",
"shiftPlanningConfigId": "planningConfigId1",
"archived": false,
"frequency": "never"
}Example (Daily Recurring Base Shift)
{
"userId": "userId1",
"startDate": 1746057600000,
"duration": 28800000,
"shiftTemplateId": "shiftTemplateId1",
"shiftPlanningConfigId": "planningConfigId1",
"archived": false,
"frequency": "daily",
"interval": 1,
"endDate": 1751328000000
}Planning Configuration Override A base shift can override the planning configuration defined on the template by specifying shiftPlanningConfigId. If omitted, the default configuration from the template is used.
Related endpoints
-
GET /base-shift/get -
GET /base-shift/list -
POST /base-shift/update -
POST /base-shift/remove -
POST /base-shift/add-date-exception -
POST /base-shift/remove-date-exception
Docs: Dutch docs - Shifts
5. Retrieve Generated Shifts
Generated shifts are the actual shift instances derived from base shifts.
They are:
- calculated based on base shifts and recurrence rules
- constrained by templates and planning configurations
- returned for a specific date range
- used by planning and scheduling logic
Generated shifts are read-only. To change them, update the underlying base shift or use date exceptions.
Endpoint
GET /shift/list
Example Request
GET /shift/list?startDate=1746057600000&endDate=1748736000000
Example Response
{
"ok": 1,
"count": 2,
"shifts": [
{
"_id": "shiftInstanceId1",
"baseShiftId": "baseShiftId1",
"userId": "userId1",
"startTime": 1746057600000,
"endTime": 1746086400000,
"duration": 28800000,
"shiftTemplateId": "shiftTemplateId1",
"shiftPlanningConfigId": "planningConfigId1",
"taskTypeIds": [
"taskTypeInstallation",
"taskTypeMaintenance"
],
"tagIds": [
"region-north",
"team-alpha"
],
"startBaseLocationId": "locationStartId",
"endBaseLocationId": "locationEndId",
"archived": false
},
{
"_id": "shiftInstanceId2",
"baseShiftId": "baseShiftId1",
"userId": "userId1",
"startTime": 1746144000000,
"endTime": 1746172800000,
"duration": 28800000,
"shiftTemplateId": "shiftTemplateId1",
"shiftPlanningConfigId": "planningConfigId1",
"taskTypeIds": [
"taskTypeInstallation"
],
"tagIds": [
"region-north"
],
"startBaseLocationId": "locationStartId",
"endBaseLocationId": "locationEndId",
"archived": false
}
]
}Supported filters
-
_id -
userIds -
taskTypeIds -
tagIds -
startDate -
endDate -
skip -
limit -
sort
The /shift/list endpoint requires startDate and endDate.
Maximum allowed date range: 31 days.
Docs: Dutch docs - Shifts
📅 Base Shift Date Exceptions
Date exceptions allow you to exclude specific dates from a recurring base shift.
This is useful when a user is:
- on vacation
- unavailable on a specific day
- on a different schedule temporarily
Instead of modifying the recurrence rule, you can simply skip individual occurrences.
➕ Add a Date Exception
Exclude a specific date from a base shift.
Endpoint
POST /base-shift/add-date-exception
Example (JSON)
{
"baseShiftId": "baseShift1",
"date": 1746403200000
}➖ Remove a Date Exception
Re-include a previously excluded date.
Endpoint
POST /base-shift/remove-date-exception
Example (JSON)
{
"baseShiftId": "baseShift1",
"date": 1746403200000
}🔁 Base Shift Recurrence
Base shifts support recurrence, allowing you to define repeating schedules (e.g. daily or weekly shifts).
Recurrence is configured when creating or updating a base shift.
🧠 How it works
- A base shift defines the first occurrence (startDate)
- Recurrence rules define how it repeats over time
- The system generates shifts based on these rules
📌 Core Fields
| Field | Description |
|---|---|
| frequency | Recurrence type (e.g. daily, weekly, monthly, yearly, never) |
| interval | How often the shift repeats (e.g. every 2 weeks) |
| endDate | End date of the recurrence |
| count | Number of occurrences |
| byWeekDay | Days of the week (for weekly recurrence) |
| byMonth | Months in which the recurrence applies |
| bySetPos | Set positions for advanced recurrence rules |
| byMonthDay | Days of the month |
| byWeekNumber | Week numbers |
| weekStart | Week start day |
| timezone | Timezone used for recurrence handling |
| recurrencyRule | Full RRULE string for advanced recurrence |
🔄 Supported Recurrence Options
Daily
Repeats every X days.
{
"frequency": "daily",
"interval": 1
}Weekly
Repeats on specific weekdays.
{
"frequency": "weekly",
"interval": 1,
"byWeekDay": [
"MO",
"WE",
"FR"
]
}Custom Intervals
Repeat every X units.
{
"frequency": "weekly",
"interval": 2
}Example: every 2 weeks.
⚙️ Advanced Recurrence (RRULE)
For more complex scenarios, you can use an RRULE string via recurrencyRule.
This follows the RFC 5545 iCalendar RRULE standard.
Example
{
"recurrencyRule": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR"
}Example with end date
{
"recurrencyRule": "FREQ=DAILY;INTERVAL=1;UNTIL=20251231T235959Z"
}Notes
- When recurrencyRule is provided, it overrides fields like frequency, interval, and byWeekDay
- Use RRULE for advanced recurrence patterns such as:
- monthly schedules
- yearly schedules
- first/last weekday of a month
- complex combinations of weekdays, months, or set positions
⏹️ Recurrence End Options
A recurrence can be configured to end based on specific criteria. The following options determine when the recurrence stops:
Using standard fields
{
"endDate": 1750000000000
}or:
{
"count": 10
}Using RRULE
{
"recurrencyRule": "FREQ=WEEKLY;COUNT=10"
}⚠️ Important Recurrence Notes
- startDate always defines the first occurrence
- If no endDate, count, or RRULE UNTIL is set, recurrence continues indefinitely
- byWeekDay is mainly relevant for weekly recurrence recurrency
- Rule takes precedence over other recurrence fields
- All timestamps are in milliseconds
- RRULE UNTIL uses UTC date-time format
🔄 Common Operations
Most shift-related resources support the following operations:
| Operation | Endpoint pattern |
|---|---|
| Get | GET /< resource >/get |
| List | GET /< resource >/list |
| Create | POST /< resource >/create |
| Update | POST /< resource >/update |
| Remove | POST /< resource >/remove |
Examples:
-
GET /shift-template/list -
POST /shift-template/update -
POST /base-shift/remove
🗑️ Archiving vs Removing
Most resources support an archived field.
Use archiving when:
- the resource is no longer actively used
- the resource cannot be removed because dependencies exist
- you want to preserve historical references
Use removing only when:
- the resource has no linked dependencies
- the API allows removal
🔗 Dependency Rules
| Entity | Cannot be removed if |
|---|---|
| Shift Break | It is linked to one or more planning configurations |
| Planning Config | It is linked to one or more shift templates or base shifts |
| Shift Template | It is linked to one or more base shifts |
| Base Shift | Can be removed directly, but generated shifts should be treated as read-only results |
⚠️ Rate Limiting
Endpoints can return 429 when rate limits, record limits, concurrency limits, or temporary penalty windows are exceeded.
Example response:
{
"message": "Rate limit exceeded",
"reason": "rate-limit-too-many-requests",
"info": {
"retryAfterMs": 5000
}
}Common machine-readable reasons:
-
penalty -
rate-limit-too-many-requests -
rate-limit-too-many-records -
too-many-concurrent-requests
Implement retry logic using retryAfterMs where provided.
📎 General Notes
- All timestamps are in milliseconds
- Generated shifts are read-only
- To change generated shifts, update the underlying base shift or use date exceptions
- Prefer archiving over removing when resources may still be referenced