# HR WORKS MCP connector

Connecting HR WORKS to Elaichi lets Claude, ChatGPT, Cursor and the Elaichi Agent look up absences, sick leave, remote work, applicants, working hours and cost centers within each person's own HR WORKS access, with every call logged.

Source: https://elaichi.ai/connectors/hrworks/

## Facts

| | |
| --- | --- |
| Application | HR WORKS |
| Category | HRIS |
| AI tools | 144 |
| Authentication | Connects with an API key |
| Bring your own OAuth app | No |
| MCP endpoint | https://api.elaichi.ai/mcp |
| Works with | Claude, ChatGPT, Cursor, any MCP client, and the Elaichi Agent |
| Tools advertised by name | No. Connected tools are never listed one by one, however few there are. The endpoint advertises `search_tools` and `execute_tool` instead |

## What you can ask once HR WORKS is connected

- Who on the support team is out sick next week?
- List open applicants for the sales engineer role.
- Show remote work days for the Berlin cost center this month.

## Connect HR WORKS in Elaichi

This happens once for the organization, before any client is involved.

1. Open Connections, choose Add connection, and pick HR WORKS.
2. Optionally set Share with, then press Connect.
3. Paste a HR WORKS API key. One person generates a token in HR WORKS and pastes it once. Everyone else works through Share with, and never sees it.

Credentials are vaulted and nobody, including the AI, reads them back. The connection becomes a toolbox immediately, so you can curate which HR WORKS tools are exposed, rename them, or freeze arguments before anyone points a client at it.

## HR WORKS MCP connector for Claude

Endpoint: https://api.elaichi.ai/mcp

1. Open Customize, then Connectors.
2. Press Add.
3. Name it, paste the MCP server URL, then Continue.
4. Sign in and approve.

On Team and Enterprise, an Owner adds it once. Everyone else turns it on for themselves.

## HR WORKS MCP connector for ChatGPT

Endpoint: https://api.elaichi.ai/mcp

1. Open Plugins, then press the + button.
2. Name it and paste the endpoint into Server URL.
3. Leave Authentication on OAuth, then tick the risk acknowledgement.
4. Press Create, then sign in and approve.

Works on the web today. The plugin directory lives at chatgpt.com/plugins.

## HR WORKS MCP connector for Cursor

Endpoint: https://api.elaichi.ai/mcp

1. Open `~/.cursor/mcp.json`.
2. Add the endpoint under `mcpServers`.
3. Reload Cursor, then sign in and approve.

Set up per machine, so repeat it on each computer you work from.

## Connect HR WORKS to any MCP client

Endpoint: https://api.elaichi.ai/mcp

1. Add the endpoint as a remote MCP server.
2. Sign in and approve.

The Elaichi Agent already has these tools, with nothing to set up.

## What the consent screen decides

Only Read is granted by default, which is not enough to call a HR WORKS tool. Over MCP there is no trusted place to confirm a write in the moment, so the consent screen is the standing approval rather than a formality. Grant Read and Run tools. Think hard before granting Delete, which reaches into connected apps and cannot be undone.

## What teams do with HR WORKS through Elaichi

### Log an absence straight from the request

HR. When someone messages that they will be off Thursday, ask the agent to create the absence in HR WORKS with the right absence type, and fix the dates later if plans change.

### Add a new absence type before rollout

HR. Set up a new category such as parental leave or a company day in HR WORKS, then check the full list of absence types reads cleanly before announcing it.

### See who is out before planning the week

Team leads. Ask which people on the team have absences or remote work days next week and what working hours are still available, without opening HR WORKS.

### Check on an applicant before the interview

Recruiting. Pull up an applicant's record in HR WORKS a minute before the call, or list everyone in the pipeline for a role to prepare the weekly hiring update.

### Keep cost center assignments current

Finance. When someone moves teams, assign them to the new cost center in HR WORKS and confirm the cost center list matches what the ledger expects.

### Pull accumulated sick leave for the month

Payroll. Ask for accumulated absences and sick leave across the company before the payroll cutoff, so the numbers that reach payroll match what HR WORKS holds.

## Frequently asked questions

### How do I connect HR WORKS to Claude?

Connect HR WORKS in Elaichi by pasting in your HR WORKS API key. There is no OAuth application to register and no client ID or secret to generate. Then in Claude open Customize, then Connectors, then Add, and paste the endpoint https://api.elaichi.ai/mcp. Sign in to Elaichi when Claude asks and HR WORKS is ready.

### Does HR WORKS work with ChatGPT and Cursor as well as Claude?

Yes. Once HR WORKS is connected in Elaichi, the same endpoint, https://api.elaichi.ai/mcp, works in Claude, ChatGPT, Cursor, any other MCP client and the Elaichi Agent. You connect HR WORKS once and every client picks it up.

### What can an AI agent actually do with my HR WORKS data?

An agent connected to HR WORKS can list, create, update and delete absences, look up and add absence types, show accumulated absences, sick leave and remote work, check available working hours, find applicants, and list or assign cost centers. In practice that means asking who is out next week, logging a day off from a chat message, or pulling sick leave totals before payroll. Short, concrete asks such as "who is off Friday" work better than long paragraphs.

### Does connecting HR WORKS give the AI access to every employee's data?

No. The HR WORKS connection carries the permissions of the API key used to connect it, and each person using it signs in to Elaichi as themselves, so their calls run inside what the connection and their Elaichi role allow. Elaichi can narrow that further with restrictions per action. It can never widen what HR WORKS itself permits.

### Can my team share one HR WORKS connection?

Yes. One person connects HR WORKS with the API key and shares the connection with a team in Elaichi, and nobody else on the team ever sees or handles that key. Each teammate still signs in to Elaichi as themselves, so the audit log names the actual person behind every HR WORKS call.

### Can I stop an agent from deleting or changing absences in HR WORKS?

Yes. In Elaichi you restrict HR WORKS actions one by one, so you can allow reading absences and cost centers while blocking creating, updating or deleting them. A restricted action is never advertised to Claude, ChatGPT, Cursor or any other client, so no prompt, however worded, can reach it.

### What happens to an HR WORKS connection when someone leaves?

Offboarding the person in Elaichi ends their access to HR WORKS through every client at once. A shared HR WORKS connection keeps working for everyone else on the team. If you disconnect HR WORKS in Elaichi, it disappears from Claude, ChatGPT, Cursor and every other client in one step.

### Does the HR WORKS MCP connector work with Gemini, Codex, Claude Code or other MCP clients?

Yes. HR WORKS is reached over the same MCP endpoint every client uses, so anything that speaks MCP can call it — Gemini, Codex, Claude Code, Windsurf, Cline, Zed and OpenCode among them — alongside Claude, ChatGPT, Cursor, and the Elaichi Agent. The tools on offer and the access behind them are identical whichever client asks. Only the setup screen differs.

## All 144 HR WORKS tools

Every tool below is callable through https://api.elaichi.ai/mcp once HR WORKS is connected, subject to the toolbox it is in and the restrictions on the caller.

- **Get single HR WORKS absence job by ID** (Get). 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). 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). 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). 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). 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). 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). 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). 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** (Update). 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). 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** (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). 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). 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** (List). 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** (List). 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** (List). 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). 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). 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** (List). 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). 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** (Create). 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). 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). 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). 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). 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). 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). 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** (Create). 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). 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). 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). 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** (Create). 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). 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). 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). 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). 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** (Update). 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** (List). 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). 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). 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). 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** (Create). 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). 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). 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). 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). 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** (Update). 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). 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** (List). 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). 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.
- ...and 94 more tools. Call `tools/list` via the MCP endpoint, or see the full catalog via the API, for the complete set.
