Overview
Xenia integrates with 50+ HRIS (Human Resource Information System) providers through Merge.dev , enabling automatic employee synchronization, role assignment, and location mapping.
Supported Providers:
BambooHR, Rippling, Workday, ADP
Gusto, Paylocity, Paycom, Paychex
UKG, Ceridian, Namely, Zenefits
And 40+ more HRIS platforms
Key Capabilities:
One-time and continuous employee sync
Automatic role assignment via job title mapping
Multi-location assignment (1:M mapping)
Review-based or automatic employee onboarding
Real-time sync via webhooks
Prerequisites
Before using HRIS integration:
Feature Flag : HRIS must be enabled for your workspace
Permissions : User must have HRIS management permissions
Role Setup : Define roles in Xenia for job title mapping
Connection Management
Get Link Token
Generate a Merge.dev link token to initiate the connection flow.
curl -X POST "https://api.xenia.team/api/v1/mgt/users/hris/merge/link-token" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"endUserEmail": "[email protected] ",
"endUserOrganization": "Company Name",
"categories": ["hris"]
}'
Request Body:
Field Type Required Description endUserEmailstring No Admin email for Merge notifications endUserOrganizationstring No Organization name displayed in Merge Link endUserOriginIdstring No Custom identifier for the connection integrationSlugstring No Pre-select a specific HRIS (e.g., bamboohr) categoriesarray No Filter integrations (default: ["hris"])
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"linkToken" : "link_token_xxx" ,
"integrationName" : null ,
"expiresAt" : "2024-12-24T10:00:00Z"
}
}
Complete Connection
After the user completes the Merge Link UI, exchange the public token for a permanent connection.
curl -X POST "https://api.xenia.team/api/v1/mgt/users/hris/merge/complete" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"publicToken": "pt_xxx_from_merge_link",
"integrationSlug": "bamboohr",
"integrationName": "BambooHR"
}'
Request Body:
Field Type Required Description publicTokenstring Yes Token received from Merge Link callback integrationSlugstring No HRIS provider identifier integrationNamestring No Display name for the integration linkedAccountIdstring No Merge’s linked account ID categoriesarray No Integration categories metadataobject No Custom metadata to store
Response:
{
"status" : true ,
"code" : 200 ,
"message" : "Successfully connected to BambooHR" ,
"data" : {
"id" : "account-uuid" ,
"accountId" : "account-uuid" ,
"integrationSlug" : "bamboohr" ,
"integrationName" : "BambooHR" ,
"provider" : "merge" ,
"status" : "active" ,
"categories" : [ "hris" ],
"linkedAccountId" : "merge-linked-account-id" ,
"connectedAt" : "2024-12-23T10:00:00Z" ,
"workspaceId" : "workspace-uuid"
}
}
List Connections
Retrieve all HRIS connections for a workspace with sync status.
curl -X GET "https://api.xenia.team/api/v1/mgt/hris/connections" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Response:
{
"status" : true ,
"code" : 200 ,
"data" : [
{
"id" : "account-uuid" ,
"provider" : "Merge.dev" ,
"integrationSlug" : "bamboohr" ,
"integrationName" : "BambooHR" ,
"categories" : [ "hris" ],
"status" : "active" ,
"syncState" : "synced" ,
"hasCompletedInitialSync" : true ,
"lastSyncedAt" : "2024-12-23T08:00:00Z" ,
"lastSyncStatus" : "success" ,
"syncError" : null ,
"employeeCount" : 150 ,
"syncProgress" : {
"recordsProcessed" : 150 ,
"recordsSucceeded" : 148 ,
"recordsFailed" : 2 ,
"employeesCreated" : 10 ,
"employeesUpdated" : 138
},
"createdAt" : "2024-12-01T10:00:00Z" ,
"updatedAt" : "2024-12-23T08:00:00Z"
}
]
}
Sync States:
State Description waiting_for_initial_syncConnection established, waiting for first sync initial_sync_in_progressFirst sync running syncingIncremental sync in progress syncedAll data synchronized errorSync failed (check syncError) unlinkedConnection disconnected
Disconnect Connection
Unlink an HRIS connection. This preserves user data but stops sync.
curl -X DELETE "https://api.xenia.team/api/v1/mgt/hris/connections/{accountId}" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Response:
{
"status" : true ,
"code" : 200 ,
"message" : "Successfully disconnected from BambooHR. Users and employee data have been preserved." ,
"data" : {
"id" : "account-uuid" ,
"integration" : "BambooHR" ,
"disconnectedAt" : "2024-12-23T10:00:00Z" ,
"deletedMirrors" : 5 ,
"deletedTokens" : 1
}
}
Employee Management
List Stored Employees
Retrieve HRIS employees stored in Xenia with advanced filtering.
curl -X GET "https://api.xenia.team/api/v1/mgt/hris/employees?page=1&pageSize=25&employmentStatus=ACTIVE&includeCompany=true" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Query Parameters:
Parameter Type Default Description pagenumber 1 Page number pageSizenumber 25 Items per page (max: 200) searchstring - Search name, email, employee number employmentStatusstring/array - Filter: ACTIVE, INACTIVE, TERMINATED accountIdstring - Filter by HRIS account homeLocationIdstring - Filter by home location workLocationIdstring - Filter by work location includeCompanyboolean true Include company details includeLocationsboolean true Include location details includeManagerboolean false Include manager details includeDeletedboolean false Include soft-deleted records sortBystring createdAtSort field sortOrderstring DESCSort direction: ASC or DESC
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"employees" : [
{
"id" : "employee-uuid" ,
"WorkspaceId" : "workspace-uuid" ,
"HRISAccountId" : "account-uuid" ,
"remoteId" : "hris-employee-id" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"preferredName" : "Johnny" ,
"displayFullName" : "John Doe" ,
"workEmail" : "[email protected] " ,
"personalEmail" : "[email protected] " ,
"employeeNumber" : "EMP001" ,
"employmentStatus" : "ACTIVE" ,
"startDate" : "2023-01-15" ,
"endDate" : null ,
"remoteCreatedAt" : "2023-01-10T00:00:00Z" ,
"remoteModifiedAt" : "2024-12-20T00:00:00Z" ,
"Company" : {
"id" : "company-uuid" ,
"legalName" : "Company Inc." ,
"displayName" : "Company"
},
"HomeLocation" : {
"id" : "location-uuid" ,
"name" : "Headquarters" ,
"remoteId" : "hris-location-id"
},
"xeniaStatus" : {
"isXeniaUser" : true ,
"status" : "ACTIVE" ,
"statusLabel" : "Active User" ,
"userId" : "user-uuid" ,
"workspaceUserId" : "workspace-user-uuid" ,
"inviteStatus" : "Active" ,
"canInvite" : false ,
"isLinkedToHRIS" : true ,
"hrisEmployeeId" : "employee-uuid"
}
}
],
"pagination" : {
"page" : 1 ,
"pageSize" : 25 ,
"total" : 150 ,
"totalPages" : 6 ,
"hasMore" : true
}
}
}
Xenia Status Values:
Status Description NO_EMAILEmployee has no work email NOT_INVITEDEligible for invitation NOT_IN_WORKSPACEUser exists but not in workspace DELETEDUser was deleted ACTIVEActive Xenia user PENDING_INVITEInvitation sent, not accepted INACTIVEUser deactivated
Bulk Invite Employees
Queue bulk invitations for HRIS employees to Xenia.
# Simple mode - same role for all
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/accounts/{accountId}/employees/bulk-invite" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"employeeIds": ["emp-uuid-1", "emp-uuid-2", "emp-uuid-3"],
"roleId": "default-role-uuid"
}'
# Advanced mode - per-employee configuration
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/accounts/{accountId}/employees/bulk-invite" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"employees": [
{
"employeeId": "emp-uuid-1",
"roleId": "manager-role-uuid",
"locationId": "hq-location-uuid"
},
{
"employeeId": "emp-uuid-2",
"roleId": "staff-role-uuid",
"locationId": "branch-location-uuid"
}
]
}'
Request Body (Simple Mode):
Field Type Required Description employeeIdsarray Yes* Employee UUIDs to invite (max: 3000) roleIdstring No Default role for all employees locationMappingobject No HRIS location ID → Xenia location ID map
Request Body (Advanced Mode):
Field Type Required Description employeesarray Yes* Per-employee configuration (max: 3000) employees[].employeeIdstring Yes Employee UUID employees[].roleIdstring No Role for this employee employees[].locationIdstring No Location for this employee locationMappingobject No HRIS location ID → Xenia location ID map
Use either employeeIds OR employees, not both. They are mutually exclusive.
Response:
{
"status" : true ,
"code" : 200 ,
"message" : "Bulk invite job queued. 45 employee(s) will be invited." ,
"data" : {
"jobId" : "bull-job-id" ,
"total" : 50 ,
"eligible" : 45 ,
"skipped" : [
{
"employeeId" : "emp-uuid-4" ,
"email" : null ,
"name" : "Jane Smith" ,
"reason" : "NO_WORK_EMAIL"
},
{
"employeeId" : "emp-uuid-5" ,
"email" : "[email protected] " ,
"name" : "Bob Johnson" ,
"reason" : "EMPLOYMENT_STATUS_INACTIVE"
}
]
}
}
Job Title Mapping
Map HRIS job titles to Xenia roles for automatic role assignment during invite.
List Job Titles
Get unique job titles from synced employees with mapping status.
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/job-titles?accountId={accountId}&sortBy=employeeCount&sortOrder=DESC" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Response:
{
"status" : true ,
"code" : 200 ,
"data" : [
{
"jobTitle" : "Software Engineer" ,
"employeeCount" : 25 ,
"isMapped" : true ,
"mappedToRole" : {
"roleId" : "developer-role-uuid" ,
"roleTitle" : "Developer"
}
},
{
"jobTitle" : "Store Manager" ,
"employeeCount" : 12 ,
"isMapped" : false ,
"mappedToRole" : null
}
],
"meta" : {
"total" : 15 ,
"page" : 1 ,
"pageSize" : 50 ,
"totalPages" : 1
}
}
List Job Title Mappings
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/job-title-mappings?accountId={accountId}" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Create/Upsert Job Title Mappings
Create or update job title mappings in bulk.
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/job-title-mappings" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"accountId": "account-uuid",
"create": [
{
"hrisJobTitle": "Software Engineer",
"roleId": "developer-role-uuid"
},
{
"hrisJobTitle": "Store Manager",
"roleId": "manager-role-uuid"
}
],
"delete": ["old-mapping-uuid-1"]
}'
Request Body:
Field Type Required Description accountIdstring Yes HRIS account UUID createarray No Mappings to create/update create[].hrisJobTitlestring Yes Job title from HRIS create[].roleIdstring Yes Xenia role UUID deletearray No Mapping UUIDs to delete
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"created" : [
{
"id" : "mapping-uuid-1" ,
"hrisJobTitle" : "Software Engineer" ,
"roleId" : "developer-role-uuid" ,
"roleTitle" : "Developer" ,
"isActive" : true ,
"createdAt" : "2024-12-23T10:00:00Z"
}
],
"updated" : [
{
"id" : "mapping-uuid-2" ,
"hrisJobTitle" : "Store Manager" ,
"roleId" : "manager-role-uuid" ,
"roleTitle" : "Manager" ,
"isActive" : true ,
"updatedAt" : "2024-12-23T10:00:00Z"
}
],
"deleted" : [
{
"id" : "old-mapping-uuid-1" ,
"deleted" : true
}
],
"errors" : []
}
}
Location Mapping
Map HRIS locations to Xenia locations. Supports 1:M mapping (one HRIS location to multiple Xenia locations).
List HRIS Locations
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/locations?accountId={accountId}" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"locations" : [
{
"id" : "hris-location-uuid" ,
"WorkspaceId" : "workspace-uuid" ,
"HRISAccountId" : "account-uuid" ,
"remoteId" : "bamboo-location-123" ,
"name" : "Downtown Office" ,
"phoneNumber" : "+1-555-0100" ,
"street1" : "123 Main St" ,
"city" : "New York" ,
"state" : "NY" ,
"zipCode" : "10001" ,
"country" : "US" ,
"locationType" : "office"
}
],
"pagination" : {
"page" : 1 ,
"pageSize" : 25 ,
"total" : 8 ,
"totalPages" : 1
}
}
}
Create/Upsert Location Mappings
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/location-mappings" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"accountId": "account-uuid",
"create": [
{
"hrisLocationId": "hris-location-uuid",
"locationId": "xenia-location-uuid-1",
"isPrimary": true
},
{
"hrisLocationId": "hris-location-uuid",
"locationId": "xenia-location-uuid-2",
"isPrimary": false
}
]
}'
Request Body:
Field Type Required Description accountIdstring Yes HRIS account UUID createarray No Mappings to create/update create[].hrisLocationIdstring Yes HRIS location UUID create[].locationIdstring Yes Xenia location UUID create[].isPrimaryboolean No Set as primary location (default: false) deletearray No Mapping UUIDs to delete
1:M Mapping : One HRIS location can map to multiple Xenia locations. When an employee is invited, they’ll be assigned to ALL mapped locations, with defaultLocationId set from the primary mapping.
Auto-Sync Configuration
Configure automatic employee synchronization behavior.
Get Auto-Sync Settings
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/settings" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"id" : "settings-uuid" ,
"WorkspaceId" : "workspace-uuid" ,
"HRISAccountId" : "account-uuid" ,
"autoSyncEnabled" : true ,
"autoInviteEnabled" : true ,
"autoDeactivateEnabled" : true ,
"inviteMode" : "review_required" ,
"defaultRoleId" : "basic-user-role-uuid" ,
"defaultLocationId" : "hq-location-uuid" ,
"notifyAdmins" : true ,
"notifyOnNewHires" : true ,
"notifyOnTerminations" : true ,
"DefaultRole" : {
"id" : "basic-user-role-uuid" ,
"title" : "Basic User"
},
"DefaultLocation" : {
"id" : "hq-location-uuid" ,
"name" : "Headquarters"
}
}
}
Update Auto-Sync Settings
curl -X PUT "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/settings" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"autoSyncEnabled": true,
"autoInviteEnabled": true,
"autoDeactivateEnabled": true,
"inviteMode": "review_required",
"defaultRoleId": "basic-user-role-uuid",
"defaultLocationId": "hq-location-uuid",
"notifyAdmins": true,
"notifyOnNewHires": true,
"notifyOnTerminations": true
}'
Request Body:
Field Type Description autoSyncEnabledboolean Enable automatic data sync autoInviteEnabledboolean Automatically invite new employees autoDeactivateEnabledboolean Auto-deactivate terminated employees inviteModestring automatic or review_requireddefaultRoleIdstring Default role for new employees defaultLocationIdstring Default location for new employees notifyAdminsboolean Send admin notifications notifyOnNewHiresboolean Notify on new hire detection notifyOnTerminationsboolean Notify on termination detection
Trigger Manual Sync
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/trigger-sync" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"message" : "Sync job queued successfully" ,
"jobId" : "bull-queue-job-id"
}
}
Employee Review Workflow
When inviteMode is set to review_required, new employees are queued for admin review.
List Pending Employees
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/pending-employees?changeType=new_hire" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Query Parameters:
Parameter Type Description changeTypestring Filter: new_hire, update, termination, re_hire pagenumber Page number (default: 1) pageSizenumber Items per page (default: 50)
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"mirrors" : [
{
"id" : "mirror-uuid" ,
"HRISAccountId" : "account-uuid" ,
"HRISEmployeeId" : "employee-uuid" ,
"status" : "pending_review" ,
"changeType" : "new_hire" ,
"matchConfidence" : "high" ,
"MatchedUserId" : null ,
"HRISEmployee" : {
"id" : "employee-uuid" ,
"remoteId" : "hris-123" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"workEmail" : "[email protected] "
},
"MatchedUser" : null ,
"createdAt" : "2024-12-23T08:00:00Z"
}
],
"pagination" : {
"page" : 1 ,
"pageSize" : 50 ,
"total" : 5 ,
"totalPages" : 1
}
}
}
Change Types:
Type Description new_hireNew employee detected in HRIS updateEmployee data changed terminationEmployee marked as terminated re_hirePreviously terminated employee reactivated
Approve Employees
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/approve-employees" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"mirrorIds": ["mirror-uuid-1", "mirror-uuid-2"]
}'
Response:
{
"status" : true ,
"code" : 200 ,
"data" : {
"approved" : 2 ,
"processing" : {
"queued" : 2 ,
"deactivated" : 0 ,
"errors" : 0
}
}
}
Reject Employees
curl -X POST "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/reject-employees" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}" \
-H "Content-Type: application/json" \
-d '{
"mirrorIds": ["mirror-uuid-3"],
"rejectionReason": "Contractor - not eligible for Xenia access"
}'
View Sync History
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/employee-history?status=completed&page=1" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
View Webhook Logs
For debugging sync issues, view Merge.dev webhook events.
curl -X GET "https://api.xenia.team/api/v1/mgt/hris-integration/auto-sync/accounts/{accountId}/webhook-logs?processingStatus=failed" \
-H "x-client-key: YOUR_CLIENT_KEY" \
-H "x-client-secret: YOUR_CLIENT_SECRET" \
-H "workspace-id: {workspaceId}"
Permissions Reference
Permission Description CAN_ACCESS_HRISBase access to HRIS features (required for all) CAN_VIEW_HRIS_INTEGRATIONRead-only access to connections and employees CAN_MANAGE_HRIS_INTEGRATIONFull management of HRIS connections CAN_MANAGE_HRIS_EMPLOYEESApprove/reject employees, bulk invite CAN_MANAGE_HRIS_LOCATIONSManage location mappings CAN_MANAGE_HRIS_ROLESManage job title mappings CAN_MANAGE_HRIS_CONFIGURATIONSConfigure auto-sync settings
Common Workflows
Initial HRIS Setup
Employee Onboarding Flow
Best Practices
Configure mappings before bulk invite
Set up job title → role mappings and location mappings before inviting employees. This ensures automatic role and location assignment.
Use review mode for initial sync
Start with inviteMode: "review_required" to manually verify the first batch of employees before switching to automatic mode.
Monitor sync status regularly
Check connection status and webhook logs periodically to catch sync issues early. Set up admin notifications for failures.
Handle terminated employees
Enable autoDeactivateEnabled to automatically deactivate users when they’re terminated in the HRIS. This maintains security without manual intervention.
Use 1:M location mapping strategically
When an employee works across multiple Xenia locations, map their HRIS location to all relevant Xenia locations with one marked as primary.