> ## Documentation Index
> Fetch the complete documentation index at: https://f4c7a9e2d8b1-docs.tenzo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Greenhouse ATS

> Feature coverage and implementation details for Greenhouse integration

## Implementation Overview

**Implementation Type:** Kombo-based Integration\
**Note Format:** Plain Text\
**Status:** Production Ready

Greenhouse integrates through Kombo's unified API, providing automatic implementation of most core functionality. This client includes custom implementations for enhanced job data and custom field management.

## Configuration

| Property               | Value                             | Description                            |
| ---------------------- | --------------------------------- | -------------------------------------- |
| Provider Enum          | `greenhouse`                      | Identifier in ProviderEnum             |
| Note Format            | `PLAIN`                           | Text-only notes (no HTML)              |
| Disposition Reasons    | Supported                         | Can retrieve and use rejection reasons |
| All Application Stages | Supported                         | Can fetch all available stages         |
| Passthrough URL        | `/passthrough/greenhouse/harvest` | Kombo passthrough endpoint             |

## Greenhouse-Specific Features

<CardGroup cols={2}>
  <Card title="Interview Scorecards" icon="clipboard-check">
    Fetch interview feedback and scorecards for applications. Unique to Greenhouse!
  </Card>

  <Card title="Office Associations" icon="building">
    Jobs have office/location relationships tracked automatically
  </Card>

  <Card title="Keyed Custom Fields" icon="key">
    Custom fields use dictionary format for efficient access
  </Card>

  <Card title="Custom Field Caching" icon="database">
    Schemas cached to reduce API calls
  </Card>
</CardGroup>

## Feature Coverage

<AccordionGroup>
  <Accordion title="Streaming Operations" defaultOpen>
    ### 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.
  </Accordion>

  <Accordion title="Application Management">
    ### 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.
  </Accordion>

  <Accordion title="Job Management">
    ### 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.
  </Accordion>

  <Accordion title="Candidate Management">
    ### 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.
  </Accordion>

  <Accordion title="Notes & Attachments">
    ### 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:** Supported (Inherited)\
    **Method:** `_add_attachment_to_application(filename, application_id, pdf_b64)`

    Uploads attachments (PDFs, resumes, etc.) to applications.

    ### Add Attachment to Candidate

    **Status:** Supported (Inherited)\
    **Method:** `_add_attachment_to_candidate(filename, candidate_id, pdf_b64)`

    Uploads attachments to candidate records.
  </Accordion>

  <Accordion title="Custom Fields">
    ### Application Custom Fields

    **Status:** Fully Supported (Custom Implementation)\
    **Methods:**

    * `_get_application_custom_fields()` - Get field definitions (with caching)
    * `_update_application_custom_fields(application, updates)` - Update field values

    Complete support for getting and updating application custom fields through Kombo's unified API.

    ### Candidate Custom Fields

    **Status:** Partial Support (Not Wired to Base Methods)\
    **Available Methods:**

    * `get_custom_fields(AtsEntityType.CANDIDATE)` - Get field definitions via passthrough
    * `update_custom_fields(candidate_id, updates, CANDIDATE)` - Update via passthrough

    **Missing:**

    * `_get_candidate_custom_fields()` - Inherits `NotImplementedError` from BaseKomboAtsClient
    * `_update_candidate_custom_fields()` - Inherits `NotImplementedError` from BaseKomboAtsClient

    **Impact:** Candidate custom fields CAN be accessed via the public `get_custom_fields()` and `update_custom_fields()` methods using Greenhouse passthrough API. However, the abstract base class methods are not overridden, so any code calling the underscored methods directly will encounter `NotImplementedError`.

    **Workaround:** Use the public passthrough methods instead of relying on abstract base methods.

    ### 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 Salv (for example the Tenzo job link) is not implemented yet.
  </Accordion>
</AccordionGroup>

## 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 organizes custom fields as "keyed custom fields" which are accessed via the passthrough API. The client parses these into standardized `CustomFieldValue` objects.

## Known Limitations

<Warning>
  **Note Format**\
  Greenhouse only supports plain text notes. HTML formatting will not be rendered.
</Warning>

<Note>
  **Kombo Dependency**\
  This integration relies on Kombo's Greenhouse connection. Any Kombo service issues will affect this integration's availability.
</Note>

## Implementation Notes

### Scorecard Support (Unique Feature)

Greenhouse has an interview scorecard system where interviewers rate candidates. We provide methods to fetch these:

```python theme={null}
scorecards = await client.get_submitted_application_scorecards(application_id)
count = await client.get_submitted_application_scorecard_count(application_id)
```

**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:

```python theme={null}
class GreenhouseJob(AtsJob):
    offices: list[GreenhouseOffice]  # Office locations for this job
```

Helper method to get office ID:

```python theme={null}
office_id = await client.get_job_office_id(remote_job_id)
```

### Custom Field Format

Greenhouse returns custom fields as a **keyed dictionary** rather than an array:

```json theme={null}
{
  "keyed_custom_fields": {
    "field_123": {"name": "Department", "value": "Engineering"},
    "field_456": {"name": "Level", "value": "Senior"}
  }
}
```

Our parser converts this to standard `CustomFieldValue` objects automatically.

### Note Format

<Warning>
  Greenhouse uses **plain text** notes, not HTML. Formatting (bold, italics, etc.) will not render.
</Warning>

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:

```
"Senior Engineer" → "Senior Engineer (REQ-2024-001)"
```

The req ID is pulled from `requisition_id` field or `job_code`.

### Error Handling Quirk

<Info>
  **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.
</Info>

Example of nested error:

```json theme={null}
{
  "status": 200,  // Kombo says OK
  "data": {
    "status": 403,  // But Greenhouse rejected it!
    "message": "Insufficient permissions"
  }
}
```

## Related Files

* Implementation: `server/ats/greenhouse_ats_client.py` (\~480 lines)
* 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

* [Greenhouse ATS setup](/ats-integrations/greenhouse-setup)
* [ATS Coverage Matrix](/internal/ats-coverage)
* [Greenhouse Public Documentation](https://developers.greenhouse.io/)
