Shifts API guide

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:


ComponentDescriptionDocumentation
Shift BreaksDefine pause windowsDutch docs - Pauzes
Planning ConfigsDefine planning rulesDutch docs - Planning configuraties
Shift TemplatesDefine reusable shift configurationsDutch docs - Templates
Base ShiftsDefine the source schedule for shifts, including user assignment, timing, duration, and recurrence rulesDutch docs - Shifts
ShiftsGenerated, read-only instances of base shifts used for planning and schedulingDutch 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

FieldDescription
skipTravelTimeStartDayIndicates whether travel time at the start of the day is skipped
skipTravelTimeEndDayIndicates whether travel time at the end of the day is skipped
maxTravelTimeFromBaseMaximum allowed travel time from the base location to the first appointment, in milliseconds
maxTravelTimeToBaseMaximum allowed travel time from the last appointment back to the base location, in milliseconds
maxDeltaTravelTimeMaximum allowed increase in route travel time, in milliseconds
maxTravelTimeMaximum allowed total route travel time, in milliseconds
speedModifierFactor used to adjust the estimated time required to execute a task
maxTasksMaximum number of appointments/tasks allowed within a shift
breakIdsShift 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


FieldDescription
frequencyRecurrence type (e.g. daily, weekly, monthly, yearly, never)
intervalHow often the shift repeats (e.g. every 2 weeks)
endDateEnd date of the recurrence
countNumber of occurrences
byWeekDayDays of the week (for weekly recurrence)
byMonthMonths in which the recurrence applies
bySetPosSet positions for advanced recurrence rules
byMonthDayDays of the month
byWeekNumberWeek numbers
weekStartWeek start day
timezoneTimezone used for recurrence handling
recurrencyRuleFull 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:

OperationEndpoint pattern
GetGET /< resource >/get
ListGET /< resource >/list
CreatePOST /< resource >/create
UpdatePOST /< resource >/update
RemovePOST /< 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

EntityCannot be removed if
Shift BreakIt is linked to one or more planning configurations
Planning ConfigIt is linked to one or more shift templates or base shifts
Shift TemplateIt is linked to one or more base shifts
Base ShiftCan 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