Timebutler API v2

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.

Authentication

OAuth 2.0 token endpoint. Issues JWT access tokens and rotating refresh tokens. This endpoint is public — no bearer token is required.

Personal Access Tokens

As an alternative to the OAuth 2.0 password flow, individual users can create personal access tokens. Tokens are long-lived, named, revocable, and scoped to a single user — no password sharing or refresh-token rotation required.

Tokens have the prefix tb_ followed by an opaque, URL-safe Base64 string. Send them in the Authorization header just like any other bearer credential:

curl "https://app.timebutler.com/api/v2/timeentry/pendingcount" \
  -H "Authorization: Bearer tb_a1b2c3d4e5f6..."

Create or revoke a personal token →

POST/oauth2/token

Issue or refresh a token

Supports five grant types: **`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`. **`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). **`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. **`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. **`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. All 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.

Request Body
Request Body schema:
NameTypeDescription
grant_typestringOAuth 2.0 grant type. Supported values: `password`, `refresh_token`, `mfa_totp`, `sso_id_token`, `sso_code`.
usernamestringUser's email address. Required for `grant_type=password`.
passwordstringUser's password. Required for `grant_type=password`.
refresh_tokenstringRefresh token obtained from a previous token response. Required for `grant_type=refresh_token`.
client_idstringClient identifier. Use `public` for unauthenticated public clients.
client_secretstringClient secret. Required for confidential clients; omit when using `client_id=public`.
mfa_tokenstringMFA challenge token received from a `password` grant when 2FA is required. Required for `grant_type=mfa_totp`.
totp_codestringCurrent TOTP code from the user's authenticator app. Required for `grant_type=mfa_totp`.
providerstringSSO provider identifier. Required for `grant_type=sso_id_token` (`google`) and `grant_type=sso_code` (`microsoft`, `slack`).
id_tokenstringSSO ID token issued by the provider (e.g. a Google ID token JWT). Required for `grant_type=sso_id_token`.
codestringAuthorization code returned by the SSO provider's authorize endpoint. Required for `grant_type=sso_code`.
redirect_uristringRedirect URI that was used when obtaining the authorization code. Must match the value registered with the SSO provider. Required for `grant_type=sso_code`.
code_verifierstringPKCE 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).
Responses
200Token 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.
Response samples
{ }
400Invalid request, unsupported grant type, or invalid credentials.
Response Schema:
NameTypeDescription
errorstringOAuth 2.0 error code (e.g. `invalid_grant`, `invalid_client`).
error_descriptionstringHuman-readable description of the error.
Response samples
{
  "error" : "invalid_grant",
  "error_description" : "Invalid username or password"
}
403Access denied due to IP address restriction.
Response Schema:
NameTypeDescription
errorstringOAuth 2.0 error code (e.g. `invalid_grant`, `invalid_client`).
error_descriptionstringHuman-readable description of the error.
Response samples
{
  "error" : "invalid_grant",
  "error_description" : "Invalid username or password"
}
429Too many requests from this IP address. Try again in 1 minute.
Response Schema:
NameTypeDescription
errorstringOAuth 2.0 error code (e.g. `invalid_grant`, `invalid_client`).
error_descriptionstringHuman-readable description of the error.
Response samples
{
  "error" : "invalid_grant",
  "error_description" : "Invalid username or password"
}
500Unexpected server error.
Response Schema:
NameTypeDescription
errorstringOAuth 2.0 error code (e.g. `invalid_grant`, `invalid_client`).
error_descriptionstringHuman-readable description of the error.
Response samples
{
  "error" : "invalid_grant",
  "error_description" : "Invalid username or password"
}
Request samples
Password grant
curl -X POST "https://app.timebutler.com/api/v2/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=password&username=user%40example.com&password=s3cr3t&client_id=public'
Refresh token grant
curl -X POST "https://app.timebutler.com/api/v2/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=refresh_token&refresh_token=550e8400-e29b-41d4-a716-446655440000&client_id=public'
MFA TOTP grant (2FA second step)
curl -X POST "https://app.timebutler.com/api/v2/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=mfa_totp&mfa_token=mfa-a1b2c3d4e5f6&totp_code=123456&client_id=public'

Absence requests

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.

POST/absence-requests/{id}/approve

Approve a pending absence request

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.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Internal ID of the absence request to approve.
Responses
200Request after the approval has been recorded.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request.
typestringAbsence type code (3 characters), e.g. `"URL"` for vacation.
startDatestringFirst day of the absence in ISO-8601 format (`YYYY-MM-DD`).
endDatestringLast day of the absence in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans, calculated according to the employee's working-days schedule.
statusstringCurrent status of the request. One of `pending` (awaiting manager approval), `approved`, or `rejected`.
Possible values: pending approved rejected
halfDaystringHalf-day indicator. `"morning"` or `"afternoon"` for half-day requests; `null` for full-day absences.
Possible values: morning afternoon
substitutePendingbooleanWhether the substitute employee's confirmation is still pending. `null` if no substitute was set.
medicalCertificateProvidedbooleanWhether a medical certificate has been uploaded for this absence. Only present for absence types that require a sick note; `null` otherwise.
employeeNamestringDisplay name of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
employeeIdstringInternal ID of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
Response samples
{
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
}
401Missing or invalid bearer token.
403Caller is not a manager or admin, or is not permitted to act on this particular request.
404No request with the given `id` exists for the caller's company.
409Request is not in `pending` state (already approved, rejected, or deleted).
500Unexpected server error while updating the request.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/absence-requests/{id}/approve" \
  -H "Authorization: Bearer <token>"
GET/absence-requests

List the caller's own absence requests (alias)

Convenience alias that returns the same result as `GET /my`. Prefer `/my` in new integrations — this path exists for backwards compatibility.

Authorizations:
bearerAuth
Responses
200List of the caller's requests. Empty array if there are none.
Response samples
[ {
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
} ]
401Missing or invalid bearer token.
403Caller's account is inactive.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/absence-requests" \
  -H "Authorization: Bearer <token>"
POST/absence-requests

Create a new absence request

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`.

Authorizations:
bearerAuth
Request Body

Fields of the new absence request.

Request Body schema:
NameTypeDescription
absenceTypeIdrequiredstringAbsence type code (3 characters), e.g. `"URL"` for vacation. Must match an active type from `GET /absence-types`.
startDaterequiredstringFirst day of the absence in ISO-8601 format (`YYYY-MM-DD`).
endDaterequiredstringLast day of the absence in ISO-8601 format (`YYYY-MM-DD`). Must be on or after `startDate`. For half-day requests must equal `startDate`.
halfDaystringSet 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.
Possible values: morning afternoon
commentsstringOptional free-text note visible to the employee's managers.
substituteEmployeeIdstringEmployee ID of the designated substitute. Must belong to the same company. Omit or send `null` if no substitute is needed.
Responses
200Created request, in the state the server assigned.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request.
typestringAbsence type code (3 characters), e.g. `"URL"` for vacation.
startDatestringFirst day of the absence in ISO-8601 format (`YYYY-MM-DD`).
endDatestringLast day of the absence in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans, calculated according to the employee's working-days schedule.
statusstringCurrent status of the request. One of `pending` (awaiting manager approval), `approved`, or `rejected`.
Possible values: pending approved rejected
halfDaystringHalf-day indicator. `"morning"` or `"afternoon"` for half-day requests; `null` for full-day absences.
Possible values: morning afternoon
substitutePendingbooleanWhether the substitute employee's confirmation is still pending. `null` if no substitute was set.
medicalCertificateProvidedbooleanWhether a medical certificate has been uploaded for this absence. Only present for absence types that require a sick note; `null` otherwise.
employeeNamestringDisplay name of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
employeeIdstringInternal ID of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
Response samples
{
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
}
400Missing, malformed or mutually inconsistent fields (invalid `absenceTypeId`, unparseable date, `endDate < startDate`, bad `halfDay` value, or unknown `substituteEmployeeId`).
401Missing or invalid bearer token.
403Caller's role is not allowed to enter this absence type.
500Unexpected server error while persisting the request.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/absence-requests" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"absenceTypeId":"URL","startDate":"2025-08-01","endDate":"2025-08-05","halfDay":"morning","comments":"Holiday trip","substituteEmployeeId":"42"}'
PUT/absence-requests/{id}

Update an existing absence request

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`.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Internal ID of the absence request to update. Matches `id` from `GET /my` or `GET /team`.
Request Body

Fields to update. Omit a field (send `null`) to keep the existing value.

Request Body schema:
NameTypeDescription
absenceTypeIdstringNew absence type code (3 characters). Send `null` to keep the existing type.
startDatestringNew start date in ISO-8601 format (`YYYY-MM-DD`). Send `null` to keep the existing value.
endDatestringNew end date in ISO-8601 format (`YYYY-MM-DD`). Must be on or after `startDate`. Send `null` to keep the existing value.
halfDaystringNew 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.
Possible values: morning afternoon
commentsstringNew free-text comment. Send `null` to keep the existing comment; send an empty string to clear it.
substituteEmployeeIdstringNew substitute employee ID. Send `null` to keep the existing substitute; send `"0"` or an empty string to remove the substitute.
Responses
200Updated request as stored.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request.
typestringAbsence type code (3 characters), e.g. `"URL"` for vacation.
startDatestringFirst day of the absence in ISO-8601 format (`YYYY-MM-DD`).
endDatestringLast day of the absence in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans, calculated according to the employee's working-days schedule.
statusstringCurrent status of the request. One of `pending` (awaiting manager approval), `approved`, or `rejected`.
Possible values: pending approved rejected
halfDaystringHalf-day indicator. `"morning"` or `"afternoon"` for half-day requests; `null` for full-day absences.
Possible values: morning afternoon
substitutePendingbooleanWhether the substitute employee's confirmation is still pending. `null` if no substitute was set.
medicalCertificateProvidedbooleanWhether a medical certificate has been uploaded for this absence. Only present for absence types that require a sick note; `null` otherwise.
employeeNamestringDisplay name of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
employeeIdstringInternal ID of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
Response samples
{
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
}
400Malformed body or invalid field combination.
401Missing or invalid bearer token.
403Caller is not the owner of this request and not an admin, or the absence type is forbidden for the caller's role.
404No request with the given `id` exists for the caller's company.
409Request is no longer in a state that permits editing (already approved, rejected, or deleted).
500Unexpected server error while persisting the update.
Request samples
curl -X PUT "https://app.timebutler.com/api/v2/absence-requests/{id}" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"absenceTypeId":"URL","startDate":"2025-08-01","endDate":"2025-08-05","halfDay":"morning","comments":"Rescheduled trip","substituteEmployeeId":"42"}'
DELETE/absence-requests/{id}

Delete an absence request or send a cancellation request

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.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Internal ID of the absence request to delete or cancel.
Request Body

Optional body with a free-text `reason`. Required for cancellation requests when the company's configuration forces a reason.

Request Body schema:
NameTypeDescription
reasonstringOptional free-text reason for the cancellation. Required when the company's configuration mandates a reason for cancellation requests.
Responses
200Outcome of the delete/cancel attempt. The `result` field tells the caller which path was taken.
Response Schema:
NameTypeDescription
idstringInternal ID of the affected record.
resultstringOutcome 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).
Possible values: deleted cancellation_requested deletion_requested
Response samples
{
  "id" : "12345",
  "result" : "deleted"
}
400Cancellation reason missing where the company requires one.
401Missing or invalid bearer token.
403Caller may neither delete nor cancel this request (wrong company, or permissions on the request's state do not allow any action).
404No request with the given `id` exists for the caller's company.
500Unexpected server error while deleting or cancelling.
Request samples
curl -X DELETE "https://app.timebutler.com/api/v2/absence-requests/{id}" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Plans changed"}'
GET/absence-requests/my

List the caller's own absence requests

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`.

Authorizations:
bearerAuth
Responses
200List of the caller's requests. Empty array if there are none.
Response samples
[ {
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
} ]
401Missing or invalid bearer token.
403Caller's account is inactive.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/absence-requests/my" \
  -H "Authorization: Bearer <token>"
GET/absence-requests/pending-count

Pending request counts for the caller's team

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.

Authorizations:
bearerAuth
Responses
200Counts returned.
Response Schema:
NameTypeDescription
numberOfAbsenceRequestsinteger(int32)Number of absence requests in the caller's team that are waiting for the caller's approval.
numberOfAbsenceRequestCancellationsinteger(int32)Number of cancellation requests in the caller's team that are waiting for approval.
Response samples
{
  "numberOfAbsenceRequests" : 3,
  "numberOfAbsenceRequestCancellations" : 1
}
401Missing or invalid bearer token.
403Caller is not a manager or admin.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/absence-requests/pending-count" \
  -H "Authorization: Bearer <token>"
GET/absence-requests/team

List absence requests of the caller's direct reports

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.

Authorizations:
bearerAuth
Responses
200List of team requests. Empty array if none.
Response samples
[ {
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
} ]
401Missing or invalid bearer token.
403Caller is not a manager or admin.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/absence-requests/team" \
  -H "Authorization: Bearer <token>"
POST/absence-requests/{id}/reject

Reject a pending absence request

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.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Internal ID of the absence request to reject.
Request Body

Optional body containing a `reason` string shown to the employee.

Request Body schema:
NameTypeDescription
reasonstringOptional free-text reason for the rejection. Stored on the request and shown to the employee.
Responses
200Request after the rejection has been recorded.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request.
typestringAbsence type code (3 characters), e.g. `"URL"` for vacation.
startDatestringFirst day of the absence in ISO-8601 format (`YYYY-MM-DD`).
endDatestringLast day of the absence in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans, calculated according to the employee's working-days schedule.
statusstringCurrent status of the request. One of `pending` (awaiting manager approval), `approved`, or `rejected`.
Possible values: pending approved rejected
halfDaystringHalf-day indicator. `"morning"` or `"afternoon"` for half-day requests; `null` for full-day absences.
Possible values: morning afternoon
substitutePendingbooleanWhether the substitute employee's confirmation is still pending. `null` if no substitute was set.
medicalCertificateProvidedbooleanWhether a medical certificate has been uploaded for this absence. Only present for absence types that require a sick note; `null` otherwise.
employeeNamestringDisplay name of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
employeeIdstringInternal ID of the employee who submitted the request. Only present in responses from `GET /team`; `null` in `GET /my` responses.
Response samples
{
  "id" : "12345",
  "type" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "halfDay" : "morning",
  "substitutePending" : false,
  "medicalCertificateProvided" : false,
  "employeeName" : "Maria Müller",
  "employeeId" : "42"
}
401Missing or invalid bearer token.
403Caller is not a manager or admin, or is not permitted to act on this particular request.
404No request with the given `id` exists for the caller's company.
409Request is not in `pending` state.
500Unexpected server error while updating the request.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/absence-requests/{id}/reject" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Insufficient staffing during that period"}'

Absence types

Lookup list of absence types configured for the company (vacation, sick leave, etc.). Used to populate dropdowns when creating an absence request.

GET/absence-types

List absence types

Returns all active absence types the authenticated user is allowed to submit.

Authorizations:
bearerAuth
Responses
200List of absence types.
Response samples
[ {
  "id" : "URL",
  "name" : "Urlaub",
  "needsApproval" : true,
  "requiresMedicalCertificate" : false
} ]
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/absence-types" \
  -H "Authorization: Bearer <token>"

Cancellation requests

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.

POST/cancellation-requests/{id}/approve

Approve a cancellation request

Approves the pending cancellation request for the given absence. The absence is deleted and notification emails are sent. Requires `MANAGER` or `ADMIN` role.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Absence ID.
Responses
200Cancellation approved; returns the deleted absence snapshot.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request being cancelled.
employeeIdstringInternal ID of the employee who submitted the original absence request.
employeeNamestringDisplay name of the employee who submitted the original absence request.
absenceTypestringAbsence type code (3 characters) of the request being cancelled.
startDatestringStart date of the absence being cancelled, in ISO-8601 format (`YYYY-MM-DD`).
endDatestringEnd date of the absence being cancelled, in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans.
statusstringCurrent status of the cancellation request. One of `pending` (awaiting approval) or `approved`.
Possible values: pending approved
cancelCommentsstringOptional free-text reason provided by the employee when requesting the cancellation.
Response samples
{
  "id" : "12345",
  "employeeId" : "42",
  "employeeName" : "Maria Müller",
  "absenceType" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "cancelComments" : "Plans changed"
}
401Missing or invalid bearer token.
403Caller is not a manager or admin, or does not manage the absence owner.
404Absence not found.
409No pending cancellation request for this absence.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/cancellation-requests/{id}/approve" \
  -H "Authorization: Bearer <token>"
GET/cancellation-requests

List pending cancellation requests

Returns all pending cancellation requests visible to the authenticated manager or admin. Requires `MANAGER` or `ADMIN` role.

Authorizations:
bearerAuth
Responses
200List of pending cancellation requests.
Response samples
[ {
  "id" : "12345",
  "employeeId" : "42",
  "employeeName" : "Maria Müller",
  "absenceType" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "cancelComments" : "Plans changed"
} ]
401Missing or invalid bearer token.
403Caller is not a manager or admin.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/cancellation-requests" \
  -H "Authorization: Bearer <token>"
GET/cancellation-requests/pending-count

Get pending cancellation count

Returns the number of cancellation requests waiting for a manager or admin decision. Requires `MANAGER` or `ADMIN` role.

Authorizations:
bearerAuth
Responses
200Pending count.
Response Schema:
NameTypeDescription
numberOfCancellationRequestsinteger(int32)Number of cancellation requests in the caller's team that are waiting for approval.
Response samples
{
  "numberOfCancellationRequests" : 2
}
401Missing or invalid bearer token.
403Caller is not a manager or admin.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/cancellation-requests/pending-count" \
  -H "Authorization: Bearer <token>"
POST/cancellation-requests/{id}/reject

Reject a cancellation request

Rejects the pending cancellation request. The absence is kept; the employee is notified by email. Requires `MANAGER` or `ADMIN` role.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Absence ID.
Responses
200Cancellation rejected; returns the updated absence.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request being cancelled.
employeeIdstringInternal ID of the employee who submitted the original absence request.
employeeNamestringDisplay name of the employee who submitted the original absence request.
absenceTypestringAbsence type code (3 characters) of the request being cancelled.
startDatestringStart date of the absence being cancelled, in ISO-8601 format (`YYYY-MM-DD`).
endDatestringEnd date of the absence being cancelled, in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans.
statusstringCurrent status of the cancellation request. One of `pending` (awaiting approval) or `approved`.
Possible values: pending approved
cancelCommentsstringOptional free-text reason provided by the employee when requesting the cancellation.
Response samples
{
  "id" : "12345",
  "employeeId" : "42",
  "employeeName" : "Maria Müller",
  "absenceType" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending",
  "cancelComments" : "Plans changed"
}
401Missing or invalid bearer token.
403Caller is not a manager or admin, or does not manage the absence owner.
404Absence not found.
409No pending cancellation request for this absence.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/cancellation-requests/{id}/reject" \
  -H "Authorization: Bearer <token>"

Categories

Lookup list of time-tracking categories for the company. Used when creating or stopping a clock entry.

GET/categories

List categories

Returns all active categories along with the user's default category and whether a category is required when saving a time entry.

Authorizations:
bearerAuth
Responses
200Category list with default and obligation flag.
Response Schema:
NameTypeDescription
categoriesCategoryDto[]List of categories available to the authenticated user's company.
idstringInternal ID of the category, used when submitting a time entry.
namestringHuman-readable name of the category.
defaultCategoryIdstringID of the category pre-selected by default when creating a time entry. `null` if no default is configured.
isCategoryMandatorybooleanWhether the employee must select a category when creating a time entry.
Response samples
{
  "categories" : [ {
    "id" : "7",
    "name" : "Internal project"
  } ],
  "defaultCategoryId" : "7",
  "isCategoryMandatory" : false
}
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/categories" \
  -H "Authorization: Bearer <token>"

Projects

Lookup list of time-tracking projects for the company. Favorites are listed first. Used when creating a clock entry or time-tracking record.

GET/projects

List projects

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.

Authorizations:
bearerAuth
Responses
200Project list with default and obligation flag.
Response Schema:
NameTypeDescription
projectsProjectDto[]List of projects available to the authenticated user's company.
idstringInternal ID of the project, used when submitting a time entry.
namestringHuman-readable name of the project.
isFavoritebooleanWhether this project is marked as a favourite by the authenticated user.
defaultProjectIdstringID of the project pre-selected by default when creating a time entry. `null` if no default is configured.
isProjectMandatorybooleanWhether the employee must select a project when creating a time entry.
Response samples
{
  "projects" : [ {
    "id" : "5",
    "name" : "Website Relaunch",
    "isFavorite" : true
  } ],
  "defaultProjectId" : "5",
  "isProjectMandatory" : true
}
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/projects" \
  -H "Authorization: Bearer <token>"

Substitute employees

Lookup list of employees that may be selected as a substitute when submitting an absence request.

GET/substitute-employees

List substitute employees

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.

Authorizations:
bearerAuth
Responses
200List of substitute employees.
Response samples
[ {
  "id" : "42",
  "name" : "Thomas Schmidt",
  "department" : "Engineering"
} ]
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/substitute-employees" \
  -H "Authorization: Bearer <token>"

Substitute requests

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.

POST/substitute-requests/{id}/accept

Accept a substitute request

Accepts the substitute request for the given absence. Only the designated substitute may call this endpoint.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Absence ID.
Responses
200Request accepted; returns the updated absence.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request for which the substitute was requested.
employeeIdstringInternal ID of the employee who is absent and needs a substitute.
employeeNamestringDisplay name of the absent employee.
absenceTypestringAbsence type code (3 characters) of the absence for which this substitute was requested.
startDatestringStart date of the absence, in ISO-8601 format (`YYYY-MM-DD`).
endDatestringEnd date of the absence, in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans.
statusstringCurrent status of the substitute confirmation. One of `pending` (awaiting the substitute's confirmation) or `confirmed`.
Possible values: pending confirmed
Response samples
{
  "id" : "12345",
  "employeeId" : "7",
  "employeeName" : "Anna Bauer",
  "absenceType" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending"
}
401Missing or invalid bearer token.
403Caller is not the designated substitute.
404Absence not found.
409Request cannot be accepted in its current state (already accepted, rejected, or no acceptance required).
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/substitute-requests/{id}/accept" \
  -H "Authorization: Bearer <token>"
GET/substitute-requests/pending-count

Get pending substitute request count

Returns the number of substitute requests that are still waiting for the authenticated user's response.

Authorizations:
bearerAuth
Responses
200Pending count.
Response Schema:
NameTypeDescription
numberOfSubstituteRequestsinteger(int32)Number of substitute requests awaiting the caller's confirmation.
Response samples
{
  "numberOfSubstituteRequests" : 1
}
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/substitute-requests/pending-count" \
  -H "Authorization: Bearer <token>"
GET/substitute-requests

List substitute requests

Returns all substitute requests directed at the authenticated user, regardless of their status (pending, accepted, rejected).

Authorizations:
bearerAuth
Responses
200List of substitute requests.
Response samples
[ {
  "id" : "12345",
  "employeeId" : "7",
  "employeeName" : "Anna Bauer",
  "absenceType" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending"
} ]
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/substitute-requests" \
  -H "Authorization: Bearer <token>"
POST/substitute-requests/{id}/reject

Reject a substitute request

Rejects the substitute request for the given absence. Only the designated substitute may call this endpoint.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Absence ID.
Responses
200Request rejected; returns the updated absence.
Response Schema:
NameTypeDescription
idstringInternal ID of the absence request for which the substitute was requested.
employeeIdstringInternal ID of the employee who is absent and needs a substitute.
employeeNamestringDisplay name of the absent employee.
absenceTypestringAbsence type code (3 characters) of the absence for which this substitute was requested.
startDatestringStart date of the absence, in ISO-8601 format (`YYYY-MM-DD`).
endDatestringEnd date of the absence, in ISO-8601 format (`YYYY-MM-DD`).
daysnumber(double)Number of working days the absence spans.
statusstringCurrent status of the substitute confirmation. One of `pending` (awaiting the substitute's confirmation) or `confirmed`.
Possible values: pending confirmed
Response samples
{
  "id" : "12345",
  "employeeId" : "7",
  "employeeName" : "Anna Bauer",
  "absenceType" : "URL",
  "startDate" : "2025-08-01",
  "endDate" : "2025-08-05",
  "days" : 4.0,
  "status" : "pending"
}
401Missing or invalid bearer token.
403Caller is not the designated substitute.
404Absence not found.
409Request cannot be rejected in its current state.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/substitute-requests/{id}/reject" \
  -H "Authorization: Bearer <token>"

Time clock

Real-time time clock. Start, pause, resume, stop, or cancel the running clock for the authenticated user. Business-trip mode is also controlled here.

POST/time-clock/business-trip-end

End a business trip

Marks the end of an active business trip on the current clock. Returns 409 if no business trip is currently active.

Authorizations:
bearerAuth
Responses
200Business trip ended; returns current status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Business trips not enabled, or time tracking not active.
401Missing or invalid bearer token.
409No business trip is currently active.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/business-trip-end" \
  -H "Authorization: Bearer <token>"
POST/time-clock/business-trip-start

Start a business trip

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.

Authorizations:
bearerAuth
Responses
200Business trip started; returns current status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Business trips not enabled, or time tracking not active.
401Missing or invalid bearer token.
409Clock is not running/paused, or a business trip is already active.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/business-trip-start" \
  -H "Authorization: Bearer <token>"
POST/time-clock/cancel

Cancel the time clock

Discards the current clock session without saving a time entry. Returns 409 if the clock is already `idle`.

Authorizations:
bearerAuth
Responses
200Clock cancelled; returns `idle` status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
409Clock is already `idle`.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/cancel" \
  -H "Authorization: Bearer <token>"
GET/time-clock/status

Get time clock status

Returns the current state of the time clock: `idle`, `running`, `paused`, or `waiting`. Also returns elapsed time, pause time, and business-trip flag.

Authorizations:
bearerAuth
Responses
200Current clock status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/time-clock/status" \
  -H "Authorization: Bearer <token>"
POST/time-clock/pause

Pause the time clock

Pauses a running time clock. Returns 409 if the clock is not in `running` state.

Authorizations:
bearerAuth
Responses
200Clock paused; returns current status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
409Clock is not running.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/pause" \
  -H "Authorization: Bearer <token>"
POST/time-clock/resume

Resume the time clock

Resumes a paused time clock. Returns 409 if the clock is not in `paused` state.

Authorizations:
bearerAuth
Responses
200Clock resumed; returns current status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
409Clock is not paused.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/resume" \
  -H "Authorization: Bearer <token>"
POST/time-clock/start

Start the time clock

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.

Authorizations:
bearerAuth
Responses
200Clock started; returns current status.
Response Schema:
NameTypeDescription
statusstringCurrent state of the time clock. One of `idle` (not running), `running` (clocked in), `paused`, or `waiting` (started but held until a minimum interval passes).
Possible values: idle running paused waiting
startTimestampinteger(int64)Unix timestamp in milliseconds when the current clock-in session started. `null` when `status` is `idle`.
pauseTimestampinteger(int64)Unix timestamp in milliseconds when the current pause started. `null` if the clock is not paused.
workTimeElapsedSecondsinteger(int32)Total working time elapsed in the current session, in seconds (excludes breaks).
breakElapsedSecondsinteger(int32)Duration of the current ongoing break, in seconds. `0` if the clock is not paused.
accumulatedBreakSecondsinteger(int32)Total accumulated break time in the current session, in seconds (including any completed breaks).
waitSecondsinteger(int32)Minimum number of seconds remaining before the employee is allowed to clock out, based on company policy. `null` if there is no minimum.
isBusinessTripActivebooleanWhether a business trip is currently active for this session. `false` when no business trip is active or the feature is not enabled.
Response samples
{
  "status" : "running",
  "startTimestamp" : 1753700400000,
  "pauseTimestamp" : 1753714800000,
  "workTimeElapsedSeconds" : 14400,
  "breakElapsedSeconds" : 0,
  "accumulatedBreakSeconds" : 1800,
  "waitSeconds" : 0,
  "isBusinessTripActive" : false
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
403User is not permitted to create time entries.
409Clock is already running, or start conditions are not met.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/start" \
  -H "Authorization: Bearer <token>"
POST/time-clock/stop

Stop and save the time clock

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.

Authorizations:
bearerAuth
Request Body

Optional project ID, category ID, and remarks for the saved entry.

Request Body schema:
NameTypeDescription
projectIdinteger(int32)ID of the project to assign the completed session to. Send `0` if no project assignment is needed.
categoryIdinteger(int32)ID of the category to assign the completed session to. Send `0` if no category assignment is needed.
remarksstringOptional free-text note for the completed time entry.
Responses
200Clock stopped; returns the saved entry details (ID, duration, pause).
Response Schema:
NameTypeDescription
idstringInternal ID of the time entry created when the clock was stopped.
workedMinutesinteger(int32)Net working time recorded for the session, in minutes (total duration minus breaks).
breakMinutesinteger(int32)Total break time recorded for the session, in minutes.
Response samples
{
  "id" : "98765",
  "workedMinutes" : 480,
  "breakMinutes" : 30
}
204Clock was in `waiting` state and was discarded without saving.
400Time tracking not active for this company.
401Missing or invalid bearer token.
409Clock is not active, duration is too short, or duplicate entry detected.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-clock/stop" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"projectId":5,"categoryId":7,"remarks":"Code review"}'

Time entries

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).

DELETE/time-entries/{id}

Delete a time entry

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`.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Time entry ID.
Responses
200Entry deleted or deletion request submitted.
Response Schema:
NameTypeDescription
idstringInternal ID of the affected record.
resultstringOutcome 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).
Possible values: deleted cancellation_requested deletion_requested
Response samples
{
  "id" : "12345",
  "result" : "deleted"
}
400Invalid ID or time tracking is not active for this company.
401Missing or invalid bearer token.
403Caller is not allowed to delete this entry.
404Time entry not found.
409Entry is locked by the month-close restriction.
500Unexpected server error.
Request samples
curl -X DELETE "https://app.timebutler.com/api/v2/time-entries/{id}" \
  -H "Authorization: Bearer <token>"
GET/time-entries/pending-count

Get pending time-entry count

Returns the number of time entries in the manager's team that are awaiting approval. Requires `MANAGER` or `ADMIN` role.

Authorizations:
bearerAuth
Responses
200Pending count.
Response Schema:
NameTypeDescription
numberOfTimeEntriesinteger(int32)Number of time entries in the caller's team that are waiting for approval.
Response samples
{
  "numberOfTimeEntries" : 5
}
401Missing or invalid bearer token.
403Caller is not a manager or admin.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/time-entries/pending-count" \
  -H "Authorization: Bearer <token>"

Time entry requests

Pending change or deletion requests for time entries that require manager or admin approval. Managers and admins can list, approve, and reject these requests.

POST/time-entry-requests/{id}/approve

Approve a time-entry request

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.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Time entry ID.
Responses
200Request approved; returns the updated time entry.
Response Schema:
NameTypeDescription
idstringInternal ID of the time entry.
employeeIdstringInternal ID of the employee who submitted the time entry. Only present in team/manager views.
employeeNamestringDisplay name of the employee who submitted the time entry. Only present in team/manager views.
datestringDate of the time entry in ISO-8601 format (`YYYY-MM-DD`).
startTimestringClock-in time in `HH:mm` format.
endTimestringClock-out time in `HH:mm` format.
workedMinutesinteger(int32)Net working time for this entry, in minutes (total duration minus breaks).
breakMinutesinteger(int32)Total break time for this entry, in minutes.
projectstringName of the project assigned to this entry. `null` if no project was assigned.
categorystringName of the category assigned to this entry. `null` if no category was assigned.
remarksstringFree-text note attached to this entry. `null` if no remark was entered.
requestTypestringType of request that produced this entry. One of `manual` (entered by hand) or `clock` (created by stopping the time clock).
Possible values: manual clock
statusstringApproval status of this time entry. One of `pending` (awaiting manager approval) or `approved`.
Possible values: pending approved
Response samples
{
  "id" : "98765",
  "employeeId" : "42",
  "employeeName" : "Maria Müller",
  "date" : "2025-07-28",
  "startTime" : "08:00",
  "endTime" : "17:00",
  "workedMinutes" : 480,
  "breakMinutes" : 60,
  "project" : "Website Relaunch",
  "category" : "Internal project",
  "remarks" : "Sprint planning",
  "requestType" : "manual",
  "status" : "pending"
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
403Caller is not a manager or admin, or only admins may approve in this company.
404Time entry not found.
409No pending request, or the proposed change has a scheduling conflict.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-entry-requests/{id}/approve" \
  -H "Authorization: Bearer <token>"
GET/time-entry-requests

List pending time-entry requests

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.

Authorizations:
bearerAuth
Responses
200List of pending time-entry requests.
Response samples
[ {
  "id" : "98765",
  "employeeId" : "42",
  "employeeName" : "Maria Müller",
  "date" : "2025-07-28",
  "startTime" : "08:00",
  "endTime" : "17:00",
  "workedMinutes" : 480,
  "breakMinutes" : 60,
  "project" : "Website Relaunch",
  "category" : "Internal project",
  "remarks" : "Sprint planning",
  "requestType" : "manual",
  "status" : "pending"
} ]
401Missing or invalid bearer token.
403Caller is not a manager or admin.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/time-entry-requests" \
  -H "Authorization: Bearer <token>"
POST/time-entry-requests/{id}/reject

Reject a time-entry request

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.

Authorizations:
bearerAuth
Path Parameters
NameTypeDescription
idrequiredinteger(int32)Time entry ID.
Request Body

Optional rejection reason.

Request Body schema:
NameTypeDescription
reasonstringOptional free-text reason for the rejection. Stored on the request and shown to the employee.
Responses
200Request rejected; returns the time entry in its current state.
Response Schema:
NameTypeDescription
idstringInternal ID of the time entry.
employeeIdstringInternal ID of the employee who submitted the time entry. Only present in team/manager views.
employeeNamestringDisplay name of the employee who submitted the time entry. Only present in team/manager views.
datestringDate of the time entry in ISO-8601 format (`YYYY-MM-DD`).
startTimestringClock-in time in `HH:mm` format.
endTimestringClock-out time in `HH:mm` format.
workedMinutesinteger(int32)Net working time for this entry, in minutes (total duration minus breaks).
breakMinutesinteger(int32)Total break time for this entry, in minutes.
projectstringName of the project assigned to this entry. `null` if no project was assigned.
categorystringName of the category assigned to this entry. `null` if no category was assigned.
remarksstringFree-text note attached to this entry. `null` if no remark was entered.
requestTypestringType of request that produced this entry. One of `manual` (entered by hand) or `clock` (created by stopping the time clock).
Possible values: manual clock
statusstringApproval status of this time entry. One of `pending` (awaiting manager approval) or `approved`.
Possible values: pending approved
Response samples
{
  "id" : "98765",
  "employeeId" : "42",
  "employeeName" : "Maria Müller",
  "date" : "2025-07-28",
  "startTime" : "08:00",
  "endTime" : "17:00",
  "workedMinutes" : 480,
  "breakMinutes" : 60,
  "project" : "Website Relaunch",
  "category" : "Internal project",
  "remarks" : "Sprint planning",
  "requestType" : "manual",
  "status" : "pending"
}
400Time tracking not active for this company.
401Missing or invalid bearer token.
403Caller is not a manager or admin, or only admins may reject in this company.
404Time entry not found.
409No pending request for this entry.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-entry-requests/{id}/reject" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Insufficient staffing during that period"}'

Time tracking

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.

POST/time-tracking/clock-entry

Submit a completed clock entry

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`.

Authorizations:
bearerAuth
Request Body

Clock entry with start/end timestamps and optional project and remarks.

Request Body schema:
NameTypeDescription
startTimestamprequiredinteger(int64)Start of the work period as a Unix timestamp in milliseconds.
endTimestamprequiredinteger(int64)End of the work period as a Unix timestamp in milliseconds.
pauseSecondsinteger(int32)Total break duration within the work period, in seconds.
projectIdinteger(int32)ID of the project to assign this entry to. Send `0` if no project assignment is needed.
remarksstringOptional free-text note for this time entry.
Responses
200Entry created; returns the entry ID and worked/pause minutes.
Response Schema:
NameTypeDescription
idstringInternal ID of the created time entry.
workedMinutesinteger(int32)Net working time recorded for this entry, in minutes (total duration minus breaks).
breakMinutesinteger(int32)Total break time recorded for this entry, in minutes.
Response samples
{
  "id" : "98765",
  "workedMinutes" : 480,
  "breakMinutes" : 30
}
400Missing or invalid request body, invalid timestamps, or time tracking not active.
401Missing or invalid bearer token.
403User is not allowed to create time entries.
422Remarks exceed maximum length.
500Unexpected server error.
Request samples
curl -X POST "https://app.timebutler.com/api/v2/time-tracking/clock-entry" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"startTimestamp":1753700400000,"endTimestamp":1753729200000,"pauseSeconds":1800,"projectId":5,"remarks":"Sprint planning"}'
GET/time-tracking/time-off-summary

Get time-off summary

Returns the authenticated user's remaining and total vacation days for the current year, plus the number of illness days taken.

Authorizations:
bearerAuth
Responses
200Time-off summary.
Response Schema:
NameTypeDescription
vacationDaysRemainingnumber(double)Number of vacation days the employee still has available in the current year, after accounting for approved and pending requests.
vacationDaysTotalnumber(double)Total vacation day entitlement for the current year.
illnessDaysTakennumber(double)Number of sick-leave days taken in the current year.
Response samples
{
  "vacationDaysRemaining" : 12.5,
  "vacationDaysTotal" : 30.0,
  "illnessDaysTaken" : 3.0
}
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/time-tracking/time-off-summary" \
  -H "Authorization: Bearer <token>"
GET/time-tracking/work-summary

Get today's work summary

Returns the authenticated user's work commitment, worked minutes, remaining minutes, completion percentage, and break minutes for today.

Authorizations:
bearerAuth
Responses
200Today's work summary.
Response Schema:
NameTypeDescription
commitmentMinutesinteger(int32)Total working time the employee is contractually required to work in the queried period, in minutes.
workedMinutesinteger(int32)Total working time the employee has actually worked in the queried period, in minutes.
remainingMinutesinteger(int32)Remaining working time still to be worked in the queried period, in minutes. Negative values indicate overtime.
percentDoneinteger(int32)Percentage of the contractual working time that has been worked so far, rounded to the nearest integer.
breakMinutesinteger(int32)Total break time recorded in the queried period, in minutes.
Response samples
{
  "commitmentMinutes" : 2400,
  "workedMinutes" : 2280,
  "remainingMinutes" : 120,
  "percentDone" : 95,
  "breakMinutes" : 300
}
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/time-tracking/work-summary" \
  -H "Authorization: Bearer <token>"

User profile

Basic profile information for the authenticated user: name, email, role, department, and avatar URL.

GET/user/profile

Get profile

Returns the authenticated user's display name, email address, role (`USER`, `MANAGER`, `ADMIN`), department, branch office, and avatar URL.

Authorizations:
bearerAuth
Responses
200Profile data.
Response Schema:
NameTypeDescription
idstringInternal ID of the authenticated user.
namestringFull display name of the user.
emailstringEmail address of the user.
rolestringRole of the user within the company. One of `USER`, `MANAGER`, or `ADMIN`.
Possible values: USER MANAGER ADMIN
departmentstringName of the department the user belongs to. `null` if the company does not use departments.
branchOfficestringName of the branch office the user is assigned to. `null` if the company does not use branch offices.
countryobjectCountry of the user as configured in their profile. The structure depends on the company's configuration.
avatarstringURL of the user's profile picture. `null` if no avatar has been uploaded.
isBusinessTripEnabledbooleanWhether the business trip feature is enabled for this user. `null` if the feature is not available in the company's plan.
isTimeTrackingEnabledbooleanWhether time tracking is active for the user's company.
Response samples
{
  "id" : "42",
  "name" : "Maria Müller",
  "email" : "maria.mueller@example.com",
  "role" : "MANAGER",
  "department" : "Engineering",
  "branchOffice" : "Berlin",
  "country" : { },
  "avatar" : "/images/avatar/42/abc123.jpg?v=5",
  "isBusinessTripEnabled" : true,
  "isTimeTrackingEnabled" : true
}
401Missing or invalid bearer token.
500Unexpected server error.
Request samples
curl -X GET "https://app.timebutler.com/api/v2/user/profile" \
  -H "Authorization: Bearer <token>"