---
title: HR WORKS API Integration on Truto
slug: hrworks
category: HRIS
canonical: "https://truto.one/integrations/detail/hrworks/"
---

# HR WORKS API Integration on Truto



**Category:** HRIS  
**Status:** Beta

## Unified APIs

### Unified HRIS API

- **Bank Info** — Bank info represent the Bank Account information for an Employee
- **Employee Compensations** — Represent the compensation configuration for an Employee
- **Employees** — Represents an employee in HRIS
- **Employments** — Employments represent a job position at a company.
- **Groups** — Groups represent the groups for an Employee
- **Locations** — Locations represent the locations in HRIS
- **Timeoff Balances** — Represent the time off balances for an Employee
- **Timeoff Policies** — Represent the time off policies in a company
- **Timeoff Requests** — Represent the time off requests for an Employee
- **Timeoff Types** — Represent the time off types in a company
- **Timesheet Entries** — Represents a block of time an employee worked or reported, such as a shift, a punch or a timesheet line, attributed to a single work date

## MCP-ready AI tools

Truto exposes 144 tools for HR WORKS that AI agents can call directly.

- **get_single_hr_works_absence_job_by_id** — Get the status and result of an asynchronous absence write.
id is the jobId returned by absences.create, absences.bulk_update, absences.bulk_delete, absences.update, absences.delete.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **hr_works_absence_jobs_get_or_error** — Get the status of an absences write job, like get (GET /v2/absences/jobs/{jobId}), except that an HTTP error from HR WORKS (e.g. 429 rate limit, 5xx) is returned as a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified timeoff_requests create/update/delete mappings, which poll the job after HR WORKS accepted the write and must still report the job id when a poll fails.
- **get_single_hr_works_absence_type_job_by_id** — Get the status and result of an asynchronous absence type write.
id is the jobId returned by absence_types.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_absence_types** — List the absence types configured in HR WORKS with their key, name and rules (holiday entitlement, time account, substitution, payroll use). Use the keys as the types filter of absences.list; onlyActive=true returns only active types.
Not paginated: everything comes back in one response.
- **create_a_hr_works_absence_type** — Create absence types in bulk: body {data: [...]} with 1-1000 types (name, key and rule flags such as reducesHolidayEntitlement or isSubstitutionMandatory).
Asynchronous: returns a jobId; poll absence_type_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_absences** — List absences per person in a date range (type, dates, half-days, status, working days); filter by persons, types or statusFilter. interval splits the range (days, weeks, months); count=true returns per-type day totals instead.
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_absence_by_id** — Get one absence; id is the absence number (the person's license number, an underscore, then the person's running absence number), as returned by absences.list.
- **create_a_hr_works_absence** — Create absences in bulk: body {data: [...]} with 1-100 absences.
Each item needs type, beginDate, endDate, status, personnelNumber.
Asynchronous: returns a jobId; poll absence_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **update_a_hr_works_absence_by_id** — Edit one absence; id is its absence number. Send only what changes (type, dates, half-day flags, status, substitutes, remark); attributes you leave out stay unchanged.
Asynchronous: returns a jobId; poll absence_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **delete_a_hr_works_absence_by_id** — Delete one absence; id is its absence number (the person's license number, an underscore, then the running absence number).
Asynchronous: returns a jobId; poll absence_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_absences_bulk_update** — Edit absences in bulk: body {data: [...]} with 1-100 items, each identified by its absence number; attributes you leave out stay unchanged (mandatory ones excepted).
Each item needs number.
Asynchronous: returns a jobId; poll absence_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_absences_bulk_delete** — Delete several absences at once: numbers lists 1-100 absence numbers (license number, underscore, running number).
Required: numbers.
Asynchronous: returns a jobId; poll absence_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_absences_list_or_error** — List absences like list (GET /v2/absences, one page, the same required beginDate/endDate parameters), but return HR WORKS's whole response (the map of person identifier to date intervals) and, when HR WORKS answers an HTTP error (e.g. 429 rate limit, 5xx), a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified timeoff_requests create mapping to read the new absence back after its write job finished, so a failing read-back can still report the job id.
- **list_all_hr_works_accumulated_absences** — Get per-person totals of absence working days by absence type for a date range; filter by persons, types or statusFilter, and split the range with interval (days, weeks or months).
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **list_all_hr_works_accumulated_remote_work** — Get per-person totals of remote-work working days for a date range; filter by persons or statusFilter, and split the range with interval (days, weeks or months).
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **list_all_hr_works_accumulated_sick_leaves** — Get per-person totals of sick-leave working days by sick leave type for a date range; filter by persons, types or statusFilter, and split the range with interval (days, weeks or months).
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **list_all_hr_works_applicants** — List applicants (only applicants that have job applications): name, contact data, address, birthday and more. Filter by applicant uuids (applicants) or by the status of their job applications (statusFilter).
Paginated by HR WORKS at 50 per page (limit is ignored); pass next_cursor with the same filters for the next page.
- **get_single_hr_works_applicant_by_id** — Get one applicant; id is the applicant's uuid, which stays the same when the applicant later becomes an employee.
Returns: address, birthday, earliestPossibleJoinDate, email, firstName, gender, uuid, hasNoticePeriod and more.
- **list_all_hr_works_available_working_hours** — Get each person's available working hours (working hours, regular working hours, remarks, related events) for a date range, optionally split by interval into days, weeks or months; choose persons with personIdentifierType.
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 150 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_cost_center_assignment_job_by_id** — Get the status and result of an asynchronous cost center assignment write.
id is the jobId returned by cost_center_assignments.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **create_a_hr_works_cost_center_assignment** — Assign cost centers to persons and/or organization units in bulk: body {data: [...]} with 1-2000 assignments; personIdentifierType sets the type of each personIdentifier (uuid by default).
Asynchronous: returns a jobId; poll cost_center_assignment_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_cost_center_job_by_id** — Get the status and result of an asynchronous cost center write.
id is the jobId returned by cost_centers.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **hr_works_cost_center_jobs_get_or_error** — Get the status of a cost centers write job, like get (GET /v2/cost-objects/cost-centers/jobs/{jobId}), except that an HTTP error from HR WORKS (e.g. 429 rate limit, 5xx) is returned as a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified groups create/update mappings, which poll the job after HR WORKS accepted the write and must still report the job id when a poll fails.
- **list_all_hr_works_cost_centers** — List all cost centers of the company (number and name).
Paginated by HR WORKS at 10000 per page (limit is ignored); pass next_cursor with the same filters for the next page.
- **create_a_hr_works_cost_center** — Create cost centers in bulk: body {data: [...]} with 1-1000 items (number, name); overwriteNames=true renames an existing cost center that has the same number.
Asynchronous: returns a jobId; poll cost_center_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_cost_centers_list_or_error** — List cost centers like list (GET /v2/cost-objects/cost-centers, first page only, up to 10000), but return HR WORKS's whole response ({"costCenters": [...]}) and, when HR WORKS answers an HTTP error (e.g. 429 rate limit, 5xx), a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified groups create/update mappings to read the cost center back after its write job finished, so a failing read-back can still report the job id.
- **get_single_hr_works_cost_objective_assignment_job_by_id** — Get the status and result of an asynchronous cost objective assignment write.
id is the jobId returned by cost_objective_assignments.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **create_a_hr_works_cost_objective_assignment** — Assign cost objectives to persons and/or organization units in bulk: body {data: [...]} with 1-2000 assignments; personIdentifierType sets the type of each personIdentifier (uuid by default).
Asynchronous: returns a jobId; poll cost_objective_assignment_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_cost_objective_job_by_id** — Get the status and result of an asynchronous cost objective write.
id is the jobId returned by cost_objectives.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_cost_objectives** — List all cost objectives of the company (number and name).
Paginated by HR WORKS at 10000 per page (limit is ignored); pass next_cursor with the same filters for the next page.
- **create_a_hr_works_cost_objective** — Create cost objectives in bulk: body {data: [...]} with 1-1000 items (number, name); overwriteNames=true renames an existing cost objective that has the same number.
Asynchronous: returns a jobId; poll cost_objective_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **create_a_hr_works_document_pool_file** — Upload a file to the HR WORKS document pool.
Required: fileName.
Give the file as url (HR WORKS downloads it) or send its bytes (up to 15 MB): call the Truto proxy with truto_body_passthrough=true and the file's MIME type as Content-Type (MCP tools can only pass url).
Asynchronous: returns a jobId; poll person_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_expense_report_job_by_id** — Get the status and result of an asynchronous travel expense report write.
id is the jobId returned by expense_reports.create, expense_reports.update.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_expense_reports** — List travel expense reports per person in a date range; choose persons with personIdentifierType, filter with statusFilter, and add receipt collections without a trip with includeExpenseReportsWithoutTrip=true.
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 20 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_expense_report_by_id** — Get one travel expense report with its days, receipts, advances and totals; id is its number (the person's personnel number, a dash, then the trip number).
- **create_a_hr_works_expense_report** — Create travel expense reports in bulk: body {data: [...]} (HR WORKS documents no maximum); personIdentifierType sets the type of the personIdentifier values (personnel number by default).
Each item needs personIdentifier, beginDate, endDate.
Asynchronous: returns a jobId; poll expense_report_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **update_a_hr_works_expense_report_by_id** — Change the status of one travel expense report (statusIdentifier is the only editable property); id is its number (the person's license number, a hyphen, then the running expense report number).
Asynchronous: returns a jobId; poll expense_report_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_health_check** — Check that the HR WORKS API is up and accepts this connection's access token; Truto uses it to validate new connections. HR WORKS documents no response body: a successful call means the API is reachable.
- **get_single_hr_works_holiday_job_by_id** — Get the status and result of an asynchronous holiday write.
id is the jobId returned by holidays.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_holidays** — List the holidays of one year for the company, its countries and permanent establishments; filter with countryCodes (ISO 3166-1 alpha-3) or permanentEstablishments (general holidays of the country stay included).
Required: year.
Not paginated: the result is ONE object keyed by ISO 3166-1 alpha-3 country code; `<key>` in the response schema is a placeholder, not a field.
- **create_a_hr_works_holiday** — Create company holidays in bulk: body {data: [...]} with 1-1000 holidays (date, name, half-day flag). Scope each one with countryCode, state (together with countryCode) or permanentEstablishmentId; at least one of them is required.
Asynchronous: returns a jobId; poll holiday_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **create_a_hr_works_job_application_file** — Attach a file to a job application; job_application_id is the application's id.
Required: fileName, job_application_id.
Give the file as url (HR WORKS downloads it) or send its bytes (up to 15 MB): call the Truto proxy with truto_body_passthrough=true and the file's MIME type as Content-Type (MCP tools can only pass url).
Asynchronous: returns a jobId; poll job_application_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_job_application_job_by_id** — Get the status and result of an asynchronous job application write.
id is the jobId returned by job_applications.create, job_applications.update, job_applications.delete, job_application_files.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_job_applications** — List job applications with their applicant, documents, post and status; filter by post ids (posts) and status identifiers (statusFilter).
Paginated by HR WORKS at 50 per page (limit is ignored); pass next_cursor with the same filters for the next page.
- **get_single_hr_works_job_application_by_id** — Get one job application (HR WORKS API v3); id is the job application's uuid. Truto returns the data object of the v3 response.
Returns: id, statusIdentifier, postUuid, applicant, applicationDocuments, creationDateAndTime, desiredSalary, expectedSalary and more.
- **create_a_hr_works_job_application** — Create job applications in bulk: body {data: [...]} with 1-100 applications, each holding applicant and application details.
Each item needs firstName, lastName, postId.
Asynchronous: returns a jobId; poll job_application_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **update_a_hr_works_job_application_by_id** — Edit one job application; id is its uuid. Only the attributes you send change (post, post offer, desired salary, remark, privacy flags, statusIdentifier).
Asynchronous: returns a jobId; poll job_application_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **delete_a_hr_works_job_application_by_id** — Delete one job application; id is its uuid, as returned by job_applications.list.
Asynchronous: returns a jobId; poll job_application_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_leave_accounts** — Get leave-account figures per person (entitlement, requested, approved, planned, expiring and more) for the period from January 1 of referenceDate's year up to referenceDate; filter by persons or add leavers with onlyActive=false.
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_leave_account_by_id** — Get one person's leave account (entitlement, requested, approved, planned, expiring and more); id is the person's HR WORKS personnel number. referenceDate sets the end of the period, which starts on January 1 of that year.
- **create_a_hr_works_onboarding_document_file** — Attach a file to an onboarding document; onboarding_document_id is the document id.
Required: fileName, onboarding_document_id.
Give the file as url (HR WORKS downloads it) or send its bytes (up to 15 MB): call the Truto proxy with truto_body_passthrough=true and the file's MIME type as Content-Type (MCP tools can only pass url).
Asynchronous: returns a jobId; poll onboarding_document_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_onboarding_document_job_by_id** — Get the status and result of an asynchronous onboarding document write.
id is the jobId returned by onboarding_documents.create, onboarding_documents.update, onboarding_document_files.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_onboarding_documents** — List the onboarding documents of the company (the data collected from new hires before they become persons); filter by statusFilter or organizationUnits.
Not paginated: everything comes back in one response.
- **get_single_hr_works_onboarding_document_by_id** — Get one onboarding document with the new hire's personal, address, bank and tax data; id is its id, as returned by onboarding_documents.list.
Returns: address, bankAccount, birthday, birthName, confession, countryOfBirth, childAllowanceCategory, emergencyContactDegreeOfKinship and more.
- **create_a_hr_works_onboarding_document** — Create onboarding documents for new hires in bulk: body {data: [...]} with 1-100 documents.
Each item needs privateEmail, firstName, lastName, organizationUnitNumber, joinDate.
Asynchronous: returns a jobId; poll onboarding_document_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **update_a_hr_works_onboarding_document_by_id** — Edit one onboarding document; id is its id. HR WORKS ignores attributes you leave out, but its schema marks firstName, lastName, privateEmail, organizationUnitNumber and joinDate as required.
Asynchronous: returns a jobId; poll onboarding_document_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_organization_unit_job_by_id** — Get the status and result of an asynchronous organization unit write.
id is the jobId returned by organization_units.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **hr_works_organization_unit_jobs_get_or_error** — Get the status of an organization units write job, like get (GET /v2/organization-units/jobs/{jobId}), except that an HTTP error from HR WORKS (e.g. 429 rate limit, 5xx) is returned as a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified groups create mapping, which polls the job after HR WORKS accepted the write and must still report the job id when a poll fails.
- **list_all_hr_works_organization_unit_present_persons** — List the persons of one organization unit who are in the office right now: HR WORKS leaves out vacations, trips, sick leaves and other absences, counted by forenoon and afternoon.
Required: organization_unit_number.
Not paginated: everything comes back in one response.
- **list_all_hr_works_organization_units** — List all active organization units of the company (number, name, parent unit, SAP codes, country and more).
Not paginated: everything comes back in one response.
- **get_single_hr_works_organization_unit_by_id** — Get one organization unit; id is its uuid (passing the unit number instead is deprecated by HR WORKS).
Returns: parentOrganizationUnit, number, name, additionalName, sapCodeType, sapUnitCode, countryCode, uuid.
- **create_a_hr_works_organization_unit** — Create organization units in bulk: body {data: [...]} with 1-500 units; parent and child units can be created in the same call.
Each item needs number, name, parentOrganizationUnitNumber, countryCode.
Asynchronous: returns a jobId; poll organization_unit_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_organization_units_list_or_error** — List the active organization units like list (GET /v2/organization-units, one call, no paging), but return HR WORKS's whole response ({"organizationUnits": [...]}) and, when HR WORKS answers an HTTP error (e.g. 429 rate limit, 5xx), a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified groups create mapping to read the new unit back after its write job finished, so a failing read-back can still report the job id.
- **get_single_hr_works_payroll_file_job_by_id** — Get the status and result of an asynchronous payroll file write.
id is the jobId returned by payroll_files.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **create_a_hr_works_payroll_file** — Upload a payroll file for a person and a year (optionally a month). Choose the person with personIdentifier and personIdentifierType (uuid by default; personnelNumber is deprecated).
Required: fileName, year.
Give the file as url (HR WORKS downloads it) or send its bytes (up to 15 MB): call the Truto proxy with truto_body_passthrough=true and the file's MIME type as Content-Type (MCP tools can only pass url).
Asynchronous: returns a jobId; poll payroll_file_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_permanent_establishment_job_by_id** — Get the status and result of an asynchronous permanent establishment write.
id is the jobId returned by permanent_establishments.create, permanent_establishments.bulk_update.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_permanent_establishments** — List all active permanent establishments (business locations) of the company: id, number, name and address.
Not paginated: everything comes back in one response.
- **get_single_hr_works_permanent_establishment_by_id** — Get one permanent establishment (business location): id, number, name and address; id is its id, as returned by permanent_establishments.list.
Returns: id, name, address, number.
- **create_a_hr_works_permanent_establishment** — Create permanent establishments (business locations) in bulk: body {data: [...]} with 1-1000 items (id, number, name, address).
Asynchronous: returns a jobId; poll permanent_establishment_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_permanent_establishments_bulk_update** — Edit permanent establishments in bulk: body {data: [...]} with 1-1000 items (id, number, name, address).
Asynchronous: returns a jobId; poll permanent_establishment_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **create_a_hr_works_person_document** — Upload a file to one person's document pool; person_identifier selects the person (personIdentifierType sets its type, personId by default).
Required: fileName, person_identifier.
Give the file as url (HR WORKS downloads it) or send its bytes (up to 15 MB): call the Truto proxy with truto_body_passthrough=true and the file's MIME type as Content-Type (MCP tools can only pass url).
Asynchronous: returns a jobId; poll person_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_person_job_by_id** — Get the status and result of an asynchronous person write.
id is the jobId returned by persons.create, persons.bulk_update, document_pool_files.create, person_documents.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **hr_works_person_jobs_get_or_error** — Get the status of a persons write job, like get (GET /v2/persons/jobs/{jobId}), except that an HTTP error from HR WORKS (e.g. 429 rate limit, 5xx) is returned as a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified employees create/update mappings, which poll the job after HR WORKS accepted the write and must still report the job id when a poll fails.
- **list_all_hr_works_person_master_data** — Get full master data (contact, address, bank account, employment, organization and more) for many persons; choose persons with persons and personIdentifierType, or add leavers with onlyActive=false.
Paginated by HR WORKS at 50 per page (limit is ignored); pass next_cursor with the same filters for the next page.
- **get_single_hr_works_person_master_datum_by_id** — Get one person's full master data; id is the person identifier, a personnel number unless personIdentifierType says otherwise. Custom master-data fields defined in HR WORKS are returned as extra properties.
- **list_all_hr_works_person_wage_payments** — List one person's wage payments (HR WORKS API v3): month, gross amount, wage type and more; person_uuid is the person's uuid.
Required: person_uuid.
Paginated (page size not documented; limit is ignored); pass next_cursor with the same filters for the next page. HR WORKS documents page links only for v2, so paging may stop after page 1.
- **get_single_hr_works_person_working_time_status_by_id** — Get whether a person is clocked in right now and, if so, the current working time; id is the person identifier (a personnel number by default).
Returns: clockedIn, workingTime.
- **create_a_hr_works_person_working_time** — Clock a person in or out now; person_identifier is a personnel number by default. action is clockIn or clockOut, and a clockIn needs type. beginDateAndTime or endDateAndTime (ISO 8601 UTC) may backdate it by up to 24 hours.
Required: action, person_identifier.
Synchronous: returns the working time and any warnings directly.
- **list_all_hr_works_personnel_file_categories** — List the personnel file categories of the company (key and name). Use a key as the category filter of personnel_file_entries.list or as the category of a new entry.
Not paginated: everything comes back in one response.
- **list_all_hr_works_personnel_file_entries** — List one person's personnel file entries (category, creation date, documents, name, notes and more); person_identifier selects the person (a personnel number unless personIdentifierType says otherwise).
Required: person_identifier.
Not paginated: everything comes back in one response.
- **get_single_hr_works_personnel_file_entry_by_id** — Get one personnel file entry; id is the entry id and person_identifier the person (a personnel number unless personIdentifierType says otherwise).
Required: person_identifier.
- **create_a_hr_works_personnel_file_entry** — Create personnel file entries for many persons: body {data: [...]} with 1-1000 entries; personIdentifierType sets the type of the personIdentifier values in the items.
Each item needs name, creationDate, category, personnelNumber.
Asynchronous: returns a jobId; poll personnel_file_entry_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **create_a_hr_works_personnel_file_entry_file** — Attach a file to one of a person's personnel file entries (personIdentifierType sets the type of person_identifier, personnel number by default).
Required: fileName, person_identifier, personnel_file_entry_id.
Give the file as url (HR WORKS downloads it) or send its bytes (up to 15 MB): call the Truto proxy with truto_body_passthrough=true and the file's MIME type as Content-Type (MCP tools can only pass url).
Asynchronous: returns a jobId; poll personnel_file_entry_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_personnel_file_entry_job_by_id** — Get the status and result of an asynchronous personnel file entry write.
id is the jobId returned by personnel_file_entries.create, personnel_file_entry_files.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_persons** — List persons (base data: names and identifiers such as personId, personnelNumber and uuid) grouped by organization unit; filter with organizationUnits. Only active persons unless onlyActive=false.
Not paginated: the result is ONE object keyed by organization-unit number, or uuid when identifierType=uuid; `<key>` in the response schema is a placeholder, not a field.
- **get_single_hr_works_person_by_id** — Get one person's base data (names and identifiers); id is the person identifier, a personnel number unless personIdentifierType says otherwise (uuid, personId, personLicenseNumber, personIdentifierForKiosk).
- **create_a_hr_works_person** — Create employees in bulk: body {data: [...]} with 1-100 persons. HR WORKS notes that creating employees causes license costs.
Each item needs personId, personnelNumber.
Asynchronous: returns a jobId; poll person_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_persons_bulk_update** — Edit employees in bulk: body {data: [...]} with 1-100 persons; attributes you leave out stay unchanged (mandatory ones excepted).
Each item needs personId, personnelNumber.
Asynchronous: returns a jobId; poll person_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_persons_list_or_error** — List the company's persons, like list (GET /v2/persons), except that an HTTP error from HR WORKS (e.g. a 403 because the key pair has no permission for /persons, a 429 rate limit or a 5xx) is returned as a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified timeoff_requests, timeoff_balances and timesheet_entries mappings, which resolve the person of every record through this endpoint and must still return the records when it is not readable.
- **hr_works_persons_get_or_error** — Get a single person, like get (GET /v2/persons/{personIdentifier}), except that an HTTP error from HR WORKS (e.g. a 403 because the key pair has no permission for /persons, a 429 rate limit or a 5xx) is returned as a 200 result {"truto_error": {"status": <HTTP status>}} instead of failing. Used by the unified timeoff_requests.get mapping, which resolves the absence's employee from the licence number in the absence number and must still return the absence when that look-up is not permitted.
- **list_all_hr_works_persons_today** — Get today's day data per person: target working time and holidays always, plus the sources listed in includeData (such as working times, absences, sick leaves and travel requests).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **list_all_hr_works_posts** — List the job posts of the company (display name, name, key, uuid, organization unit and more); onlyActive=false also returns inactive posts.
Not paginated: everything comes back in one response.
- **get_single_hr_works_post_by_id** — Get one job post; id is the post's uuid (passing the older post id is deprecated by HR WORKS).
Returns: displayName, name, key, uuid, organizationUnitNumber, organizationUnitUuid, contactInformation, scopeOfActivities and more.
- **get_single_hr_works_project_customer_job_by_id** — Get the status and result of an asynchronous project customer write.
id is the jobId returned by project_customers.create, project_customers.bulk_update.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_project_customers** — List the time-tracking project customers (id, number, name, active flag, project counts); onlyActive=false also returns inactive customers.
Not paginated: everything comes back in one response.
- **get_single_hr_works_project_customer_by_id** — Get one time-tracking project customer (id, number, name, active flag, project counts); id is the customer number (the number field of project_customers.list, not its id field).
- **create_a_hr_works_project_customer** — Create project customers in bulk: body {data: [...]} with 1-1000 customers (name, number).
Asynchronous: returns a jobId; poll project_customer_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_project_customers_bulk_update** — Edit project customers in bulk: body {data: [...]} with 1-1000 items (id, number, name, isActive).
Asynchronous: returns a jobId; poll project_customer_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_receipt_types** — List the travel-expense receipt types per country (name, category, key, deduction, flags and accounts); filter with countryCodes (ISO 3166-1 alpha-3), categories or onlyActive=false.
Not paginated: Truto returns the receiptTypes object, keyed by 3-letter country code (`<key>` in the response schema is a placeholder) with the receipt types of each country.
- **list_all_hr_works_remote_work** — List remote-work entries per person in a date range (dates, half-days, number, status, working days); filter by persons or statusFilter, and split the range with interval (days, weeks or months).
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_remote_work_by_id** — Get one remote-work entry; id is its number (the person's license number, an underscore, then the running remote-work number), as returned by remote_work.list.
- **create_a_hr_works_remote_work** — Create remote-work entries in bulk: body {data: [...]} with 1-100 entries.
Each item needs beginDate, endDate, personnelNumber, statusIdentifier.
Asynchronous: returns a jobId; poll remote_work_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **update_a_hr_works_remote_work_by_id** — Edit one remote-work entry; id is its number. Send only what changes (beginDate, endDate, half-day flags, statusIdentifier).
Asynchronous: returns a jobId; poll remote_work_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **delete_a_hr_works_remote_work_by_id** — Delete one remote-work entry; id is its number (the person's license number, an underscore, then the running remote-work number).
Asynchronous: returns a jobId; poll remote_work_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_remote_work_bulk_update** — Edit remote-work entries in bulk: body {data: [...]} with 1-100 items, each identified by its remote-work number; attributes you leave out stay unchanged (mandatory ones excepted).
Each item needs number.
Asynchronous: returns a jobId; poll remote_work_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_remote_work_job_by_id** — Get the status and result of an asynchronous remote work write.
id is the jobId returned by remote_work.create, remote_work.bulk_update, remote_work.update, remote_work.delete.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **get_single_hr_works_sick_leave_job_by_id** — Get the status and result of an asynchronous sick leave write.
id is the jobId returned by sick_leaves.create, sick_leaves.bulk_update, sick_leaves.bulk_delete, sick_leaves.delete.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **get_single_hr_works_sick_leave_type_job_by_id** — Get the status and result of an asynchronous sick leave type write.
id is the jobId returned by sick_leave_types.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_sick_leave_types** — List the sick leave types configured in HR WORKS (key, name, color and visibility flags). Use the keys as the types filter of sick_leaves.list; onlyActive=false also returns deactivated types.
Not paginated: everything comes back in one response.
- **create_a_hr_works_sick_leave_type** — Create sick leave types in bulk: body {data: [...]} with 1-1000 types (name, key and flags such as isSicknessOfChild or useInMonthPayroll).
Asynchronous: returns a jobId; poll sick_leave_type_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_sick_leaves** — List sick leaves per person in a date range (type, dates, half-days, status, working days); filter by persons, types or statusFilter. interval splits the range (days, weeks, months); count=true returns per-type day totals instead.
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_sick_leaf_by_id** — Get one sick leave; id is its number (the person's license number, an underscore, then the running sick leave number), as returned by sick_leaves.list.
- **create_a_hr_works_sick_leaf** — Create sick leaves in bulk: body {data: [...]} with 1-100 sick leaves.
Each item needs type, beginDate, endDate, status, personnelNumber.
Asynchronous: returns a jobId; poll sick_leave_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **delete_a_hr_works_sick_leaf_by_id** — Delete one sick leave; id is its number (the person's license number, an underscore, then the running sick leave number).
Asynchronous: returns a jobId; poll sick_leave_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_sick_leaves_bulk_update** — Edit sick leaves in bulk: body {data: [...]} with 1-100 items, each identified by its sick leave number; attributes you leave out stay unchanged (mandatory ones excepted).
Each item needs number.
Asynchronous: returns a jobId; poll sick_leave_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_sick_leaves_bulk_delete** — Delete several sick leaves at once: numbers lists 1-100 sick leave numbers (license number, underscore, running number).
Required: numbers.
Asynchronous: returns a jobId; poll sick_leave_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_time_accounts** — Get monthly time accounts per person (working hours, target hours, time credit, carry-over and totals) for the months in monthYears (YYYY-MM, the current month by default); filter by persons.
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **list_all_hr_works_time_recording_regulations** — List the time recording regulations of the company (title, key, online and kiosk use, working time types, kiosks and more); their working time types are the types filter of working_times.list.
Not paginated: everything comes back in one response.
- **get_single_hr_works_time_tracking_project_assignment_job_by_id** — Get the status and result of an asynchronous project team assignment write.
id is the jobId returned by time_tracking_project_assignments.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **create_a_hr_works_time_tracking_project_assignment** — Add persons to project teams in bulk: body {data: [...]} with 1-500 assignments.
Each item needs personIdentifier, projectNumber.
Asynchronous: returns a jobId; poll time_tracking_project_assignment_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_time_tracking_project_job_by_id** — Get the status and result of an asynchronous time-tracking project write.
id is the jobId returned by time_tracking_projects.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_time_tracking_projects** — List time-tracking projects, optionally within a beginDate/endDate range; filter by numbers, projectIds, statusFilter, projectType, projectManagerPersonnelNumber or projectCustomerId.
Paginated by HR WORKS at 50 per page (limit is ignored); pass next_cursor with the same filters for the next page.
- **get_single_hr_works_time_tracking_project_by_id** — Get one time-tracking project (number, name, dates, status, budget, manager, customer and more); id is the project's id field (not its number).
Returns: id, number, name, beginDate, endDate, description, parentProject, statusIdentifier and more.
- **create_a_hr_works_time_tracking_project** — Create time-tracking projects in bulk: body {data: [...]} with 1-500 projects.
Each item needs id, name, beginDate, status, projectManagerPersonnelNumber, customerId.
Asynchronous: returns a jobId; poll time_tracking_project_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **list_all_hr_works_travel_requests** — List travel requests per person in a date range; choose persons with personIdentifierType and filter with statusFilter.
Required: beginDate and endDate (at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_travel_request_by_id** — Get one travel request (destination, dates, cost center and objective, person and more); id is its number (the person's personnel number, a dash, then the trip request number).
- **list_all_hr_works_vacation_types** — List the vacation types of the company per country (key, name, country, active, assigned and default flags). Filter with countryCodes (ISO 3166-1 alpha-3); onlyAssigned=false and onlyActive=false widen the result.
Not paginated: everything comes back in one response.
- **list_all_hr_works_wage_and_salary_types** — List the wage types of the company (number, name, type, export key, country, active flag); onlyActive decides whether only active wage types are returned.
Not paginated: everything comes back in one response.
- **get_single_hr_works_wage_payment_job_by_id** — Get the status and result of an asynchronous wage payment write.
id is the jobId returned by wage_payments.create.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_wage_payments** — List all wage payments (HR WORKS API v3): person uuid, month, gross amount, wage type and more; filter by wageTypeNumber.
Paginated (page size not documented; limit is ignored); pass next_cursor with the same filters for the next page. HR WORKS documents page links only for v2, so paging may stop after page 1.
- **get_single_hr_works_wage_payment_by_id** — Get one wage payment (HR WORKS API v3); id is the wage payment's uuid. Truto returns the data object of the v3 response.
Returns: personUuid, monthYear, grossAmount, wageTypeNumber, remark, endMonthYear, interval.
- **create_a_hr_works_wage_payment** — Create wage payments in bulk: body {data: [...]} (HR WORKS documents no maximum).
Each item needs personUuid, monthYear, grossAmount, wageTypeNumber.
Asynchronous: returns a jobId; poll wage_payment_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_webhook_job_by_id** — Get the status and result of an asynchronous webhook write.
id is the jobId returned by webhooks.create, webhooks.bulk_update, webhooks.bulk_delete.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_webhooks** — List the server-event webhooks of the company (id, name, resource, action, url, active flag); filter by resource (person, applicant, absence, sickLeave, remoteWork), action, or add inactive ones with onlyActive=false.
Not paginated: everything comes back in one response.
- **create_a_hr_works_webhook** — Create server-event webhooks in bulk: body {data: [...]} with 1-500 webhooks (HTTPS url, resource, action).
Each item needs action, name, resource, url.
Asynchronous: returns a jobId; poll webhook_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_webhooks_bulk_update** — Edit webhooks in bulk: body {data: [...]} with 1-500 items, each identified by its webhook id.
Each item needs action, name, resource, url, id.
Asynchronous: returns a jobId; poll webhook_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_webhooks_bulk_delete** — Delete webhooks by id: numbers lists 1-500 webhook ids, as returned by webhooks.list.
Required: numbers.
Asynchronous: returns a jobId; poll webhook_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **get_single_hr_works_working_time_job_by_id** — Get the status and result of an asynchronous working time write.
id is the jobId returned by working_times.create, working_times.bulk_update, working_times.bulk_delete.
The job is pending until HR WORKS has processed the write, then finished with the written records or the errors (write errors are reported here inside an HTTP 200). Kept for two weeks.
- **list_all_hr_works_working_time_kiosks** — List the time-tracking kiosks (terminals) of the company: name, id and whether they use QR codes or PINs.
Not paginated: everything comes back in one response.
- **list_all_hr_works_working_times** — List working times per person in a date range, with worked, target and break minutes per interval; filter by persons or types. Only persons with a time recording regulation in the range are returned.
Required: beginDate and endDate (YYYY-MM-DD, at most one year apart; at most 31 days with interval=days).
Each page is ONE object keyed by person identifier (`<key>` in the response schema is a placeholder, not a field) holding up to 50 persons; HR WORKS fixes the page size, so limit is ignored. Pass next_cursor with the same filters for the next page.
- **get_single_hr_works_working_time_by_id** — Get one working time (HR WORKS API v3); id is the working time's uuid. Truto returns the data object of the v3 response.
Returns: beginDateAndTime, endDateAndTime, beginDateTime, endDateTime, clockInKioskId, clockOutKioskId, comment, workingTimeType and more.
- **create_a_hr_works_working_time** — Create working times in bulk: body {data: [...]} with 1-1000 entries; deleteOverlappingWorkingTimes=true deletes existing working times that the new ones overlap.
Each item needs beginDateAndTime.
Asynchronous: returns a jobId; poll working_time_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_working_times_bulk_update** — Edit existing working times in bulk: body {data: [...]} with 1-1000 entries; deleteOverlappingWorkingTimes=true deletes other working times that the edited ones overlap.
Each item needs beginDateAndTime.
Asynchronous: returns a jobId; poll working_time_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).
- **hr_works_working_times_bulk_delete** — Delete working times: numbers lists 1-1000 working time numbers (personnel number, an underscore, then the begin time in Unix seconds, e.g. 1_1683093600).
Required: numbers.
Asynchronous: returns a jobId; poll working_time_jobs.get with it until the job is finished (write errors are reported there inside an HTTP 200).

## How it works

1. **Link your customer's HR WORKS account.** Use Truto's frontend SDK; we handle every OAuth and API key flow so you don't need to create the OAuth app.
2. **Authentication is automatic.** Truto refreshes tokens, stores credentials securely, and injects them into every API request.
3. **Call Truto's API to reach HR WORKS.** The Proxy API is a 1-to-1 mapping of the HR WORKS API.
4. **Get a unified response format.** Every response uses a single shape, with cursor-based pagination and data in the `result` field.

## Use cases

- **Localize spend management for DACH customers** — Corporate card and expense platforms can sync HR WORKS cost centers, cost objectives, and organization units to categorize spend, then push transactions back as expense reports so German finance teams keep a single compliant record.
- **Automate IT provisioning from HR events** — IAM and provisioning tools can subscribe to HR WORKS person webhooks to create accounts for new hires on their join date and revoke access when employees become inactive — no manual IT tickets required.
- **Power workforce and shift planning with real absence data** — Scheduling SaaS can pull absences, sick leaves, remote work, holidays, and available working hours to avoid double-booking employees who are off, and push planned shifts back as working times.
- **Bridge ATS to HRIS for hired candidates** — Applicant tracking systems can push hired candidates into HR WORKS as onboarding documents along with their contract and resume files, eliminating the manual handoff between recruiting and HR admin.
- **Sync billable project hours into payroll-ready records** — Project management and agency tools can mirror time tracking projects and project customers, then push logged working times into HR WORKS so hours flow directly into DACH-compliant payroll.

## What you can build

- **Two-way employee master data sync** — Read and write HR WORKS persons, person master data, and employments to keep employee records aligned with your product in both directions.
- **Unified time-off module** — Surface vacation absences, sick leaves, and remote work as distinct primitives — or use the unified Timeoff Requests/Balances/Types/Policies endpoints for a single consistent model.
- **Expense and travel report ingestion** — List and create expense reports, attach receipts by receipt type, and track travel requests scoped to the correct personnel number.
- **Org hierarchy mirroring** — Map HR WORKS permanent establishments, organization units, cost centers, and cost objectives into your product's groups, departments, and locations.
- **Personnel file and onboarding document uploads** — Push contracts, policies, and onboarding paperwork directly into personnel file entries or onboarding documents, with file attachments included.
- **Real-time HR event listeners** — Create HR WORKS webhooks for resources like person, applicant, absence, and sick leave to trigger downstream workflows the moment they change.

## FAQs

### How does authentication work for HR WORKS?

End users connect their HR WORKS account through Truto's hosted connect flow. Truto handles credential storage, token refresh, and any API-key or OAuth handshake so you never touch raw secrets.

### Why do some writes not appear immediately after a create or update call?

HR WORKS processes most mutations asynchronously — the API returns a job ID that must be polled until it resolves. Truto exposes the underlying job endpoints (e.g. person jobs, absence jobs, working time jobs) so you can confirm final status before updating your UI.

### Do you support real-time events or only polling?

Both. You can use HR WORKS native webhooks for events like person, applicant, absence, and sick leave changes, or fall back to scheduled list calls. Truto normalizes delivery so your handlers look the same across tenants.

### Can I use a unified HRIS schema instead of HR WORKS–specific fields?

Yes. Truto's Unified HRIS API covers Employees, Employments, Employee Compensations, Bank Info, Groups, Locations, Timeoff Balances, Timeoff Policies, Timeoff Requests, Timeoff Types, and Timesheet Entries. You can also drop down to the raw HR WORKS tools when you need DACH-specific fields like tax class or cost objective.

### How are vacation, sick leave, and remote work represented?

HR WORKS keeps them as separate resources — absences, sick leaves, and remote work — each with its own list, create, update, delete, and bulk operations. The unified Timeoff endpoints consolidate them into a common model when you want a simpler surface.

### Can I upload files like contracts or receipts through the integration?

Yes. You can create personnel file entry files, onboarding document files, document pool files, payroll files, person documents, and job application files directly through the HR WORKS tools, keeping employee paperwork in sync with your product.
