{
  "openapi": "3.1.0",
  "info": {
    "title": "PeopleDesk API",
    "version": "1.4.0",
    "summary": "Employees, departments, absences, time-off balances and compensation for one PeopleDesk company account.",
    "description": "The PeopleDesk API gives programmatic access to the HR records of one company account: the employee directory and\nreporting lines, departments, absences (vacation, sick leave, parental leave and other leave), vacation balances, and\ncompensation. All requests and responses are JSON. Dates are ISO 8601 calendar dates (`YYYY-MM-DD`); timestamps are UTC.\n\n**Authentication.** Every `/v1` request carries an API key as `Authorization: Bearer <key>`. Each company account has\ntwo keys: a *production* key, which works on the live records, and a *sandbox* key, which reads the same records but\nkeeps everything it writes apart from production. Every response says which one served it in the\n`X-PeopleDesk-Environment` header (`production` or `sandbox`). API keys see the whole company account; restrict who\nmay use a key in your own integration.\n\n**Rate limits.** 120 requests per minute per API key, counted in fixed one-minute windows. Every response carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds when the window resets).\nOver the limit the API answers `429` with a `Retry-After` header in seconds.\n\n**Pagination.** List endpoints take `page` (from 1) and `per_page` (1 to 100, default 25) and return `meta` with\n`page`, `per_page`, `total` and `total_pages`, plus `links.next` and `links.prev` (relative URLs, or null).\n\n**Errors.** Errors are `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}` with a stable machine-readable `code` and a\nhuman-readable `message`.\n\n**Sensitive data.** `reason` on an absence is the employee's free-text comment and may contain health or family\ndetails. Compensation is confidential pay data. Both should be shown only to people entitled to see them.",
    "contact": {
      "name": "PeopleDesk Developer Support",
      "url": "https://hr-demo.vortner.dev/docs"
    }
  },
  "servers": [
    {
      "url": "https://hr-demo.vortner.dev",
      "description": "PeopleDesk API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Employees",
      "description": "The employee directory and reporting lines."
    },
    {
      "name": "Departments",
      "description": "The departments of the company and who leads each one."
    },
    {
      "name": "Absences",
      "description": "Vacation, sick leave, parental leave and other absences, and requests for time off."
    },
    {
      "name": "Compensation",
      "description": "Confidential pay data: base salary and bonus target."
    },
    {
      "name": "Meta",
      "description": "This API description."
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Meta"
        ],
        "summary": "Get this API description",
        "description": "Returns this OpenAPI 3.1 document. No API key is needed and the request does not count toward the rate limit.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/employees": {
      "get": {
        "operationId": "listEmployees",
        "tags": [
          "Employees"
        ],
        "summary": "List employees",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Only employees of this department, given as its id (`dep_sales`) or its exact name (`Sales`, case-insensitive).",
            "schema": {
              "type": "string"
            },
            "example": "Sales"
          },
          {
            "name": "manager_id",
            "in": "query",
            "required": false,
            "description": "Only employees whose direct manager is this employee id.",
            "schema": {
              "type": "string"
            },
            "example": "emp_1006"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only employees with this employment status.",
            "schema": {
              "$ref": "#/components/schemas/EmployeeStatus"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Only the employee with this work email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "sofia.petrova@alderfinch.example"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of employees.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Employee"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "emp_1008",
                      "first_name": "Sofia",
                      "last_name": "Petrova",
                      "email": "sofia.petrova@alderfinch.example",
                      "job_title": "Account Executive",
                      "department": "Sales",
                      "manager_id": "emp_1006",
                      "location": "Munich",
                      "start_date": "2022-04-01",
                      "status": "active",
                      "employment_type": "full_time"
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/employees/{id}": {
      "get": {
        "operationId": "getEmployee",
        "tags": [
          "Employees"
        ],
        "summary": "Get an employee",
        "description": "Returns one employee by id.",
        "parameters": [
          {
            "$ref": "#/components/parameters/employeeId"
          }
        ],
        "responses": {
          "200": {
            "description": "The employee.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Employee"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "emp_1008",
                    "first_name": "Sofia",
                    "last_name": "Petrova",
                    "email": "sofia.petrova@alderfinch.example",
                    "job_title": "Account Executive",
                    "department": "Sales",
                    "manager_id": "emp_1006",
                    "location": "Munich",
                    "start_date": "2022-04-01",
                    "status": "active",
                    "employment_type": "full_time"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/employees/{id}/direct-reports": {
      "get": {
        "operationId": "listDirectReports",
        "tags": [
          "Employees"
        ],
        "summary": "List an employee's direct reports",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/employeeId"
          }
        ],
        "responses": {
          "200": {
            "description": "The direct reports.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Employee"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "emp_1008",
                      "first_name": "Sofia",
                      "last_name": "Petrova",
                      "email": "sofia.petrova@alderfinch.example",
                      "job_title": "Account Executive",
                      "department": "Sales",
                      "manager_id": "emp_1006",
                      "location": "Munich",
                      "start_date": "2022-04-01",
                      "status": "active",
                      "employment_type": "full_time"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/employees/{id}/time-off-balance": {
      "get": {
        "operationId": "getTimeOffBalance",
        "tags": [
          "Absences"
        ],
        "summary": "Get an employee's vacation balance",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/employeeId"
          },
          {
            "name": "year",
            "in": "query",
            "required": false,
            "description": "Calendar year. Defaults to the current year.",
            "schema": {
              "type": "integer",
              "minimum": 2000,
              "maximum": 2100
            },
            "example": 2026
          }
        ],
        "responses": {
          "200": {
            "description": "The balance.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TimeOffBalance"
                    }
                  }
                },
                "example": {
                  "data": {
                    "employee_id": "emp_1008",
                    "year": 2026,
                    "unit": "days",
                    "entitlement": 28,
                    "taken": 8,
                    "planned": 14,
                    "remaining": 6
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/departments": {
      "get": {
        "operationId": "listDepartments",
        "tags": [
          "Departments"
        ],
        "summary": "List departments",
        "description": "Returns every department with its head and its number of employees. The list is complete and not paginated.",
        "responses": {
          "200": {
            "description": "The departments.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Department"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dep_sales",
                      "name": "Sales",
                      "head_id": "emp_1006",
                      "employee_count": 6
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/absences": {
      "get": {
        "operationId": "listAbsences",
        "tags": [
          "Absences"
        ],
        "summary": "List absences in a date range",
        "description": "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.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "First day of the window (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-11-02"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "Last day of the window (YYYY-MM-DD), on or after `from`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-11-06"
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "description": "Only absences of this employee.",
            "schema": {
              "type": "string"
            },
            "example": "emp_1008"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Only absences of this type.",
            "schema": {
              "$ref": "#/components/schemas/AbsenceType"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only absences with this status.",
            "schema": {
              "$ref": "#/components/schemas/AbsenceStatus"
            }
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of absences.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Absence"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "abs_2001",
                      "employee_id": "emp_1008",
                      "type": "vacation",
                      "status": "approved",
                      "start_date": "2026-11-02",
                      "end_date": "2026-11-06",
                      "half_day": false,
                      "days": 5,
                      "reason": "Family trip to Porto",
                      "created_at": "2026-09-14T08:12:00Z"
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createAbsence",
        "tags": [
          "Absences"
        ],
        "summary": "Request time off",
        "description": "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).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AbsenceCreate"
              },
              "example": {
                "employee_id": "emp_1008",
                "type": "vacation",
                "start_date": "2026-11-19",
                "end_date": "2026-11-20",
                "half_day": false,
                "reason": "Long weekend"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The request was created with status `pending`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              },
              "Location": {
                "description": "Path of the new absence.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Absence"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "abs_3f9c2a71d0be",
                    "employee_id": "emp_1008",
                    "type": "vacation",
                    "status": "pending",
                    "start_date": "2026-11-19",
                    "end_date": "2026-11-20",
                    "half_day": false,
                    "days": 2,
                    "reason": "Long weekend",
                    "created_at": "2026-10-29T09:15:02Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The period overlaps another pending or approved absence of the employee.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "overlap",
                    "message": "The request overlaps absence abs_2001 (2026-11-02 to 2026-11-06, approved)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The 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) or `employee_inactive`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_balance",
                    "message": "The request needs 5 vacation days but only 4 remain for 2026."
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/absences/{id}": {
      "delete": {
        "operationId": "withdrawAbsence",
        "tags": [
          "Absences"
        ],
        "summary": "Withdraw a pending time-off request",
        "description": "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.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Absence id.",
            "schema": {
              "type": "string"
            },
            "example": "abs_3f9c2a71d0be"
          }
        ],
        "responses": {
          "204": {
            "description": "The request was withdrawn. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The absence cannot be withdrawn through the API.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_withdrawable",
                    "message": "Absence abs_2001 was not requested through the API and cannot be withdrawn here; ask HR to change it."
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/compensation": {
      "get": {
        "operationId": "getCompensation",
        "tags": [
          "Compensation"
        ],
        "summary": "Get an employee's compensation",
        "description": "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.",
        "parameters": [
          {
            "name": "employee_id",
            "in": "query",
            "required": true,
            "description": "Employee id.",
            "schema": {
              "type": "string"
            },
            "example": "emp_1008"
          }
        ],
        "responses": {
          "200": {
            "description": "The compensation record.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-PeopleDesk-Environment": {
                "$ref": "#/components/headers/X-PeopleDesk-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Compensation"
                    }
                  }
                },
                "example": {
                  "data": {
                    "employee_id": "emp_1008",
                    "base_salary": 66000,
                    "currency": "EUR",
                    "pay_frequency": "monthly",
                    "bonus_target_percent": 30,
                    "effective_date": "2026-04-01"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key sent as `Authorization: Bearer <key>`. Each company account has a production key (live records) and a sandbox key (same records; writes kept apart from production). The `X-PeopleDesk-Environment` response header says which one served the request."
      }
    },
    "parameters": {
      "page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "Page number, starting at 1.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "per_page": {
        "name": "per_page",
        "in": "query",
        "required": false,
        "description": "Items per page, at most 100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "employeeId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Employee id.",
        "schema": {
          "type": "string"
        },
        "example": "emp_1008"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests allowed per one-minute window for this API key (120).",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix time in seconds when the current window ends.",
        "schema": {
          "type": "integer"
        }
      },
      "X-PeopleDesk-Environment": {
        "description": "Which key served the request: `production` or `sandbox`.",
        "schema": {
          "type": "string",
          "enum": [
            "production",
            "sandbox"
          ]
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "A parameter or the request body is missing or malformed (`invalid_parameter`, `invalid_body`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-PeopleDesk-Environment": {
            "$ref": "#/components/headers/X-PeopleDesk-Environment"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_parameter",
                "message": "from is required (YYYY-MM-DD)."
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing or not valid (`unauthorized`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "unauthorized",
                "message": "Missing or invalid API key. Send Authorization: Bearer <key>."
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The employee or absence does not exist (`not_found`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-PeopleDesk-Environment": {
            "$ref": "#/components/headers/X-PeopleDesk-Environment"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "No employee with id emp_9999."
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "More than 120 requests in the current minute for this API key (`rate_limited`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-PeopleDesk-Environment": {
            "$ref": "#/components/headers/X-PeopleDesk-Environment"
          },
          "Retry-After": {
            "description": "Seconds until the window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Rate limit of 120 requests per minute exceeded. Retry after 17 seconds."
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "An unexpected error on our side (`internal_error`). Retry later.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "An unexpected error occurred."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code.",
                "examples": [
                  "not_found"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              }
            }
          }
        }
      },
      "PageMeta": {
        "type": "object",
        "required": [
          "page",
          "per_page",
          "total",
          "total_pages"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "description": "The page returned."
          },
          "per_page": {
            "type": "integer",
            "description": "Items per page."
          },
          "total": {
            "type": "integer",
            "description": "Items across all pages."
          },
          "total_pages": {
            "type": "integer",
            "description": "Number of pages (at least 1)."
          }
        }
      },
      "PageLinks": {
        "type": "object",
        "required": [
          "next",
          "prev"
        ],
        "properties": {
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "Relative URL of the next page, or null on the last page."
          },
          "prev": {
            "type": [
              "string",
              "null"
            ],
            "description": "Relative URL of the previous page, or null on the first page."
          }
        }
      },
      "EmployeeStatus": {
        "type": "string",
        "enum": [
          "active",
          "on_leave",
          "onboarding",
          "terminated"
        ],
        "description": "`active`: working; `on_leave`: on long-term leave such as parental leave; `onboarding`: signed, start date ahead; `terminated`: has left."
      },
      "Employee": {
        "type": "object",
        "required": [
          "id",
          "first_name",
          "last_name",
          "email",
          "job_title",
          "department",
          "manager_id",
          "location",
          "start_date",
          "status",
          "employment_type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Employee id.",
            "examples": [
              "emp_1008"
            ]
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Work email address."
          },
          "job_title": {
            "type": "string"
          },
          "department": {
            "type": "string",
            "description": "Department name, as in `Department.name`.",
            "examples": [
              "Sales"
            ]
          },
          "manager_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Employee id of the direct manager; null for the chief executive."
          },
          "location": {
            "type": "string",
            "description": "Office (Berlin, Hamburg, Munich) or Remote."
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "description": "First working day."
          },
          "status": {
            "$ref": "#/components/schemas/EmployeeStatus"
          },
          "employment_type": {
            "type": "string",
            "enum": [
              "full_time",
              "part_time",
              "working_student",
              "contractor"
            ]
          }
        }
      },
      "Department": {
        "type": "object",
        "required": [
          "id",
          "name",
          "head_id",
          "employee_count"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "dep_sales"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Sales"
            ]
          },
          "head_id": {
            "type": "string",
            "description": "Employee id of the head of the department."
          },
          "employee_count": {
            "type": "integer",
            "description": "Employees in the department, including the head."
          }
        }
      },
      "AbsenceType": {
        "type": "string",
        "enum": [
          "vacation",
          "sick",
          "parental",
          "other"
        ],
        "description": "Kind of absence. Only `vacation` counts against the vacation balance."
      },
      "AbsenceStatus": {
        "type": "string",
        "enum": [
          "approved",
          "pending",
          "rejected"
        ],
        "description": "Decision state. New requests start as `pending`."
      },
      "Absence": {
        "type": "object",
        "required": [
          "id",
          "employee_id",
          "type",
          "status",
          "start_date",
          "end_date",
          "half_day",
          "days",
          "reason",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "abs_2001"
            ]
          },
          "employee_id": {
            "type": "string",
            "description": "The absent employee."
          },
          "type": {
            "$ref": "#/components/schemas/AbsenceType"
          },
          "status": {
            "$ref": "#/components/schemas/AbsenceStatus"
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "description": "First day of the absence."
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "description": "Last day of the absence (inclusive)."
          },
          "half_day": {
            "type": "boolean",
            "description": "True for a half-day absence on a single date."
          },
          "days": {
            "type": "number",
            "description": "Working days (Monday to Friday) the absence covers; 0.5 for a half day."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "The employee's free-text comment. Sensitive: may contain health or family details."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the absence was entered or requested (UTC)."
          }
        }
      },
      "AbsenceCreate": {
        "type": "object",
        "required": [
          "employee_id",
          "type",
          "start_date",
          "end_date"
        ],
        "additionalProperties": false,
        "properties": {
          "employee_id": {
            "type": "string",
            "description": "The employee who will be absent."
          },
          "type": {
            "$ref": "#/components/schemas/AbsenceType"
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "description": "First day off; today or later."
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "description": "Last day off (inclusive), in the same calendar year as `start_date`."
          },
          "half_day": {
            "type": "boolean",
            "default": false,
            "description": "A half day; `start_date` and `end_date` must then be the same date."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Optional comment for the approver."
          }
        }
      },
      "TimeOffBalance": {
        "type": "object",
        "required": [
          "employee_id",
          "year",
          "unit",
          "entitlement",
          "taken",
          "planned",
          "remaining"
        ],
        "properties": {
          "employee_id": {
            "type": "string"
          },
          "year": {
            "type": "integer"
          },
          "unit": {
            "type": "string",
            "enum": [
              "days"
            ],
            "description": "All figures are working days."
          },
          "entitlement": {
            "type": "number",
            "description": "Vacation days for the year."
          },
          "taken": {
            "type": "number",
            "description": "Approved vacation already ended."
          },
          "planned": {
            "type": "number",
            "description": "Approved vacation still to come plus pending vacation requests."
          },
          "remaining": {
            "type": "number",
            "description": "`entitlement - taken - planned`."
          }
        }
      },
      "Compensation": {
        "type": "object",
        "required": [
          "employee_id",
          "base_salary",
          "currency",
          "pay_frequency",
          "bonus_target_percent",
          "effective_date"
        ],
        "properties": {
          "employee_id": {
            "type": "string"
          },
          "base_salary": {
            "type": "number",
            "description": "Gross annual base salary in `currency`."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code.",
            "examples": [
              "EUR"
            ]
          },
          "pay_frequency": {
            "type": "string",
            "enum": [
              "monthly",
              "hourly"
            ],
            "description": "How the salary is paid out."
          },
          "bonus_target_percent": {
            "type": "number",
            "description": "Target bonus as a percentage of base salary."
          },
          "effective_date": {
            "type": "string",
            "format": "date",
            "description": "Date the current figures took effect."
          }
        }
      }
    }
  }
}