{"openapi":"3.0.1","info":{"title":"Timebutler API v2","description":"REST API for the Timebutler HR SaaS. Used by integrators for absence, time tracking, substitute, and user profile workflows. All endpoints except /oauth2/token require a bearer JWT access token.","contact":{"name":"Timebutler Support","url":"https://www.timebutler.com","email":"support@timebutler.com"},"license":{"name":"Proprietary"},"version":"1.0","x-logo":{"url":"/images/logo/logo.png","altText":"Timebutler"}},"servers":[{"url":"https://app.timebutler.com/api/v2"},{"url":"/api/v2"}],"tags":[{"name":"Absence requests","description":"Vacation, sick-leave and other time-off requests. Employees submit and manage their own requests (`/my`, `POST`, `PUT /{id}`, `DELETE /{id}`); managers and admins see their team's open requests (`/team`, `/pending-count`) and approve or reject them (`POST /{id}/approve`, `POST /{id}/reject`). All endpoints require a bearer token."},{"name":"Absence types","description":"Lookup list of absence types configured for the company (vacation, sick leave, etc.). Used to populate dropdowns when creating an absence request."},{"name":"Cancellation requests","description":"Requests by employees to cancel (delete) an already-approved absence. Managers and admins see pending cancellation requests for their team and can approve or reject them."},{"name":"Categories","description":"Lookup list of time-tracking categories for the company. Used when creating or stopping a clock entry."},{"name":"Authentication","description":"OAuth 2.0 token endpoint. Issues JWT access tokens and rotating refresh tokens. This endpoint is public — no bearer token is required."},{"name":"Projects","description":"Lookup list of time-tracking projects for the company. Favorites are listed first. Used when creating a clock entry or time-tracking record."},{"name":"Substitute employees","description":"Lookup list of employees that may be selected as a substitute when submitting an absence request."},{"name":"Substitute requests","description":"Requests sent to the authenticated user asking them to act as a substitute during a colleague's absence. The user can accept or reject each request."},{"name":"Time clock","description":"Real-time time clock. Start, pause, resume, stop, or cancel the running clock for the authenticated user. Business-trip mode is also controlled here."},{"name":"Time entry requests","description":"Pending change or deletion requests for time entries that require manager or admin approval. Managers and admins can list, approve, and reject these requests."},{"name":"Time entries","description":"Manage individual time-tracking entries. Managers and admins can see how many entries are pending approval; any user can delete their own entries (subject to month-lock and approval-workflow rules)."},{"name":"Time tracking","description":"Today's work and time-off summaries for the authenticated user, plus an endpoint for submitting completed clock entries (start/end timestamps) directly without the real-time time clock."},{"name":"User profile","description":"Basic profile information for the authenticated user: name, email, role, department, and avatar URL."}],"paths":{"/absence-requests/{id}/approve":{"post":{"tags":["Absence requests"],"summary":"Approve a pending absence request","description":"Records the caller's approval on a request in the `pending` state. If the employee has multiple managers, the request only transitions to `approved` once all managers have approved (or if an admin approves). Callers must be an admin or the direct manager of the request's owner.","operationId":"approve","parameters":[{"name":"id","in":"path","description":"Internal ID of the absence request to approve.","required":true,"schema":{"type":"integer","format":"int32"},"example":12345}],"responses":{"200":{"description":"Request after the approval has been recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller is not a manager or admin, or is not permitted to act on this particular request.","content":{"application/json":{}}},"404":{"description":"No request with the given `id` exists for the caller's company.","content":{"application/json":{}}},"409":{"description":"Request is not in `pending` state (already approved, rejected, or deleted).","content":{"application/json":{}}},"500":{"description":"Unexpected server error while updating the request.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-requests":{"get":{"tags":["Absence requests"],"summary":"List the caller's own absence requests (alias)","description":"Convenience alias that returns the same result as `GET /my`. Prefer `/my` in new integrations — this path exists for backwards compatibility.","operationId":"getRequests","responses":{"200":{"description":"List of the caller's requests. Empty array if there are none.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller's account is inactive.","content":{"application/json":{}}},"500":{"description":"Unexpected server error.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]},"post":{"tags":["Absence requests"],"summary":"Create a new absence request","description":"Submits a new absence or vacation request for the caller. The resulting status depends on the absence type's configuration: types that require manager approval start as `pending`, otherwise they go straight to `approved`. Dates are ISO-8601 (`YYYY-MM-DD`). For half-day requests set `halfDay` to `morning` or `afternoon` and use the same value for `startDate` and `endDate`.","operationId":"create","requestBody":{"description":"Fields of the new absence request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAbsenceRequest"}}},"required":true},"responses":{"200":{"description":"Created request, in the state the server assigned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}},"400":{"description":"Missing, malformed or mutually inconsistent fields (invalid `absenceTypeId`, unparseable date, `endDate < startDate`, bad `halfDay` value, or unknown `substituteEmployeeId`).","content":{"application/json":{}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller's role is not allowed to enter this absence type.","content":{"application/json":{}}},"500":{"description":"Unexpected server error while persisting the request.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-requests/{id}":{"put":{"tags":["Absence requests"],"summary":"Update an existing absence request","description":"Updates a request that is still in the `pending` or `edit` state. Only the fields provided in the request body are changed; `null` fields are left as they were. Non-admins can only update their own requests. Requests that have already been approved or rejected cannot be updated and will return `409 Conflict`.","operationId":"update","parameters":[{"name":"id","in":"path","description":"Internal ID of the absence request to update. Matches `id` from `GET /my` or `GET /team`.","required":true,"schema":{"type":"integer","format":"int32"},"example":12345}],"requestBody":{"description":"Fields to update. Omit a field (send `null`) to keep the existing value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAbsenceRequest"}}},"required":true},"responses":{"200":{"description":"Updated request as stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}},"400":{"description":"Malformed body or invalid field combination.","content":{"application/json":{}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller is not the owner of this request and not an admin, or the absence type is forbidden for the caller's role.","content":{"application/json":{}}},"404":{"description":"No request with the given `id` exists for the caller's company.","content":{"application/json":{}}},"409":{"description":"Request is no longer in a state that permits editing (already approved, rejected, or deleted).","content":{"application/json":{}}},"500":{"description":"Unexpected server error while persisting the update.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Absence requests"],"summary":"Delete an absence request or send a cancellation request","description":"Behaviour depends on the current state of the request and the caller's rights: if the caller may delete the request outright it is removed and the `result` field of the response is `\"deleted\"`. Otherwise a cancellation request is created that the employee's managers then have to approve, and the response `result` is `\"cancellation_requested\"`. A reason may be required by the company's configuration when only cancellation is possible.","operationId":"delete","parameters":[{"name":"id","in":"path","description":"Internal ID of the absence request to delete or cancel.","required":true,"schema":{"type":"integer","format":"int32"},"example":12345}],"requestBody":{"description":"Optional body with a free-text `reason`. Required for cancellation requests when the company's configuration forces a reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAbsenceRequest"}}}},"responses":{"200":{"description":"Outcome of the delete/cancel attempt. The `result` field tells the caller which path was taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResultResponse"}}}},"400":{"description":"Cancellation reason missing where the company requires one.","content":{"application/json":{}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller may neither delete nor cancel this request (wrong company, or permissions on the request's state do not allow any action).","content":{"application/json":{}}},"404":{"description":"No request with the given `id` exists for the caller's company.","content":{"application/json":{}}},"500":{"description":"Unexpected server error while deleting or cancelling.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-requests/my":{"get":{"tags":["Absence requests"],"summary":"List the caller's own absence requests","description":"Returns every absence and vacation request belonging to the caller, in any state (pending, approved, rejected, done). Does not include requests the caller manages for their direct reports — for those see `/team`.","operationId":"getMyRequests","responses":{"200":{"description":"List of the caller's requests. Empty array if there are none.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller's account is inactive.","content":{"application/json":{}}},"500":{"description":"Unexpected server error.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-requests/pending-count":{"get":{"tags":["Absence requests"],"summary":"Pending request counts for the caller's team","description":"Returns two counts for the caller's direct reports: open absence requests awaiting the caller's approval, and open cancellation requests. Used to show notification badges on the manager dashboard. Returns zeros when the caller has no direct reports.","operationId":"getPendingCount","responses":{"200":{"description":"Counts returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbsencePendingCountResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller is not a manager or admin.","content":{"application/json":{}}},"500":{"description":"Unexpected server error.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-requests/team":{"get":{"tags":["Absence requests"],"summary":"List absence requests of the caller's direct reports","description":"Returns every absence request belonging to employees the caller manages (admins see all company users), across all states. Each entry includes the employee's display name and ID so the result can be rendered as a team calendar or review list. Empty array if the caller has no direct reports.","operationId":"getTeamRequests","responses":{"200":{"description":"List of team requests. Empty array if none.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller is not a manager or admin.","content":{"application/json":{}}},"500":{"description":"Unexpected server error.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-requests/{id}/reject":{"post":{"tags":["Absence requests"],"summary":"Reject a pending absence request","description":"Marks a pending absence request as rejected. An optional rejection reason may be passed in the body — it is stored and shown to the requesting employee. Callers must be an admin or the direct manager of the request's owner.","operationId":"reject","parameters":[{"name":"id","in":"path","description":"Internal ID of the absence request to reject.","required":true,"schema":{"type":"integer","format":"int32"},"example":12345}],"requestBody":{"description":"Optional body containing a `reason` string shown to the employee.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectRequest"}}}},"responses":{"200":{"description":"Request after the rejection has been recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AbsenceRequestDto"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{}}},"403":{"description":"Caller is not a manager or admin, or is not permitted to act on this particular request.","content":{"application/json":{}}},"404":{"description":"No request with the given `id` exists for the caller's company.","content":{"application/json":{}}},"409":{"description":"Request is not in `pending` state.","content":{"application/json":{}}},"500":{"description":"Unexpected server error while updating the request.","content":{"application/json":{}}}},"security":[{"bearerAuth":[]}]}},"/absence-types":{"get":{"tags":["Absence types"],"summary":"List absence types","description":"Returns all active absence types the authenticated user is allowed to submit.","operationId":"getAbsenceTypes","responses":{"200":{"description":"List of absence types.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AbsenceTypeDto"}}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/cancellation-requests/{id}/approve":{"post":{"tags":["Cancellation requests"],"summary":"Approve a cancellation request","description":"Approves the pending cancellation request for the given absence. The absence is deleted and notification emails are sent. Requires `MANAGER` or `ADMIN` role.","operationId":"approve_1","parameters":[{"name":"id","in":"path","description":"Absence ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":42}],"responses":{"200":{"description":"Cancellation approved; returns the deleted absence snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancellationRequestDto"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin, or does not manage the absence owner."},"404":{"description":"Absence not found."},"409":{"description":"No pending cancellation request for this absence."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/cancellation-requests":{"get":{"tags":["Cancellation requests"],"summary":"List pending cancellation requests","description":"Returns all pending cancellation requests visible to the authenticated manager or admin. Requires `MANAGER` or `ADMIN` role.","operationId":"getList","responses":{"200":{"description":"List of pending cancellation requests.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CancellationRequestDto"}}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/cancellation-requests/pending-count":{"get":{"tags":["Cancellation requests"],"summary":"Get pending cancellation count","description":"Returns the number of cancellation requests waiting for a manager or admin decision. Requires `MANAGER` or `ADMIN` role.","operationId":"getPendingCount_1","responses":{"200":{"description":"Pending count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancellationPendingCountResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/cancellation-requests/{id}/reject":{"post":{"tags":["Cancellation requests"],"summary":"Reject a cancellation request","description":"Rejects the pending cancellation request. The absence is kept; the employee is notified by email. Requires `MANAGER` or `ADMIN` role.","operationId":"reject_1","parameters":[{"name":"id","in":"path","description":"Absence ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":42}],"responses":{"200":{"description":"Cancellation rejected; returns the updated absence.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancellationRequestDto"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin, or does not manage the absence owner."},"404":{"description":"Absence not found."},"409":{"description":"No pending cancellation request for this absence."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/categories":{"get":{"tags":["Categories"],"summary":"List categories","description":"Returns all active categories along with the user's default category and whether a category is required when saving a time entry.","operationId":"getCategories","responses":{"200":{"description":"Category list with default and obligation flag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoriesResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/oauth2/token":{"post":{"tags":["Authentication"],"summary":"Issue or refresh a token","description":"Supports five grant types:\n\n**`grant_type=password`** — authenticates with `username` and `password`. Returns an access token and refresh token on success. If the account has 2FA enabled, returns an MFA challenge (`mfa_required=true`, `mfa_token`, `mfa_expires_in`) instead — complete the flow with `grant_type=mfa_totp`.\n\n**`grant_type=refresh_token`** — exchanges a valid `refresh_token` for a new access token and rotates the refresh token (each refresh token can be used exactly once).\n\n**`grant_type=mfa_totp`** — second step of the 2FA flow. Submit the `mfa_token` from the password-grant challenge together with the current `totp_code` from the user's authenticator app. Returns an access token and refresh token on success.\n\n**`grant_type=sso_id_token`** — single sign-on via an ID token the mobile app already obtained from the SSO provider. Submit `provider=google` and the provider's `id_token` (a JWT). Timebutler verifies the JWT's signature, issuer and audience and returns an access token and refresh token for the linked Timebutler account.\n\n**`grant_type=sso_code`** — single sign-on via an authorization code the mobile app obtained from the SSO provider's consent screen. Submit `provider=microsoft` or `provider=slack`, the `code` from the provider, and the exact `redirect_uri` used in the authorize request. For Microsoft public-client app registrations, also send the PKCE `code_verifier` whose SHA-256 hash was used as `code_challenge` in the authorize step. Timebutler exchanges the code with the provider, identifies the user and returns the token pair.\n\nAll grant types also require `client_id`. Use `client_id=public` to authenticate without a secret, or supply both `client_id` and `client_secret` for a registered confidential client.","operationId":"token","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"grant_type":{"type":"string","description":"OAuth 2.0 grant type. Supported values: `password`, `refresh_token`, `mfa_totp`, `sso_id_token`, `sso_code`.","example":"password"},"username":{"type":"string","description":"User's email address. Required for `grant_type=password`.","example":"user@example.com"},"password":{"type":"string","description":"User's password. Required for `grant_type=password`.","example":"s3cr3t"},"refresh_token":{"type":"string","description":"Refresh token obtained from a previous token response. Required for `grant_type=refresh_token`.","example":"rt-a1b2c3d4e5f6..."},"client_id":{"type":"string","description":"Client identifier. Use `public` for unauthenticated public clients.","example":"public"},"client_secret":{"type":"string","description":"Client secret. Required for confidential clients; omit when using `client_id=public`.","example":"my-secret"},"mfa_token":{"type":"string","description":"MFA challenge token received from a `password` grant when 2FA is required. Required for `grant_type=mfa_totp`."},"totp_code":{"type":"string","description":"Current TOTP code from the user's authenticator app. Required for `grant_type=mfa_totp`."},"provider":{"type":"string","description":"SSO provider identifier. Required for `grant_type=sso_id_token` (`google`) and `grant_type=sso_code` (`microsoft`, `slack`).","example":"google"},"id_token":{"type":"string","description":"SSO ID token issued by the provider (e.g. a Google ID token JWT). Required for `grant_type=sso_id_token`."},"code":{"type":"string","description":"Authorization code returned by the SSO provider's authorize endpoint. Required for `grant_type=sso_code`."},"redirect_uri":{"type":"string","description":"Redirect URI that was used when obtaining the authorization code. Must match the value registered with the SSO provider. Required for `grant_type=sso_code`.","example":"https://app.example.com/sso/callback"},"code_verifier":{"type":"string","description":"PKCE code verifier — the plaintext value whose SHA-256 hash was sent as `code_challenge` in the authorization request. Required for `grant_type=sso_code` when the SSO provider's app registration is a public client (e.g. Microsoft Azure AD mobile app)."}}},"examples":{"password-grant":{"summary":"Password grant","value":"grant_type=password&username=user%40example.com&password=s3cr3t&client_id=public"},"refresh-token-grant":{"summary":"Refresh token grant","value":"grant_type=refresh_token&refresh_token=550e8400-e29b-41d4-a716-446655440000&client_id=public"},"mfa-totp-grant":{"summary":"MFA TOTP grant (2FA second step)","value":"grant_type=mfa_totp&mfa_token=mfa-a1b2c3d4e5f6&totp_code=123456&client_id=public"}}}}},"responses":{"200":{"description":"Token issued successfully, or MFA challenge returned (password grant with 2FA enabled). The response is an `OauthTokenResponse` (access token + refresh token) for `refresh_token` and `mfa_totp` grants, or an `MfaChallengeResponse` (MFA challenge) for a `password` grant when the account has 2FA enabled.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OauthTokenResponse"},{"$ref":"#/components/schemas/MfaChallengeResponse"}]}}}},"400":{"description":"Invalid request, unsupported grant type, or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthErrorResponse"}}}},"403":{"description":"Access denied due to IP address restriction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthErrorResponse"}}}},"429":{"description":"Too many requests from this IP address. Try again in 1 minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OauthErrorResponse"}}}}}}},"/projects":{"get":{"tags":["Projects"],"summary":"List projects","description":"Returns all active projects. The user's favourites appear first, followed by all other active projects. Also returns the user's default project and whether a project is required.","operationId":"getProjects","responses":{"200":{"description":"Project list with default and obligation flag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectsResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/substitute-employees":{"get":{"tags":["Substitute employees"],"summary":"List substitute employees","description":"Returns all active employees in the company that may serve as a substitute for the authenticated user. The authenticated user is excluded from the list.","operationId":"getSubstituteEmployees","responses":{"200":{"description":"List of substitute employees.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SubstituteEmployeeDto"}}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/substitute-requests/{id}/accept":{"post":{"tags":["Substitute requests"],"summary":"Accept a substitute request","description":"Accepts the substitute request for the given absence. Only the designated substitute may call this endpoint.","operationId":"accept","parameters":[{"name":"id","in":"path","description":"Absence ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":42}],"responses":{"200":{"description":"Request accepted; returns the updated absence.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubstituteRequestDto"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not the designated substitute."},"404":{"description":"Absence not found."},"409":{"description":"Request cannot be accepted in its current state (already accepted, rejected, or no acceptance required)."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/substitute-requests/pending-count":{"get":{"tags":["Substitute requests"],"summary":"Get pending substitute request count","description":"Returns the number of substitute requests that are still waiting for the authenticated user's response.","operationId":"getPendingCount_2","responses":{"200":{"description":"Pending count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubstitutePendingCountResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/substitute-requests":{"get":{"tags":["Substitute requests"],"summary":"List substitute requests","description":"Returns all substitute requests directed at the authenticated user, regardless of their status (pending, accepted, rejected).","operationId":"getSubstituteRequests","responses":{"200":{"description":"List of substitute requests.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SubstituteRequestDto"}}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/substitute-requests/{id}/reject":{"post":{"tags":["Substitute requests"],"summary":"Reject a substitute request","description":"Rejects the substitute request for the given absence. Only the designated substitute may call this endpoint.","operationId":"reject_2","parameters":[{"name":"id","in":"path","description":"Absence ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":42}],"responses":{"200":{"description":"Request rejected; returns the updated absence.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubstituteRequestDto"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not the designated substitute."},"404":{"description":"Absence not found."},"409":{"description":"Request cannot be rejected in its current state."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/business-trip-end":{"post":{"tags":["Time clock"],"summary":"End a business trip","description":"Marks the end of an active business trip on the current clock. Returns 409 if no business trip is currently active.","operationId":"businessTripEnd","responses":{"200":{"description":"Business trip ended; returns current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Business trips not enabled, or time tracking not active."},"401":{"description":"Missing or invalid bearer token."},"409":{"description":"No business trip is currently active."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/business-trip-start":{"post":{"tags":["Time clock"],"summary":"Start a business trip","description":"Marks the start of a business trip on the currently running or paused clock. Returns 400 if business trips are not enabled for this company; returns 409 if the clock is not active or a business trip is already in progress.","operationId":"businessTripStart","responses":{"200":{"description":"Business trip started; returns current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Business trips not enabled, or time tracking not active."},"401":{"description":"Missing or invalid bearer token."},"409":{"description":"Clock is not running/paused, or a business trip is already active."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/cancel":{"post":{"tags":["Time clock"],"summary":"Cancel the time clock","description":"Discards the current clock session without saving a time entry. Returns 409 if the clock is already `idle`.","operationId":"cancel","responses":{"200":{"description":"Clock cancelled; returns `idle` status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"409":{"description":"Clock is already `idle`."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/status":{"get":{"tags":["Time clock"],"summary":"Get time clock status","description":"Returns the current state of the time clock: `idle`, `running`, `paused`, or `waiting`. Also returns elapsed time, pause time, and business-trip flag.","operationId":"getStatus","responses":{"200":{"description":"Current clock status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/pause":{"post":{"tags":["Time clock"],"summary":"Pause the time clock","description":"Pauses a running time clock. Returns 409 if the clock is not in `running` state.","operationId":"pause","responses":{"200":{"description":"Clock paused; returns current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"409":{"description":"Clock is not running."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/resume":{"post":{"tags":["Time clock"],"summary":"Resume the time clock","description":"Resumes a paused time clock. Returns 409 if the clock is not in `paused` state.","operationId":"resume","responses":{"200":{"description":"Clock resumed; returns current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"409":{"description":"Clock is not paused."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/start":{"post":{"tags":["Time clock"],"summary":"Start the time clock","description":"Starts the time clock for the authenticated user. Returns 409 if the clock is already running. The clock may enter `waiting` state if a minimum start interval is configured.","operationId":"start","responses":{"200":{"description":"Clock started; returns current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStatusDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"User is not permitted to create time entries."},"409":{"description":"Clock is already running, or start conditions are not met."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-clock/stop":{"post":{"tags":["Time clock"],"summary":"Stop and save the time clock","description":"Stops the running or paused clock and saves the resulting time entry. An optional project ID and remarks can be provided. If the clock was in `waiting` state it is discarded without saving and 204 is returned. Returns 409 if the total duration is too short (< 1 minute) or if a duplicate entry is detected.\n\nIf the session violates the company's break-time regulation and no `breakRegulation` decision was sent, the entry is **not** saved: the response has `outcome: confirmationRequired` and a `breakRegulation` object describing the required change. Re-send the stop with `breakRegulation: apply` (accept the correction) or `breakRegulation: ignore` (save unchanged). Clients should only offer `ignore` when the regulation is not obligatory (`breakRegulation.obligatory` in the response); the server does not currently enforce this. When the company has auto-apply configured, the correction is applied on the first stop without a decision.","operationId":"stop","requestBody":{"description":"Optional project ID, category ID, and remarks for the saved entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStopRequest"}}}},"responses":{"200":{"description":"Either the saved entry details (`outcome: saved`) or a break-regulation confirmation request (`outcome: confirmationRequired`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeClockStopResponse"}}}},"204":{"description":"Clock was in `waiting` state and was discarded without saving."},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"409":{"description":"Clock is not active, duration is too short, or duplicate entry detected."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-entry-requests/{id}/approve":{"post":{"tags":["Time entry requests"],"summary":"Approve a time-entry request","description":"Approves the pending change or deletion request for the given time entry. For deletion requests the entry is removed; for change requests the entry is updated to the proposed values. Requires `MANAGER` or `ADMIN` role.","operationId":"approve_2","parameters":[{"name":"id","in":"path","description":"Time entry ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":123}],"responses":{"200":{"description":"Request approved; returns the updated time entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeEntryRequestDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin, or only admins may approve in this company."},"404":{"description":"Time entry not found."},"409":{"description":"No pending request, or the proposed change has a scheduling conflict."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-entry-requests":{"get":{"tags":["Time entry requests"],"summary":"List pending time-entry requests","description":"Returns all time-entry change/deletion requests in the manager's team that are waiting for approval. Requires `MANAGER` or `ADMIN` role. Returns an empty list if time tracking is not active or the caller is a manager in an admin-only-approval company.","operationId":"getList_1","responses":{"200":{"description":"List of pending time-entry requests.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TimeEntryRequestDto"}}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-entry-requests/{id}/reject":{"post":{"tags":["Time entry requests"],"summary":"Reject a time-entry request","description":"Rejects the pending change or deletion request for the given time entry. An optional rejection reason can be included in the request body. The employee is notified by email. Requires `MANAGER` or `ADMIN` role.","operationId":"reject_3","parameters":[{"name":"id","in":"path","description":"Time entry ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":123}],"requestBody":{"description":"Optional rejection reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectRequest"}}}},"responses":{"200":{"description":"Request rejected; returns the time entry in its current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeEntryRequestDto"}}}},"400":{"description":"Time tracking not active for this company."},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin, or only admins may reject in this company."},"404":{"description":"Time entry not found."},"409":{"description":"No pending request for this entry."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-entries/{id}":{"delete":{"tags":["Time entries"],"summary":"Delete a time entry","description":"Deletes the specified time entry. If the company's time-tracking settings require manager approval for deletions, a deletion request is submitted instead of an immediate delete. The response field `result` is either `deleted` or `deletion_requested`.","operationId":"delete_1","parameters":[{"name":"id","in":"path","description":"Time entry ID.","required":true,"schema":{"type":"integer","format":"int32"},"example":123}],"responses":{"200":{"description":"Entry deleted or deletion request submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResultResponse"}}}},"400":{"description":"Invalid ID or time tracking is not active for this company."},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not allowed to delete this entry."},"404":{"description":"Time entry not found."},"409":{"description":"Entry is locked by the month-close restriction."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-entries/pending-count":{"get":{"tags":["Time entries"],"summary":"Get pending time-entry count","description":"Returns the number of time entries in the manager's team that are awaiting approval. Requires `MANAGER` or `ADMIN` role.","operationId":"getPendingCount_3","responses":{"200":{"description":"Pending count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeEntryPendingCountResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Caller is not a manager or admin."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-tracking/clock-entry":{"post":{"tags":["Time tracking"],"summary":"Submit a completed clock entry","description":"Creates a time entry from explicit start and end Unix timestamps (milliseconds). Start and end must be on the same calendar day. If the company requires manager approval for new entries, the entry is created with status `pending`; otherwise it is immediately `done`.","operationId":"createClockEntry","requestBody":{"description":"Clock entry with start/end timestamps and optional project and remarks.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClockEntryRequest"}}},"required":true},"responses":{"200":{"description":"Entry created; returns the entry ID and worked/pause minutes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClockEntryResponse"}}}},"400":{"description":"Missing or invalid request body, invalid timestamps, or time tracking not active."},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"User is not allowed to create time entries."},"422":{"description":"Remarks exceed maximum length."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-tracking/time-off-summary":{"get":{"tags":["Time tracking"],"summary":"Get time-off summary","description":"Returns the authenticated user's remaining and total vacation days for the current year, plus the number of illness days taken.","operationId":"getTimeOffSummary","responses":{"200":{"description":"Time-off summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeOffSummaryResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/time-tracking/work-summary":{"get":{"tags":["Time tracking"],"summary":"Get today's work summary","description":"Returns the authenticated user's work commitment, worked minutes, remaining minutes, completion percentage, and break minutes for today.","operationId":"getWorkSummary","responses":{"200":{"description":"Today's work summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkSummaryResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}},"/user/profile":{"get":{"tags":["User profile"],"summary":"Get profile","description":"Returns the authenticated user's display name, email address, role (`USER`, `MANAGER`, `ADMIN`), department, branch office, and avatar URL.","operationId":"getProfile","responses":{"200":{"description":"Profile data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"500":{"description":"Unexpected server error."}},"security":[{"bearerAuth":[]}]}}},"components":{"schemas":{"AbsenceRequestDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the absence request.","example":"12345"},"type":{"type":"string","description":"Absence type code (3 characters), e.g. `\"URL\"` for vacation.","example":"URL"},"startDate":{"type":"string","description":"First day of the absence in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-01"},"endDate":{"type":"string","description":"Last day of the absence in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-05"},"days":{"type":"number","description":"Number of working days the absence spans, calculated according to the employee's working-days schedule.","format":"double","example":4.0},"status":{"type":"string","description":"Current status of the request. One of `pending` (awaiting manager approval), `approved`, or `rejected`.","example":"pending","enum":["pending","approved","rejected"]},"halfDay":{"type":"string","description":"Half-day indicator. `\"morning\"` or `\"afternoon\"` for half-day requests; `null` for full-day absences.","nullable":true,"example":"morning","enum":["morning","afternoon"]},"substitutePending":{"type":"boolean","description":"Whether the substitute employee's confirmation is still pending. `null` if no substitute was set.","nullable":true,"example":false},"medicalCertificateProvided":{"type":"boolean","description":"Whether a medical certificate has been uploaded for this absence. Only present for absence types that require a sick note; `null` otherwise.","nullable":true,"example":false},"employeeName":{"type":"string","description":"Display name of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.","nullable":true,"example":"Maria Müller"},"employeeId":{"type":"string","description":"Internal ID of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.","nullable":true,"example":"42"}}},"CreateAbsenceRequest":{"required":["absenceTypeId","endDate","startDate"],"type":"object","properties":{"absenceTypeId":{"type":"string","description":"Absence type code (3 characters), e.g. `\"URL\"` for vacation. Must match an active type from `GET /absence-types`.","example":"URL"},"startDate":{"type":"string","description":"First day of the absence in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-01"},"endDate":{"type":"string","description":"Last day of the absence in ISO-8601 format (`YYYY-MM-DD`). Must be on or after `startDate`. For half-day requests must equal `startDate`.","example":"2025-08-05"},"halfDay":{"type":"string","description":"Set to `\"morning\"` or `\"afternoon\"` to request a single half-day. When set, `startDate` and `endDate` must be the same day. Omit or send `null` for a full-day absence.","example":"morning","enum":["morning","afternoon"]},"comments":{"type":"string","description":"Optional free-text note visible to the employee's managers.","example":"Holiday trip"},"substituteEmployeeId":{"type":"string","description":"Employee ID of the designated substitute. Must belong to the same company. Omit or send `null` if no substitute is needed.","example":"42"}}},"DeleteAbsenceRequest":{"type":"object","properties":{"reason":{"type":"string","description":"Optional free-text reason for the cancellation. Required when the company's configuration mandates a reason for cancellation requests.","example":"Plans changed"}}},"DeleteResultResponse":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the affected record.","example":"12345"},"result":{"type":"string","description":"Outcome of the operation. `\"deleted\"` if the record was removed outright; `\"cancellation_requested\"` if a cancellation request was created instead; `\"deletion_requested\"` if a deletion request was created (for time entries that require approval).","example":"deleted","enum":["deleted","cancellation_requested","deletion_requested"]}}},"AbsencePendingCountResponse":{"type":"object","properties":{"numberOfAbsenceRequests":{"type":"integer","description":"Number of absence requests in the caller's team that are waiting for the caller's approval.","format":"int32","example":3},"numberOfAbsenceRequestCancellations":{"type":"integer","description":"Number of cancellation requests in the caller's team that are waiting for approval.","format":"int32","example":1}}},"RejectRequest":{"type":"object","properties":{"reason":{"type":"string","description":"Optional free-text reason for the rejection. Stored on the request and shown to the employee.","example":"Insufficient staffing during that period"}}},"UpdateAbsenceRequest":{"type":"object","properties":{"absenceTypeId":{"type":"string","description":"New absence type code (3 characters). Send `null` to keep the existing type.","example":"URL"},"startDate":{"type":"string","description":"New start date in ISO-8601 format (`YYYY-MM-DD`). Send `null` to keep the existing value.","example":"2025-08-01"},"endDate":{"type":"string","description":"New end date in ISO-8601 format (`YYYY-MM-DD`). Must be on or after `startDate`. Send `null` to keep the existing value.","example":"2025-08-05"},"halfDay":{"type":"string","description":"New half-day setting. `\"morning\"` or `\"afternoon\"` to switch to a half-day; empty string `\"\"` to remove half-day and make it a full day; `null` to keep the existing value.","example":"morning","enum":["morning","afternoon",""]},"comments":{"type":"string","description":"New free-text comment. Send `null` to keep the existing comment; send an empty string to clear it.","example":"Rescheduled trip"},"substituteEmployeeId":{"type":"string","description":"New substitute employee ID. Send `null` to keep the existing substitute; send `\"0\"` or an empty string to remove the substitute.","example":"42"}}},"AbsenceTypeDto":{"type":"object","properties":{"id":{"type":"string","description":"Absence type code (3 characters) used when creating or updating an absence request.","example":"URL"},"name":{"type":"string","description":"Human-readable name of the absence type in the user's language.","example":"Urlaub"},"needsApproval":{"type":"boolean","description":"Whether requests of this type require manager approval before they are considered approved. If `false`, the request is approved immediately on creation.","example":true},"requiresMedicalCertificate":{"type":"boolean","description":"Whether a medical certificate (sick note) must be uploaded for absences of this type.","example":false}}},"CancellationRequestDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the absence request being cancelled.","example":"12345"},"employeeId":{"type":"string","description":"Internal ID of the employee who submitted the original absence request.","example":"42"},"employeeName":{"type":"string","description":"Display name of the employee who submitted the original absence request.","example":"Maria Müller"},"absenceType":{"type":"string","description":"Absence type code (3 characters) of the request being cancelled.","example":"URL"},"startDate":{"type":"string","description":"Start date of the absence being cancelled, in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-01"},"endDate":{"type":"string","description":"End date of the absence being cancelled, in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-05"},"days":{"type":"number","description":"Number of working days the absence spans.","format":"double","example":4.0},"status":{"type":"string","description":"Current status of the cancellation request. One of `pending` (awaiting approval) or `approved`.","example":"pending","enum":["pending","approved"]},"cancelComments":{"type":"string","description":"Optional free-text reason provided by the employee when requesting the cancellation.","nullable":true,"example":"Plans changed"}}},"CancellationPendingCountResponse":{"type":"object","properties":{"numberOfCancellationRequests":{"type":"integer","description":"Number of cancellation requests in the caller's team that are waiting for approval.","format":"int32","example":2}}},"CategoriesResponse":{"type":"object","properties":{"categories":{"type":"array","description":"List of categories available to the authenticated user's company.","items":{"$ref":"#/components/schemas/CategoryDto"}},"defaultCategoryId":{"type":"string","description":"ID of the category pre-selected by default when creating a time entry. `null` if no default is configured.","nullable":true,"example":"7"},"isCategoryMandatory":{"type":"boolean","description":"Whether the employee must select a category when creating a time entry.","example":false}}},"CategoryDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the category, used when submitting a time entry.","example":"7"},"name":{"type":"string","description":"Human-readable name of the category.","example":"Internal project"}},"description":"List of categories available to the authenticated user's company."},"OauthTokenResponse":{"type":"object","properties":{"access_token":{"type":"string","description":"JWT access token. Pass as `Authorization: Bearer <token>` on subsequent requests.","example":"eyJhbGciOiJSUzI1NiJ9..."},"refresh_token":{"type":"string","description":"Opaque refresh token. Exchange it for a new access token via `grant_type=refresh_token`. Each refresh token can be used exactly once.","example":"rt-a1b2c3d4e5f6..."},"token_type":{"type":"string","description":"Token type. Always `Bearer`.","example":"Bearer"},"expires_in":{"type":"integer","description":"Access token lifetime in seconds.","format":"int32","example":3600},"scope":{"type":"string","description":"Granted scope.","example":"read"}}},"MfaChallengeResponse":{"type":"object","properties":{"mfa_required":{"type":"boolean","description":"Always `true` — indicates a 2FA step is required.","example":true},"mfa_token":{"type":"string","description":"Short-lived token identifying this MFA challenge. Pass it as `mfa_token` with `grant_type=mfa_totp`.","example":"mfa-a1b2c3d4e5f6..."},"mfa_expires_in":{"type":"integer","description":"Lifetime of the MFA token in seconds.","format":"int32","example":300}},"description":"Returned by `grant_type=password` when the account has 2FA enabled. Exchange the `mfa_token` for a full token pair by calling this endpoint again with `grant_type=mfa_totp`."},"OauthErrorResponse":{"type":"object","properties":{"error":{"type":"string","description":"OAuth 2.0 error code (e.g. `invalid_grant`, `invalid_client`).","example":"invalid_grant"},"error_description":{"type":"string","description":"Human-readable description of the error.","example":"Invalid username or password"}}},"ProjectDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the project, used when submitting a time entry.","example":"5"},"name":{"type":"string","description":"Human-readable name of the project.","example":"Website Relaunch"},"isFavorite":{"type":"boolean","description":"Whether this project is marked as a favourite by the authenticated user.","example":true}},"description":"List of projects available to the authenticated user's company."},"ProjectsResponse":{"type":"object","properties":{"projects":{"type":"array","description":"List of projects available to the authenticated user's company.","items":{"$ref":"#/components/schemas/ProjectDto"}},"defaultProjectId":{"type":"string","description":"ID of the project pre-selected by default when creating a time entry. `null` if no default is configured.","nullable":true,"example":"5"},"isProjectMandatory":{"type":"boolean","description":"Whether the employee must select a project when creating a time entry.","example":true}}},"SubstituteEmployeeDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal employee ID, used as `substituteEmployeeId` when creating an absence request.","example":"42"},"name":{"type":"string","description":"Display name of the employee.","example":"Thomas Schmidt"},"department":{"type":"string","description":"Department the employee belongs to. `null` if the company does not use departments.","nullable":true,"example":"Engineering"}}},"SubstituteRequestDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the absence request for which the substitute was requested.","example":"12345"},"employeeId":{"type":"string","description":"Internal ID of the employee who is absent and needs a substitute.","example":"7"},"employeeName":{"type":"string","description":"Display name of the absent employee.","example":"Anna Bauer"},"absenceType":{"type":"string","description":"Absence type code (3 characters) of the absence for which this substitute was requested.","example":"URL"},"startDate":{"type":"string","description":"Start date of the absence, in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-01"},"endDate":{"type":"string","description":"End date of the absence, in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-08-05"},"days":{"type":"number","description":"Number of working days the absence spans.","format":"double","example":4.0},"status":{"type":"string","description":"Current status of the substitute confirmation. One of `pending` (awaiting the substitute's confirmation) or `confirmed`.","example":"pending","enum":["pending","confirmed"]}}},"SubstitutePendingCountResponse":{"type":"object","properties":{"numberOfSubstituteRequests":{"type":"integer","description":"Number of substitute requests awaiting the caller's confirmation.","format":"int32","example":1}}},"TimeClockStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"Current state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).","example":"running","enum":["idle","running","paused","waiting"]},"startTimestamp":{"type":"integer","description":"Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.","format":"int64","nullable":true,"example":1753700400000},"pauseTimestamp":{"type":"integer","description":"Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.","format":"int64","nullable":true,"example":1753714800000},"workTimeElapsedSeconds":{"type":"integer","description":"Total working time elapsed in the current session, in seconds (excludes breaks).","format":"int32","example":14400},"breakElapsedSeconds":{"type":"integer","description":"Duration of the current ongoing break, in seconds. `0` if the clock is not paused.","format":"int32","example":0},"accumulatedBreakSeconds":{"type":"integer","description":"Total accumulated break time in the current session, in seconds (including any completed breaks).","format":"int32","example":1800},"waitSeconds":{"type":"integer","description":"Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.","format":"int32","nullable":true,"example":0},"isBusinessTripActive":{"type":"boolean","description":"Whether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.","example":false}}},"TimeClockBreakRegulationInfo":{"type":"object","properties":{"hasNoRemedy":{"type":"boolean","description":"`true` when the violation cannot be resolved automatically (e.g. the daily maximum working time is already exceeded). The session can then only be discarded or, if the regulation is not obligatory, saved unchanged with `ignore`.","example":false},"obligatory":{"type":"boolean","description":"`true` when the break regulation is obligatory for this company. When obligatory the session must not be saved unchanged; the client hides the `ignore` option.","example":false},"correctionWorkMinutes":{"type":"integer","description":"Minutes that would be deducted from working time if the correction is applied.","format":"int32","example":30},"correctionBreakMinutes":{"type":"integer","description":"Minutes that would be added to break time if the correction is applied.","format":"int32","example":30},"resultingWorkMinutes":{"type":"integer","description":"Resulting net working time in minutes after the correction is applied.","format":"int32","example":450},"resultingBreakMinutes":{"type":"integer","description":"Resulting total break time in minutes after the correction is applied.","format":"int32","example":30},"detailText":{"type":"string","description":"Human-readable, localized summary of the required change, suitable for display.","example":"Required change: working time -0:30, break +0:30"}},"description":"Break-regulation violation details. Present only when `outcome` is `confirmationRequired`; null otherwise."},"TimeClockStopResponse":{"type":"object","properties":{"outcome":{"type":"string","description":"`saved` when the entry was persisted, or `confirmationRequired` when a break-regulation decision is needed before saving.","example":"saved","enum":["saved","confirmationRequired"]},"id":{"type":"string","description":"Internal ID of the time entry created when the clock was stopped. Null when `outcome` is `confirmationRequired`.","example":"98765"},"workedMinutes":{"type":"integer","description":"Net working time recorded for the session, in minutes (total duration minus breaks). Null when `outcome` is `confirmationRequired`.","format":"int32","example":450},"breakMinutes":{"type":"integer","description":"Total break time recorded for the session, in minutes. Null when `outcome` is `confirmationRequired`.","format":"int32","example":30},"breakRegulationApplied":{"type":"boolean","description":"`true` when a break-regulation correction was applied to the saved entry (working time reduced, break increased).","example":true},"breakRegulationIgnored":{"type":"boolean","description":"`true` when the entry was saved unchanged despite a break-regulation violation (the user chose to ignore it).","example":false},"breakRegulation":{"$ref":"#/components/schemas/TimeClockBreakRegulationInfo"}}},"TimeClockStopRequest":{"type":"object","properties":{"projectId":{"type":"integer","description":"ID of the project to assign the completed session to. Send `0` if no project assignment is needed.","format":"int32","example":5},"categoryId":{"type":"integer","description":"ID of the category to assign the completed session to. Send `0` if no category assignment is needed.","format":"int32","example":7},"remarks":{"type":"string","description":"Optional free-text note for the completed time entry.","example":"Code review"},"breakRegulation":{"type":"string","description":"Decision on how to handle a break-time-regulation violation. Omit on the first stop attempt: if the session violates the break regulation the server does not save and instead returns `outcome: confirmationRequired` describing the required change. Re-send the stop with `apply` to accept the correction (reduce working time and add the missing break) or `ignore` to save the session unchanged. Clients should only offer `ignore` when the regulation is not obligatory (`obligatory` in the confirmation response); the server does not currently enforce this.","example":"apply","enum":["apply","ignore"]}}},"TimeEntryRequestDto":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the time entry.","example":"98765"},"employeeId":{"type":"string","description":"Internal ID of the employee who submitted the time entry. Only present in team/manager views.","nullable":true,"example":"42"},"employeeName":{"type":"string","description":"Display name of the employee who submitted the time entry. Only present in team/manager views.","nullable":true,"example":"Maria Müller"},"date":{"type":"string","description":"Date of the time entry in ISO-8601 format (`YYYY-MM-DD`).","example":"2025-07-28"},"startTime":{"type":"string","description":"Clock-in time in `HH:mm` format.","example":"08:00"},"endTime":{"type":"string","description":"Clock-out time in `HH:mm` format.","example":"17:00"},"workedMinutes":{"type":"integer","description":"Net working time for this entry, in minutes (total duration minus breaks).","format":"int32","example":480},"breakMinutes":{"type":"integer","description":"Total break time for this entry, in minutes.","format":"int32","example":60},"project":{"type":"string","description":"Name of the project assigned to this entry. `null` if no project was assigned.","nullable":true,"example":"Website Relaunch"},"category":{"type":"string","description":"Name of the category assigned to this entry. `null` if no category was assigned.","nullable":true,"example":"Internal project"},"remarks":{"type":"string","description":"Free-text note attached to this entry. `null` if no remark was entered.","nullable":true,"example":"Sprint planning"},"requestType":{"type":"string","description":"Type of request that produced this entry. One of `manual` (entered by hand) or `clock` (created by stopping the time clock).","example":"manual","enum":["manual","clock"]},"status":{"type":"string","description":"Approval status of this time entry. One of `pending` (awaiting manager approval) or `approved`.","example":"pending","enum":["pending","approved"]}}},"TimeEntryPendingCountResponse":{"type":"object","properties":{"numberOfTimeEntries":{"type":"integer","description":"Number of time entries in the caller's team that are waiting for approval.","format":"int32","example":5}}},"ClockEntryResponse":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the created time entry.","example":"98765"},"workedMinutes":{"type":"integer","description":"Net working time recorded for this entry, in minutes (total duration minus breaks).","format":"int32","example":480},"breakMinutes":{"type":"integer","description":"Total break time recorded for this entry, in minutes.","format":"int32","example":30}}},"ClockEntryRequest":{"required":["endTimestamp","startTimestamp"],"type":"object","properties":{"startTimestamp":{"type":"integer","description":"Start of the work period as a Unix timestamp in milliseconds.","format":"int64","example":1753700400000},"endTimestamp":{"type":"integer","description":"End of the work period as a Unix timestamp in milliseconds.","format":"int64","example":1753729200000},"pauseSeconds":{"type":"integer","description":"Total break duration within the work period, in seconds.","format":"int32","example":1800},"projectId":{"type":"integer","description":"ID of the project to assign this entry to. Send `0` if no project assignment is needed.","format":"int32","example":5},"remarks":{"type":"string","description":"Optional free-text note for this time entry.","example":"Sprint planning"}}},"TimeOffSummaryResponse":{"type":"object","properties":{"vacationDaysRemaining":{"type":"number","description":"Number of vacation days the employee still has available in the current year, after accounting for approved and pending requests.","format":"double","example":12.5},"vacationDaysTotal":{"type":"number","description":"Total vacation day entitlement for the current year.","format":"double","example":30.0},"illnessDaysTaken":{"type":"number","description":"Number of sick-leave days taken in the current year.","format":"double","example":3.0}}},"WorkSummaryResponse":{"type":"object","properties":{"commitmentMinutes":{"type":"integer","description":"Total working time the employee is contractually required to work in the queried period, in minutes.","format":"int32","example":2400},"workedMinutes":{"type":"integer","description":"Total working time the employee has actually worked in the queried period, in minutes.","format":"int32","example":2280},"remainingMinutes":{"type":"integer","description":"Remaining working time still to be worked in the queried period, in minutes. Negative values indicate overtime.","format":"int32","example":120},"percentDone":{"type":"integer","description":"Percentage of the contractual working time that has been worked so far, rounded to the nearest integer.","format":"int32","example":95},"breakMinutes":{"type":"integer","description":"Total break time recorded in the queried period, in minutes.","format":"int32","example":300}}},"UserProfileResponse":{"type":"object","properties":{"id":{"type":"string","description":"Internal ID of the authenticated user.","example":"42"},"name":{"type":"string","description":"Full display name of the user.","example":"Maria Müller"},"email":{"type":"string","description":"Email address of the user.","example":"maria.mueller@example.com"},"role":{"type":"string","description":"Role of the user within the company. One of `USER`, `MANAGER`, or `ADMIN`.","example":"MANAGER","enum":["USER","MANAGER","ADMIN"]},"department":{"type":"string","description":"Name of the department the user belongs to. `null` if the company does not use departments.","nullable":true,"example":"Engineering"},"branchOffice":{"type":"string","description":"Name of the branch office the user is assigned to. `null` if the company does not use branch offices.","nullable":true,"example":"Berlin"},"country":{"type":"object","description":"Country of the user as configured in their profile. The structure depends on the company's configuration.","nullable":true},"avatar":{"type":"string","description":"URL of the user's profile picture. `null` if no avatar has been uploaded.","nullable":true,"example":"/images/avatar/42/abc123.jpg?v=5"},"isBusinessTripEnabled":{"type":"boolean","description":"Whether the business trip feature is enabled for this user. `null` if the feature is not available in the company's plan.","nullable":true,"example":true},"isTimeTrackingEnabled":{"type":"boolean","description":"Whether time tracking is active for the user's company.","example":true}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}}}