Companies that use external systems (i.e., different than shyftplan) to manage time accounts have the option to integrate those time accounts that are relevant for shift planning purposes with shyftplan. For this, shyftplan features a specific absence reason „time account“ against which absences can be taken and which can be addressed via API endpoints.
This article provides the relevant background for external time accounts and explains how to set up, sync and use external time accounts, structured in 6 sections:
- High-level overview
- Synchronisation requirements for external „current balance“ vs. automatically calculated „projected balance“
- Calculation logic
- Creating/Updating of the absence reason
- Creating/Updating time-account absences
- Updating employees' balances
1. High-level overview
To view and use the balance (e.g. accumulated overtime) from an external time account in shyftplan, you first need to create an appropriate absence reason for it in shyftplan.
This absence reason has to be of the type „time account“ and can then be used in shyftplan as the base for absences against the time account.
The absence reason can be created and updated, and the balances synced, by way of our public API, which is documented at www.developer.shyftplan.com. This is the way for systems integration.
These actions are also possible manually in the browser app, though.
On creation of the absence reason, the employment time account for each employee is automatically created (and also will be created for new employees) and its lower/upper warnings/limits set to the absence reason's default values.
After that, each employee's time account limits, warnings and also the balance are independently configurable.
2. Synchronisation requirements for external „current balance“ vs. automatically calculated „projected balance“
When an employee looks at his balance for a particular time account (or a manager does the same for a time account of an employee), shyftplan shows him the value of the „balance“ property as per the last sync. The sync process has to make sure that the correct balance is present at the time of viewing. The balance will be displayed „as is“ and shyftplan doesn't perform deductions from or additions to it, so it is assumed that all (past) absences of the employee against the time account have already been taken into account.
Additionally the „projected balance“ is shown. This is calculated in shyftplan by deducting the hours value of future, approved absences (against the particular time account) from the current balance. The default hours value is pre-calculated by shyftplan for every absence upon creation of the absence (or when changing the time-frame of the absence). This calculation is done per calendar day. You can easily save your own daily hours instead - see „Calculation logic“. Future absences include all of today's absence hours, tomorrow’s absence hours, and so on.
The recommended sync process ensures that the current balance is updated regularly to show the value of the external time account as per the start of a day.
An example should make this clearer - we will use full hours here (instead of minutes as used by the API) for simplicity: Let's assume an employee takes an absence for Wednesday, Thursday and Friday, and a manager approves it.
According to either the preset calculation logic of the time account absence reason, or because the absence was later re-configured (see „Calculation logic“ further down), shyftplan stores a value for the absence hours of every calendar day as follows:
Let's further assume that today is Monday (first day in the table above), and the current time account balance value is +20 hours. The employee will look at his time account and see this current balance (+20:00) and also the projected balance, which will deduct all future absences from the current balance:
projected balance = 20 - 8 - 0 - 8 = 4 hours
On Monday morning, the employee sees these values:
The current time account balance will not change and always stay at +20:00, until a sync process updates it to a different value.
In contrast, the projected balance will change as time passes, because on Thursday morning the absence hours from Wednesday are no longer part of the future absences. Only 8 hours from Friday remain to be deducted from whichever current balance value has been set at the latest sync.
At the start of Thursday, the external system must be updated and the time account balance synced to shyftplan. It must be updated to a value of 12 hours to allow shyftplan a correct calculation of the projected balance:
projected balance = 12 - 0 - 8 = 4
Thus, on Thursday (and also on Friday), the employee sees these values:
The next update is necessary on Saturday, when no future absence hours remain and the current balance should reflect the past absence. This assumes an update in the external system has happened, by which the absence from Friday was deducted from the external current balance and the new external current balance of 4 was synced to shyftplan.
3. Calculation logic
The absence reason that is created in shyftplan for the external time account must be configured on how to value its absences by default, i.e. how much time will be deducted from a time account because of each individual day of absence. For this deduction, shyftplan uses the summed-up values of all future days of absence, deducts this value from the current balance, and displays it as the projected balance.
📝 Configuring the daily absence hours
The hour values for an absence taken against a time-account (or any other absence reason) can be set to differ from the preset calculation logic's values. See further below for endpoints.
The available calculation type options (which are defined for per absence reason, not per absence) are:
- planned_shifts_and_rotations
- rotations
- planned_shifts
- employee_working_days_and_absence_hours
The method description for each type is described in this article (in German).
An example curl request to create such an absence reason looks like this
curl -X 'POST' \
'https://shyftplan.com/api/v2/absence_reasons' \
-H 'accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded;' \
-d 'user_email=api_user%40my.corp&authentication_token=tkn54321&company_id=12345&type=time_account&name=External%20Time%20Account&calculation_type=rotations&short_name=ETA&employee_management_enabled=true&lower_limit_minutes=-30&lower_warning_minutes=-15&upper_warning_minutes=%2B75&upper_limit_minutes=%2B100&calculation_type=rotations'
The response looks like this:
{
"id": 103905,
"company_id": 12136,
"name": "Absence reason",
"type": "with_entitlement",
"has_localization": false,
"deleted_at": null,
"created_at": "2024-05-31T10:23:25.230+02:00",
"updated_at": "2024-05-31T10:23:25.230+02:00",
"is_absence_attachments_allowed": false,
"days": 1,
"calculation_type": "standard",
"carry_over_days_enabled": false,
"carry_over_days_deadline": null,
"hours_calculation_type": "employee_profile",
"short_name": null,
"is_default": false,
"lower_limit_minutes": null,
"lower_warning_minutes": null,
"upper_warning_minutes": null,
"upper_limit_minutes": null,
}
B. PATCH absence_reasons/{id}
PATCH https://shyftplan.com/v2/absence_reasons/{id}
{id} - replace with the ID of the absence reason, e.g. 2452221
Use this endpoint to change/update an absence reason for absences against a time-account.
This table shows the available parameters/properties in the payload, their meaning and the options for their values. Only parameters relevant for time-account absence reasons are shown.
Property |
Description |
Option/Type of value |
---|---|---|
user_email |
E-Mail of the API user |
(String) (required) |
authentication_ |
Token for API access |
(String) (required) |
company_id |
ID of the company's shyftplan account |
(Int) (required) |
name |
The new name to be used for the absence reason in shyftplan |
(String) (optional) |
calculation_type |
One of several string values to determine the mode of calculation for the „effective duration“ of an absence. For time-accounts, use one of the values on the right if you want to change the calculation type. Change will only apply to new absences or, when changing the range of an existing absence, to additional dates from the new range. For time-accounts, those function as default calculation types that can be overridden in individual absences. |
(String) (optional) One of
|
short_name |
Abbreviation for the absence reason |
(String) (max 3 letters) (optional) |
employee_ |
When set to true, employees can use the absence reason to request absences. Conversely, when set to false, only managers have the ability to use the absence reason on behalf of employees |
Boolean (optional) |
lower_limit_ |
Default lower limit for an employee's current and projected balance in minutes - negative numbers allowed |
(Int) (optional) |
lower_warning_ |
Default lower warning for employment time accounts. Warn employees/managers when the current/projected balance is lower than this value - negative numbers allowed |
(Int) (optional) |
upper_warning_ |
Default upper warning for employment time accounts. Warn employees/managers when the current/projected balance is higher than this value - negative numbers allowed |
(Int) (optional) |
upper_limit_ |
Default upper limit for an employee's current and projected balance in minutes - negative numbers allowed |
(Int) (optional) |
update_all_ |
Update the particular time-accounts of all employments to the limits and warning levels of this time-account absence reason. |
Boolean (optional) default: false |
permission_ |
Restriction of edit access for absences against the time account. „managers“ includes „super-admins“. Only users in the allowed group can create an application for an absence, and managers can only set the state of a time-account absence to „accepted“ or „refused“ if the level is not „super-admins“. |
default: „all_users“ One of:
|
allow_ |
true: Employees can change the values for absence days and hours from the automatically calculated values. |
Boolean (optional) default: false |
The response looks like this:
{
"id": 103905,
"company_id": 12136,
"name": "Absence reason",
"type": "with_entitlement",
"has_localization": false,
"deleted_at": null,
"created_at": "2024-05-31T10:23:25.230+02:00",
"updated_at": "2024-05-31T10:23:25.230+02:00",
"is_absence_attachments_allowed": false,
"days": 1,
"calculation_type": "standard",
"carry_over_days_enabled": false,
"carry_over_days_deadline": null,
"hours_calculation_type": "employee_profile",
"short_name": null,
"is_default": false,
"lower_limit_minutes": null,
"lower_warning_minutes": null,
"upper_warning_minutes": null,
"upper_limit_minutes": null,
}
C. GET absence_reasons and …/{id}
GET https://shyftplan.com/api/v1/absence_reasons/{id}
{id} - replace with the ID of an absence reason
Use this endpoint to get details about a single absence reason. This endpoint is already well-documented on developer.shyftplan.com.
GET https://shyftplan.com/api/v1/absence_reasons
Use this endpoint to retrieve a list of available absence reasons. This endpoint is already well-documented on developer.shyftplan.com. In a recent expansion, the filter parameter types has been added:
Property |
Description |
Option/Type of value |
---|---|---|
… |
(see endpoint documentation on developer.shyftplan.com) |
|
types |
Filter the list of absence reasons to only include the given absence reason types |
(Array of String) (optional) One of:
|
The response for GET …/absence_reasons/{id} looks like this:
{
"id": 532784,
"type": "time_account",
"company_id": 12345,
"name": "Overtime",
"short_name": "OTA",
"days": null,
"carry_over_days_enabled": false,
"carry_over_days_deadline": null,
"calculation_type": "rotations",
"hours_calculation_type": null,
"is_absence_attachments_allowed": false,
"lower_limit_minutes": -60,
"lower_warning_minutes": 0,
"upper_warning_minutes": 600,
"upper_limit_minutes": 1200,
"employee_management_enabled": true,
"created_at": "2024-05-31T10:23:25.230+02:00",
"updated_at": "2024-05-31T10:23:25.230+02:00"
}
For GET …/absence_reasons, the response payload will be a JSON object with a property „items“, which holds an array of objects like the above.
D. GET absences/stats
GET htttps://shyftplan.com/api/v1/absences/stats
Use this endpoint to get accumulated balances for time-account absence reasons (and also for other types of absence reasons).
This table shows the available parameters/properties in the payload, their meaning and the options for their values.
Property |
Description |
Option/Type of value |
---|---|---|
user_email |
E-Mail of the API user |
(String) (required) |
authentication_ |
Token for API access |
(String) (required) |
company_id |
ID of the company's shyftplan account |
(Int) (required) |
starts_at |
Limit the interval for absences taken into account Example value: „2024-05-31T10:23:25.230+02:00“ |
(DateTime) (optional) |
ends_at |
Limit the interval for absences taken into account Example value: „2024-05-31T10:23:25.230+02:00“ |
(DateTime) (optional) |
employment_ids[] |
Only take employments with these IDs into account. If this is left empty, all employments are included |
(Array of Int) (optional) |
location_ids[] |
Only take an absence into account, when the employment can work in a location the ID of which is included in the array. If this is left empty, all locations are included |
(Array of Int) (optional) |
employee_ |
When set to true, employees can use the absence reason to request absences. Conversely, when set to false, only managers have the ability to use the absence reason on behalf of employees |
Boolean (optional) |
attachment |
true: Only include absences for which an attachment was uploaded. false: Only include absences without attachment. (Empty): Include both. |
(Boolean) (optional) |
The response looks like this: (type of value shown instead of value)
{
"absence_reasons": [
{
"id": (Integer),
"name": (String),
"type": (String),
"limit": (Integer), // present only if filtering for one employee
"used": (Integer),
"carry_over_info": {
"used": (Integer),
"limit": (Integer)
}, // present only if filtering for one employee
"balance_minutes": (Integer)
}
],
"total_days": (Integer)
}
5. Create/Update/Read an absence against a time-account
Use one of these endpoints:
A. POST https://shyftplan.com/api/v1/absences
B. PUT https://shyftplan.com/api/v1/absences
C. GET https://shyftplan.com/api/v1/absences and …/absences/{id}
A. POST api/v1/absences
POST https://shyftplan.com/api/v1/absences
Use this endpoint to create an absence against an external time-account
Using the endpoint will not perform a sync with the external time-account. Instead, an absence in shyftplan will be created using the details from the POST request, with a reference to an absence reason, which is of type „time_account". The employment's balance stored there will not change, but the projected balance calculated by shyftplan and shown in the GUI will change,
- if the absence lies (at least partly) in the future
- and if the absence's state is "accepted".
The integration code is in charge of updating the balance so that this value displays the correct status/time amount in shyftplan every day.
This table shows the available parameters/properties in the payload, their meaning and the options for their values, but only for absences against external time-accounts:
Property |
Description |
Option/Type of value |
---|---|---|
user_email |
E-Mail of the API user |
(String) (required) |
authentication_ |
Token for API access |
(String) (required) |
company_id |
ID of the company's shyftplan account |
(Int) (required) |
employment_id |
ID of the employment that the absence applies to, as stored in the shyftplan database |
(Int) (required) |
absence_reason_id |
ID of the absence reason in shyftplan. For an absence against an external time account this needs to be the ID of a reason of type „time_account“ |
(Int) (required) |
starts_at |
Start of the absence. For full-day absences, only the date part matters. E.g. „2024-01-01T09:05:03.321Z“ |
(DateTime) (required) |
ends_at |
End of the absence. For full-day absences, only the date part matters. E.g. „2024-01-01T09:05:03.321Z“ |
(DateTime) (required) |
days |
(can be any value of type Float, e.g. „0“ - will be disregarded but most be present in the request) |
(Float) (required) |
paid |
Must be set to „true“ for time-account absences. |
(Boolean) (required) |
state |
State of the absence |
(String) (optional, default: „new“) One of :
|
notes |
Comment to be stored with the absence |
(String) (optional) |
is_full_day |
Is this an absence for entire days only? Set this to false for an absence that starts or ends at a certain time on a date |
(Boolean) (optional, default: true) |
days_breakdown |
Instead of the hour amounts calculated from the „calculation_type“ of the absence_reason, set other values for each day of the absence |
(Array of Hash) (optional) - see below this table - |
The days_breakdown array consists of JSON objects for each day of the absence. Each object looks like this:
{
"day": Date, // e.g. "2024-06-30"
"minutes": Int // e.g. 240
}
Both fields are required if days_breakdown is used, but days_breakdown is an optional parameter
The response for the successful POST request is
{
"id": 137640,
"starts_at": "2024-07-24T00:00:00.000+02:00",
"ends_at": "2024-07-26T00:00:00.000+02:00",
"state": "new",
"reason": null,
"notes": null,
"employment_id": 908066,
"created_at": "2024-06-20T17:55:55.458+02:00",
"updated_at": "2024-06-20T17:55:55.458+02:00",
"deleted_at": null,
"days": 1.0,
"vacation_minutes": null,
"paid": false,
"absence_reason_id": 104454,
"is_full_day": true,
"refuse_message": null,
"metadata": {},
"file_uploaded_at": null,
"state_updated_at": null,
"file_name": null,
"file_uid": null,
"file": null,
"deleted_staff_shifts_info": null,
"can_manage": true
}
B. PUT absences
PUT https://shyftplan.com/api/v1/absences
Use this endpoint to update an absence against an external time-account
Using the endpoint will not perform a sync with the external time-account. Instead, an absence in shyftplan will be updated using the details from the PUT request, with a reference to an absence reason, which is of type „time_account“. The employment's balance stored there will not change, but the projected balance calculated by shyftplan and shown in the GUI will change,
- if the absence lies (at least partly) in the future
- and if the absence's state is „accepted“.
The integration code is in charge of updating the balance so that this value displays the correct status/time amount in shyftplan every day.
This table shows the available parameters/properties in the payload, their meaning and the options for their values, but only for absences against external time-accounts:
Property |
Description |
Option/Type of value |
---|---|---|
user_email |
E-Mail of the API user |
(String) (required) |
authentication_ |
Token for API access |
(String) (required) |
company_id |
ID of the company's shyftplan account |
(Int) (required) |
id |
ID of the absence as stored in the shyftplan database |
(Int) (required) |
absence_reason_id |
ID of the absence reason in shyftplan. Can only be changed to an ID of another absence reason of type „time_account“ |
(Int) (optional) |
starts_at |
Start of the absence. For full-day absences, only the date part matters. E.g. „2024-01-01T09:05:03.321Z“ |
(DateTime) (optional) |
ends_at |
End of the absence. For full-day absences, only the date part matters. E.g. „2024-01-01T09:05:03.321Z“ |
(DateTime) (optional) |
refuse_message |
Comment to be stored with the absence detailing a reason for a refusal of an absence request |
(String) (optional) |
state |
State of the absence |
(String) (optional) One of
|
notes |
Comment to be stored with the absence |
(String) (optional) |
is_full_day |
Is this an absence for entire days only? Set this to false for an absence that starts or ends at a certain time on a date |
(Boolean) (optional, default: true) |
days_breakdown |
Instead of the hour amounts calculated from the „calculation_type“ of the absence_reason, set other values for each day |
(Array of Hash) (optional) - see below this table - |
The days_breakdown array consists of JSON objects for each day of the absence. Each object looks like this:
{
"day": Date, // e.g. "2024-06-30"
"minutes": Int // e.g. 240
}
Both fields are required if days_breakdown is used, but days_breakdown is an optional parameter
The response for the successful PUT request is the same as for POST
C. GET absences and absences/{id}
- GET https://shyftplan.com/api/v1/absences - for a list of many absences
- GET https://shyftplan.com/api/v1/absences/{id} - for a single absence
{id} - replace with the ID of an absence as stored in the shyftplan database
These GET endpoints are already documented on developer.shyftplan.com. The response will look like this: (Type shown instead of value):
{
"id": (Integer),
"absence_reason_type": (String), // "time_account"
"starts_at": (DateTime),
"ends_at": (DateTime),
"state": (String),
"state_updated_at": (DateTime),
"notes": (String),
"employment_id": (Integer),
"created_at": (DateTime),
"updated_at": (DateTime),
"deleted_at": (DateTime),
"days": (Float),
"vacation_minutes": (Integer),
"paid": (Boolean),
"absence_reason_id": (Integer),
"is_full_day": (Boolean),
"refuse_message": (String),
"metadata": (JSON),
"file_uid": (Integer),
"file_name": (String),
"file_uploaded_at": (DateTime),
"can_manage": (Boolean), //
"file": (String),
"absence_days": (Array[AbsenceDay])
}
Absence Day will be a JSON object for each day of the absence like this (pseudo-code with included comments):
{
"day": (Date), // YYYY-MM-DD
"minutes": (Integer), // absence time for date, see POST or PUT above
"original_value": (Integer), // The value calculated based off of the
// calculation type of the absence reason
"special_day": (String), // name of the special day, if any
"last_edited_by": (Integer) // ID of last employment to edit the object
}
6. Update/Read employees' balances
Upon creation of a time-account absence reason (see previous section), employees' time account balances as well as warnings and limits can be updated (PATCH) or read (GET) via the two endpoints
A. PATCH https://shyftplan.com/api/v1/employment_time_accounts
B. GET https://shyftplan.com/api/v1/employment_time_accounts
Both endpoints will be documented on developer.shyftplan.com, where the technical details following can also be found.
A. PATCH
PATCH https://shyftplan.com/api/v1/employment_time_accounts
Use this endpoint to change an employee's time account details.
This table shows the available parameters/properties in the payload, their meaning and the options for their values:
Property |
Description |
Option/Type of value |
---|---|---|
user_email |
E-Mail of the API user |
(String) (required) |
authentication_ |
Token for API access |
(String) (required) |
company_id |
ID of the company's shyftplan account |
(Int) (required) |
employment_id |
ID of the employee as stored in the shyftplan database |
(Int) (required) |
absence_reason_id |
ID of the absence reason used for the external time account as stored in the shyftplan database |
(Int) (required) |
lower_limit_ |
Lower limit for the current/projected balance |
(Int) (optional) |
lower_warning_ |
When the current/projected balance drops below this threshold, a warning will be shown in the GUI |
(Int) (optional) |
upper_warning_ |
When the current/projected balance surpasses this threshold, a warning will be shown in the GUI |
(Int) (optional) |
upper_limit_ |
Lower limit for the current/projected balance |
(Int) (optional) |
balance_minutes |
Value to be shown as current balance to the employee and to managers in the shyftplan GUI |
(Int) (optional) |
An example CURL request looks like this:
curl -X 'PATCH' \
'https://shyftplan.com/api/v1/employment_time_accounts' \
-H 'accept: application/json' \
-H 'Content-Type: application/json;' \
-d '{
"user_email": "api_user@my.corp",
"authentication_token": "tkn54321",
"company_id": 12345,
"employment_id": 54432,
"absence_reason_id": 11223,
"balance_minutes": 1200,
"lower_limit_minutes": -1200,
"lower_warning_minutes": -600,
"upper_warning_minutes": 1200,
"upper_limit_minutes": 2400
}'
The response looks like this:
{
"id": 7889332,
"employment_id": 54432,
"absence_reason_id": 11223,
"balance_minutes": 1200,
"upper_limit_minutes": 2400,
"upper_warning_minutes": 1200,
"lower_warning_minutes": -600,
"lower_limit_minutes": -1200,
"created_at": "2024-01-01T09:05:03.321+01:00",
"updated_at": "2024-05-03T09:10:02.148+02:00"
}
B. GET
GET https://shyftplan.com/api/v1/employment_time_accounts
Use this endpoint to get a (filtered) list of employee's time account details.
This table shows the available parameters/properties in the payload, their meaning and the options for their values:
Property |
Description |
Option/Type of value |
---|---|---|
user_email |
E-Mail of the API user |
(String) (required) |
authentication_ |
Token for API access |
(String) (required) |
company_id |
ID of the company's shyftplan account |
(Int) (required) |
employment_ids |
Filter the list returned to only include employees with given IDs |
(Array of Int) (optional) |
absence_reason_ |
Filter the list returned to only include absence reasons with given IDs. Each ID must belong to an absence reason of type „time_account“ |
(Array of Int) (optional) |
An example CURL request looks like this:
curl --request GET \
--url 'https://shyftplan.com/api/v1/employment_time_accounts?user_email=api_user%40my.corp&authentication_token=tkn54321&company_id=12345&employment_ids[]=11223&employment_ids[]=12435&absence_reason_ids[]=72873&absence_reason_ids[]=63746' \
--header 'accept: application/json'
The response looks like this:
{
"items": [
{
"id": 1234567,
"employment_id": 11223,
"absence_reason_id": 72873,
"balance_minutes": 60,
"upper_limit_minutes": 2400,
"upper_warning_minutes": 1200,
"lower_warning_minutes": -1200,
"lower_limit_minutes": -2400,
"created_at": "2024-01-01T09:05:01.123+01:00",
"updated_at": "2024-05-31T09:15:02.345+02:00"
},
{
"id": 1234567,
"employment_id": 11223,
"absence_reason_id": 63746',
"balance_minutes": 120,
"upper_limit_minutes": 2400,
"upper_warning_minutes": 1200,
"lower_warning_minutes": -1200,
"lower_limit_minutes": -2400,
"created_at": "2024-01-01T09:05:01.123+01:00",
"updated_at": "2024-05-31T09:15:02.345+02:00"
},
{
"id": 1234567,
"employment_id": 12435,
"absence_reason_id": 72873,
"balance_minutes": 0,
"upper_limit_minutes": 2400,
"upper_warning_minutes": 1200,
"lower_warning_minutes": -1200,
"lower_limit_minutes": -2400,
"created_at": "2024-01-01T09:05:01.123+01:00",
"updated_at": "2024-05-31T09:15:02.345+02:00"
},
{
"id": 1234567,
"employment_id": 12435,
"absence_reason_id": 63746',
"balance_minutes": -60,
"upper_limit_minutes": 2400,
"upper_warning_minutes": 1200,
"lower_warning_minutes": -1200,
"lower_limit_minutes": -2400,
"created_at": "2024-01-01T09:05:01.123+01:00",
"updated_at": "2024-05-31T09:15:02.345+02:00"
}
],
"total": 4
}