Implementation Overview
Implementation Type: Kombo-based IntegrationNote Format: Plain Text
Status: In Development UKG Ready integrates through Kombo’s unified API, layered with native UKG Ready REST calls via Kombo passthrough. Kombo’s unified data alone cannot deliver phone numbers, job custom fields, applicant custom-field writeback, or applicant-visible notes, so those paths call the native
/ta/rest/v2/companies/{company_reference}/... API directly.
Configuration
Setup
Kombo Connection Registration
Create the Kombo connection link from Tenzo, so Kombo receives the Tenzo org ID asEnd user origin ID. If the integration is connected directly in Kombo without it, the integration_created webhook carries no org ID and Tenzo never auto-creates the ats_integrations row. Recovery: reconnect through a Tenzo-generated link, or manually replay the integration_created webhook with the Tenzo org ID.
Company Reference
Native paths are/ta/rest/v2/companies/{company_reference}/.... UKG accepts either the numeric company ID or a short-name reference in !short_name format (the Kombo sandbox uses !kombo). Validate the value with a native GET after configuring — a wrong reference fails with Company not found, it is not arbitrary. If the short name does not work for a tenant, use its numeric UKG company ID.
Customer-Side Field Setup (“Tenzo Active”)
The customer creates a UKG Ready job requisition custom field and sets it on the jobs Tenzo should process. Sandbox-proven gotcha (verified by live A/B toggle): with “Display to Applicant” unchecked, UKG strips bothcustom_field_configs and custom_fields from the requisition API response while the job itself remains visible — making a hidden field indistinguishable from a nonexistent one. The checkbox must be enabled for Tenzo to read the field at all. No workaround through the Kombo passthrough endpoint is currently confirmed; customers who do not want the field applicant-visible need Kombo or UKG to confirm whether a privileged internal-requisition endpoint exists.
Stage IDs
Capture real stage IDs from synced job payloads after connection (prefer job-specific stage IDs). Stage movement through Kombo is the disposition mechanism, so Copper State’s routing stages (Application Received, above/below-threshold, Tenzo Recruiter Review) must be mapped before go-live.Feature Coverage
Streaming Operations
Streaming Operations
- Stream Jobs (Inherited)
- Stream Applications (Inherited)
- Stream Candidates (Inherited + native phone enrichment in batches of 8)
Application Management
Application Management
- Move to Stage (Inherited)
- Get Disposition Reasons — returns an empty list
- Reject Applications (Single & Bulk) — no-op with logging; use stage movement to disposition
- Create Applications (Inherited, candidate phone-enriched first)
- Get All Stages (Inherited)
Job Management
Job Management
- Get Job by ID (Inherited)
- Get Enhanced Job — Kombo job + native requisition detail (
custom_field_configsandcustom_fieldsmapped tocustom_field_values) - Batch Job Fetch — follows Kombo cursors until the requested limit, then enriches each job through native passthrough with bounded concurrency (8)
Candidate Management
Candidate Management
- Get Candidate by ID (Inherited + phone enrichment)
- Get Candidate by Remote ID (Inherited + phone enrichment)
- Find by Details (Inherited)
- Get Resume (Inherited)
Notes & Attachments
Notes & Attachments
- Add Note to Application — writes to the Job Application
applicant_commentvia native passthrough (GET existing → append → PUT). Kombo’s generic note action targetscompany_comment, which recruiters do not read in the customer’s workflow. Sandbox-verified for post-interview summaries and SMS/email activity notes. - Add Note to Candidate — Not Supported (no-op)
- Attachment upload — Inherited via Kombo and sandbox-verified, but UKG lists uploaded application attachments on the candidate document list, not on the application.
- Attachment readback —
GETapplication attachments is not supported for UKG Ready (Kombo returnsINTEGRATION.ACTION_NOT_AVAILABLE); candidate attachment fetching is limited to CV/resume-style documents.
Resume Ingestion
Resume Ingestion
Only files uploaded through UKG’s dedicated resume source come back through Kombo as type
CV and feed automatic resume ingestion. PDFs added via UKG’s Supporting Documents modal surface as attachment type OTHER and are not picked up as resumes.Custom Fields
Custom Fields
- Job Custom Fields — read-only; schema inferred from
custom_field_valueson jobs already in the enhanced job cache (open and closed, capped at 10,000, memoized per client instance) — no native calls at schema-discovery time. A field is only discoverable once it has a non-empty value on at least one synced job. UKGINTstrings are normalized to integers during job enrichment; other values are inferred as boolean, integer, or string from their cached value. Writeback is impossible: requisitions are GET-only in the UKG REST API, so the Tenzo job link field stays disabled (DisabledTenzoJobLinkCustomField). - Applicant Custom Fields — writable; index-based array on the applicant record. Powers application score, candidate score, interview link, and completion rate writebacks.
- Application Custom Fields — UKG job applications have no custom fields; application-level writes route to the underlying applicant (same pattern as Breezy and RecruitCRM).
Native Enrichment
Kombo’s unified payloads for UKG Ready are missing two things Tenzo depends on: job custom fields and candidate phone numbers. Both are backfilled from the native API during sync.Job Custom Fields → Rules Engine (“Tenzo Active”)
Kombo’s unified job payload does not carry UKG custom fields, so during job sync each Kombo job is enhanced with a native requisition fetch (GET /recruitment/job-requisitions/{remote_id}, bounded at 8 concurrent). UKG returns field definitions and values as two separate arrays matched by index:
custom_field_values on UkgReadyJob, which lands in the enhanced job cache — the layer the rules engine filters on. That is the full path that makes “Tenzo Active” job filtering work; without the native fetch, the field never reaches rules. Batch fetches follow Kombo cursors until the requested limit, so large requisition counts are not truncated at one page.
Rules-schema discovery is decoupled from this: it reads custom_field_values straight from the enhanced job cache (see Custom Fields above) and issues no native or Kombo calls of its own. The native requisition fetch happens only during job sync, when the cache is populated.
Candidate Phone Numbers
Kombo returns no phone numbers for UKG Ready candidates, so every candidate read path — streaming, get-by-ID, get-by-remote-ID, and the pre-flight of create-application — backfills phones from the native applicant endpoint (GET /applicants/{remote_id}) whenever Kombo left phone_numbers empty. During candidate streaming this runs in batches of 8. Ordering honors phones.preferred_phone (see Implementation Notes for its dual label/number semantics), so the number the candidate prefers is the one dialed first.
Applicant Custom-Field Writeback
Four post-interview writebacks target applicant custom fields, each driven by its own org-config setting (an applicant field index picked on the ATS integration settings page):
Configure each to a distinct index (see Duplicate Field Indexes below).
The write path is a native read-modify-write:
- GET the applicant (
/applicants/{id}) - Merge updates into
applicant_custom_fieldsby index - PUT the applicant back
- The PUT payload must echo the applicant’s
accountandaddressblocks or UKG rejects the request with validation errors. applicant_custom_fieldsis index-based ([{"index": 0, "value": "..."}]) — the REST API exposes no labels, so the settings UI offers 50 index placeholders (“Applicant Custom Field N”).- Configured field IDs accept several formats:
"3","custom_field_3","applicant_custom_fields.3","applicant_custom_fields[3]". - UKG can return HTTP 200 with a nested error envelope; responses are checked for
errors/user_messageswith severityERRORand raised asAtsApiError.
Implementation Notes
Applicant Fields Are Person-Level
Applicant custom fields belong to the person, not the application. A candidate with two concurrent applications shares one set of score/link/completion fields — last write wins. The per-application source of truth is theapplicant_comment note.
Non-Atomic Writebacks
Both the custom-field writeback and the note append are GET → merge → PUT sequences that re-send the full array/comment. Concurrent writers from different processes can silently overwrite each other. Within one call’s post-processing, writes are sequential, so this only matters cross-process.preferred_phone Dual Semantics
The native applicant phones.preferred_phone field holds either a phone-type label ("CELL", "HOME", …) or the raw preferred number itself. The client handles both: a raw number is emitted first; a label reorders the typed phone fields.
Closed Jobs Disappear
UKG Ready only returns publicly visible jobs. Closed jobs vanish from the API entirely (and filled jobs look open until closed), so missing-job warnings are suppressed for disabled positions (expected stale-cache behavior, not an integration error).No Job Code
Kombo returnsjob_code as null for UKG Ready jobs. Shared naming code falls back to remote_id where a requisition/job code is needed.
Field Type Mapping
UKG custom-field config types map viaUKG_READY_FIELD_MAPPINGS: CHAR → string, INT → integer, BOOL/BOOLEAN → boolean.
Duplicate Field Indexes
If two writeback configs resolve to the same applicant field index (e.g.custom_field_3 and 3), a warning is logged and their writes will overwrite each other. Configure each writeback to a distinct index.
Related Files
- Implementation:
server/ats/ukg_ready/ukg_ready_ats_client.py - Models:
server/ats/ukg_ready/ukg_ready_models.py - Config:
UkgReadyConfiginserver/ats/config/providers.py - Field Mappings:
UKG_READY_FIELD_MAPPINGSinserver/ats/field_type_mapper.py - Base Class:
server/ats/base_kombo_ats_client.py - Tests:
server/tests/unit/ats/ukg_ready/