Skip to main content

Implementation Overview

Implementation Type: Kombo-based Integration
Note Format: Plain Text
Status: V1 Production Ready; V3 Compatibility Implemented, Pilot Cutover Pending
Greenhouse integrates through Kombo’s unified API, providing automatic implementation of most core functionality. The same GreenhouseAtsClient supports both Harvest versions. It selects a version-specific passthrough contract from the raw Kombo integration ID:
  • greenhouse:... uses Harvest V1.
  • greenhousev3:... uses Harvest V3.
The client includes custom implementations for enhanced job data, custom-field management, scorecards, offices, and application attachments.

Configuration

Greenhouse-Specific Features

Interview Scorecards

Fetch interview feedback and scorecards for applications. Unique to Greenhouse!

Office Associations

Jobs have office/location relationships tracked automatically

Keyed Custom Fields

Custom fields use dictionary format for efficient access

Custom Field Caching

Schemas cached to reduce API calls

Feature Coverage

Streaming Operations

Stream Jobs

Status: Supported (Inherited)
Method: _stream_jobs(updated_after, statuses)
Streams jobs from Greenhouse via Kombo. Automatically inherited from BaseKomboAtsClient.

Stream Applications

Status: Supported (Inherited)
Method: _stream_applications(updated_after, for_job_ids)
Streams applications from Greenhouse via Kombo. Automatically inherited from BaseKomboAtsClient.

Stream Candidates

Status: Supported (Inherited)
Method: _stream_candidates(updated_after)
Streams candidates from Greenhouse via Kombo. Automatically inherited from BaseKomboAtsClient.

Move Application to Stage

Status: Supported (Inherited)
Method: _move_application_to_stage(application, stage)
Moves applications between stages using Kombo’s unified API.

Get Disposition Reasons

Status: Supported (Inherited)
Method: get_disposition_reasons()
Retrieves rejection reasons from Greenhouse through Kombo.

Reject Application

Status: Supported (Inherited)
Method: reject_application(application_id, reason_id)
Rejects a single application with optional rejection reason.

Bulk Reject Applications

Status: Supported (Inherited)
Method: bulk_reject_applications(application_ids, reason_id)
Bulk rejection of multiple applications.

Create Application

Status: Supported (Inherited)
Method: _create_application_for_candidate(candidate, job)
Creates new application records for candidates.

Get All Application Stages

Status: Supported (Inherited)
Method: _get_all_application_stages()
Fetches all available application stages from Greenhouse.

Get Job by ID

Status: Supported (Inherited)
Method: get_job_by_job_id(job_id)
Retrieves a single job by its ID through Kombo.

Get Enhanced Job

Status: Supported (Custom Implementation)
Method: get_enhanced_job(kombo_id, remote_job_id)
Implementation Notes:
Custom implementation that fetches full job data including Greenhouse-specific custom fields via passthrough API.
Retrieves:
  • Job requisition ID from requisition_id field
  • Keyed custom fields from Greenhouse
  • Office information

Fetch Enhanced Jobs Batch

Status: Supported (Custom Implementation)
Method: _fetch_enhanced_jobs_batch_from_ats(limit, cursor)
Batch fetches enhanced job data with custom fields for improved performance.

Get Candidate by ID

Status: Supported (Inherited)
Method: _get_candidate_by_id(candidate_id)
Retrieves candidate information from Greenhouse via Kombo.

Find Candidates by Details

Status: Supported (Inherited)
Method: _find_candidates_by_details(first_name, last_name, email, phone)
Searches for candidates by name, email, or phone number.

Get Resume

Status: Supported (Inherited)
Method: _get_resume_for_candidate_id(candidate_id)
Retrieves candidate resume data through Kombo.

Add Note to Application

Status: Supported (Inherited)
Method: _add_note_to_application(application, note, note_action_type)
Adds plain text notes to applications in Greenhouse.

Add Note to Candidate

Status: Supported (Inherited)
Method: _add_note_to_candidate(candidate, note, note_action_type)
Adds plain text notes to candidate records.

Add Attachment to Application

Status: V1 Supported; V3 Contract ImplementedMethod: _add_attachment_to_application(filename, application, pdf_b64)Uploads attachments through the version-specific Harvest passthrough contract. V1 uses the application attachment sub-resource, while V3 uses the flat attachments endpoint with an application_id in the request body. V3 request construction is covered by local contract tests; non-empty response parsing and live attachment create/delete still require an approved disposable pilot record.

Add Attachment to Candidate

Status: V1 Supported; V3 UnsupportedMethod: _add_attachment_to_candidate(filename, candidate_id, pdf_b64)V1 can use Kombo’s inherited candidate attachment action. Greenhouse Harvest V3 only supports creating attachments for applications, so V3 callers must attach the file to an application.

Application Custom Fields

Status: V1 Supported; V3 Contract ImplementedMethods:
  • _get_application_custom_fields() - Get field definitions (with caching)
  • _update_application_custom_fields(application, updates) - Update field values
Gets and updates application custom fields through the version-specific Greenhouse passthrough contract. Application updates have a separate V3 request path because Greenhouse does not document the same array-replacement warning that applies to candidate custom fields. V3 unrelated-field preservation must be confirmed with a disposable pilot record before relying on it in a customer cutover.

Candidate Custom Fields

Status: V1 Supported; V3 Contract ImplementedMethods:
  • _get_candidate_custom_fields() - Get field definitions (with caching)
  • _update_candidate_custom_fields(candidate, updates) - Update field values
  • get_custom_fields(AtsEntityType.CANDIDATE) - Public field-definition helper
  • update_custom_fields(candidate_id, updates, CANDIDATE) - Public update helper
For Harvest V3, candidate updates first read the current candidate, merge Tenzo’s changes into the existing custom-field collection, and then PATCH the complete desired collection. This is required because Greenhouse V3 replaces the custom_fields array rather than treating a one-field array as a delta. The merge and request contract are covered by local tests; end-to-end preservation of unrelated fields still requires an approved disposable pilot record.

Job Custom Fields

Status: Partial Support (Values Only)
Available Methods:
  • _get_job_custom_field_values(job_id) - Get field values (Custom Implementation)
  • _get_job_custom_fields() - Harvest job field definitions via passthrough (cached)
  • get_custom_fields(AtsEntityType.JOB) - Same schema as above (used by _get_job_custom_fields)
Missing:
  • _update_job_custom_fields() - Inherits NotImplementedError from BaseKomboAtsClient
Implementation Notes:
Job custom field VALUES can be retrieved via _get_job_custom_field_values() which parses Greenhouse’s keyed custom fields. Field definitions are available through _get_job_custom_fields() for flows such as Annexus rules that need job-level custom field metadata. Writing job custom fields from Tenzo (for example the Tenzo job link) is not implemented yet.

Implementation Notes

Req ID Handling

Greenhouse uses requisition_id as the human-readable job identifier. The client includes custom logic in _get_req_id_for_job() to extract this from raw Greenhouse data when available.

Custom Field Structure

Greenhouse custom-field values are exposed as keyed objects. Harvest V1 job responses use keyed_custom_fields; Harvest V3 job responses use custom_fields, which the V3 contract normalizes to the existing keyed_custom_fields shape. The client then parses values into standardized CustomFieldValue objects. Custom-field definition requests also differ by version:
Harvest V1 custom-field definitions paginate with since_id from the Link rel="next" URL (not page, which jobs/offices still use). Greenhouse may emit a trailing next link whose follow-up page is empty; the client stops when that happens. Harvest V3 definitions and other collections use cursor pagination. Follow-up requests use the cursor returned by Greenhouse.

Version-Specific Passthrough Requests

The client keeps V1 and V3 behavior behind typed passthrough contracts: The integration ID determines the contract once when the client is created. V1 requests retain their existing behavior while V3 requests use the new paths, parameters, payloads, pagination, and response normalization.

V1-to-V3 Cutover

Harvest V3 is a separate Kombo connector rather than an in-place API upgrade. Greenhouse-native remote IDs remain stable, but Kombo job, candidate, and application IDs change between the V1 and V3 connections. Tenzo therefore requires a reviewed mapping artifact before switching an organization:
  1. Keep V1 as the default integration while the V3 connection is prepared with ingestion disabled.
  2. Export and review the V1-to-V3 mappings for jobs, candidates, and applications.
  3. Pause ingestion and wait for active V1/V3 synchronization work to drain.
  4. Run the cutover command in dry-run mode and review its IDs, counts, unsupported references, and artifact content hash.
  5. Apply only with explicit approval for that organization and the reviewed content hash.
  6. Verify the mapped relationships and V3 default selection before enabling V3 ingestion.
Deploying the V3-compatible code does not connect or switch an organization automatically. Any persisted reference that still contains a V1 Kombo ID must either be migrated or block the cutover.

Known Limitations

Note Format
Greenhouse only supports plain text notes. HTML formatting will not be rendered.
Kombo Dependency
This integration relies on Kombo’s Greenhouse connection. Any Kombo service issues will affect this integration’s availability.

Implementation Notes

Scorecard Support (Unique Feature)

Greenhouse has an interview scorecard system where interviewers rate candidates. We provide methods to fetch these:
Use cases:
  • Filter candidates by interviewer feedback
  • Track how many interviews completed
  • Pull scorecard data for analytics

Office Associations

Jobs in Greenhouse can be associated with physical office locations. We track these in the GreenhouseJob model:
Helper method to get office ID:

Custom Field Format

Greenhouse returns custom fields as a keyed dictionary rather than an array:
Our parser converts this to standard CustomFieldValue objects automatically.

Note Format

Greenhouse uses plain text notes, not HTML. Formatting (bold, italics, etc.) will not render.
If you need formatted notes, consider using attachments instead.

Requisition ID Handling

Greenhouse stores requisition IDs separately from job names. We automatically append them to job names for display:
The req ID is pulled from requisition_id field or job_code.

Error Handling Quirk

Important: Greenhouse API errors are nested inside Kombo’s 200 OK response. We parse the nested status code in parse_and_validate_kombo_response(). When debugging, check both the outer Kombo status AND the inner Greenhouse response.
Example of nested error:
  • Implementation: server/ats/greenhouse_ats_client.py
  • Versioned passthrough contracts: server/ats/greenhouse_passthrough_contract.py
  • Data Models: GreenhouseJob, GreenhouseOffice
  • Field Mappings: server/ats/field_type_mapper.py (GREENHOUSE_FIELD_MAPPINGS)
  • Base Class: server/ats/base_kombo_ats_client.py
  • Registration: server/ats/ats_factory.py

See Also