PeopleDesk API
REST API for the PeopleDesk HR platform: employees, departments, absences, vacation balances and compensation. Version 1.4.0.
Authentication
Base URL https://hr-demo.vortner.dev. Every /v1 request carries an API key:
curl https://hr-demo.vortner.dev/v1/employees \
-H "Authorization: Bearer $PEOPLEDESK_API_KEY"
Each company account has two keys. The production key works on the live records. The sandbox key reads the same records, but time-off requests it creates are kept apart from production and are not visible to the production key. Every authenticated response carries X-PeopleDesk-Environment: production or sandbox. A missing or wrong key gets 401 unauthorized. An API key sees the whole company account, so decide in your integration who may use it.
Rate limits
120 requests per minute per API key, counted in fixed one-minute windows. Every /v1 response carries:
X-RateLimit-Limit | 120 |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | Unix time (seconds) when the window ends |
Over the limit the API answers 429 rate_limited with Retry-After in seconds. /openapi.json and this page are not rate limited.
Pagination
GET /v1/employees and GET /v1/absences are paginated with page (from 1) and per_page (1 to 100, default 25; larger values get 400). Responses look like:
{
"data": [ ... ],
"meta": { "page": 1, "per_page": 25, "total": 22, "total_pages": 1 },
"links": { "next": null, "prev": null }
}
Single resources come as {"data": {...}}. Direct reports and departments are short lists returned complete in data.
Errors
{ "error": { "code": "not_found", "message": "No employee with id emp_9999." } }
| Status | Codes |
|---|---|
| 400 | invalid_parameter, invalid_body |
| 401 | unauthorized |
| 404 | not_found |
| 405 | method_not_allowed |
| 409 | overlap, not_withdrawable |
| 422 | invalid_dates, insufficient_balance, employee_inactive |
| 429 | rate_limited |
Data model
An employee belongs to one department and has at most one direct manager (manager_id); following manager_id upward gives the reporting line to the chief executive. A department has a head. An absence belongs to one employee and covers whole working days or one half day. Only vacation counts against the time-off balance. Compensation is one current record per employee.
Sensitive fields. Absence.reason is the employee's free-text comment and may contain health or family details. All of compensation is confidential pay data.
Employee
| Field | Type | Description |
|---|---|---|
id | string | Employee id. |
first_name | string | |
last_name | string | |
email | string (email) | Work email address. |
job_title | string | |
department | string | Department name, as in Department.name. |
manager_id | string | null | Employee id of the direct manager; null for the chief executive. |
location | string | Office (Berlin, Hamburg, Munich) or Remote. |
start_date | string (date) | First working day. |
status | active | on_leave | onboarding | terminated | |
employment_type | string |
Department
| Field | Type | Description |
|---|---|---|
id | string | |
name | string | |
head_id | string | Employee id of the head of the department. |
employee_count | integer | Employees in the department, including the head. |
Absence
| Field | Type | Description |
|---|---|---|
id | string | |
employee_id | string | The absent employee. |
type | vacation | sick | parental | other | |
status | approved | pending | rejected | |
start_date | string (date) | First day of the absence. |
end_date | string (date) | Last day of the absence (inclusive). |
half_day | boolean | True for a half-day absence on a single date. |
days | number | Working days (Monday to Friday) the absence covers; 0.5 for a half day. |
reason | string | null | The employee's free-text comment. Sensitive: may contain health or family details. |
created_at | string (date-time) | When the absence was entered or requested (UTC). |
TimeOffBalance
| Field | Type | Description |
|---|---|---|
employee_id | string | |
year | integer | |
unit | string | All figures are working days. |
entitlement | number | Vacation days for the year. |
taken | number | Approved vacation already ended. |
planned | number | Approved vacation still to come plus pending vacation requests. |
remaining | number | entitlement - taken - planned. |
Compensation
| Field | Type | Description |
|---|---|---|
employee_id | string | |
base_salary | number | Gross annual base salary in currency. |
currency | string | ISO 4217 currency code. |
pay_frequency | string | How the salary is paid out. |
bonus_target_percent | number | Target bonus as a percentage of base salary. |
effective_date | string (date) | Date the current figures took effect. |
Endpoints
GET /openapi.json
Get this API description
Returns this OpenAPI 3.1 document. No API key is needed and the request does not count toward the rate limit.
200The OpenAPI document.
GET /v1/employees
List employees
Returns the employees of the company account, ordered by employee id. Filters combine with AND. Former employees stay in the directory with status terminated; filter by status to leave them out. Use email to find the employee record that belongs to a work email address.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
department | query | no | string | Only employees of this department, given as its id (dep_sales) or its exact name (Sales, case-insensitive). |
manager_id | query | no | string | Only employees whose direct manager is this employee id. |
status | query | no | active | on_leave | onboarding | terminated | Only employees with this employment status. |
email | query | no | string (email) | Only the employee with this work email address (exact match, case-insensitive). |
200A page of employees.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/employees/{id}
Get an employee
Returns one employee by id.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Employee id. |
200The employee.401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/employees/{id}/direct-reports
List an employee's direct reports
Returns the employees whose direct manager is this employee (one level down the reporting line, not the whole subtree). The list is complete and not paginated; it is empty for employees who manage nobody.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Employee id. |
200The direct reports.401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/employees/{id}/time-off-balance
Get an employee's vacation balance
Returns the vacation balance of one employee for a calendar year, in working days. taken counts approved vacation that has already ended; planned counts approved vacation still to come and pending vacation requests; remaining is entitlement - taken - planned. Sick, parental and other leave do not reduce the balance.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Employee id. |
year | query | no | integer | Calendar year. Defaults to the current year. |
200The balance.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/departments
List departments
Returns every department with its head and its number of employees. The list is complete and not paginated.
200The departments.401The API key is missing or not valid (unauthorized).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/absences
List absences in a date range
Returns the absences that overlap the window from..to (both inclusive, at most 366 days), ordered by start date. An absence that starts before from or ends after to is included when any of its days fall in the window. Pending and rejected requests are included; filter by status for approved absences only. reason is the employee's own comment and may contain health or family details.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
from | query | yes | string (date) | First day of the window (YYYY-MM-DD). |
to | query | yes | string (date) | Last day of the window (YYYY-MM-DD), on or after from. |
employee_id | query | no | string | Only absences of this employee. |
type | query | no | vacation | sick | parental | other | Only absences of this type. |
status | query | no | approved | pending | rejected | Only absences with this status. |
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
200A page of absences.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
POST /v1/absences
Request time off
Creates a time-off request for an employee. The request starts as pending until a manager or HR decides it in PeopleDesk. The period must start today or later, lie within one calendar year and at most one year ahead, contain at least one working day, and not overlap another pending or approved absence of the same employee. Vacation requests must fit the remaining vacation balance. days is computed in working days (Monday to Friday).
Body (JSON): employee_id, type, start_date, end_date required; half_day, reason optional.
201The request was created with statuspending.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).409The period overlaps another pending or approved absence of the employee.422The request is well-formed but cannot be granted:invalid_dates(end before start, in the past, a half day over several days, no working days, more than a year ahead, across two years),insufficient_balance(not enough vacation left) oremployee_inactive.429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
DELETE /v1/absences/{id}
Withdraw a pending time-off request
Withdraws a time-off request that is still pending and was created through the API with the same key. Absences entered in PeopleDesk itself and requests that have already been decided cannot be withdrawn here (409 not_withdrawable); HR changes those in PeopleDesk.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Absence id. |
204The request was withdrawn. No body.401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).409The absence cannot be withdrawn through the API.429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/compensation
Get an employee's compensation
Returns the current compensation of one employee: annual base salary, currency, pay frequency, bonus target and the date the current figures took effect. This is confidential pay data.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
employee_id | query | yes | string | Employee id. |
200The compensation record.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The employee or absence does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.