> ## 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.

# Jobvite ATS

> Feature coverage and implementation details for Jobvite integration

## Implementation Overview

**Implementation Type:** Kombo-based Integration\
**Note Format:** Plain Text (Shortened due to character limits)\
**Status:** Production Ready

Jobvite integrates through Kombo's unified API, providing automatic implementation of most core functionality. This client includes custom implementations for enhanced job data, custom field management, requisition ID handling, and optimized note formatting to work within Jobvite's character limits.

## Configuration

| Property               | Value                      | Description                            |
| ---------------------- | -------------------------- | -------------------------------------- |
| Provider Enum          | `jobvite`                  | 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/jobvite/api` | Kombo passthrough endpoint             |

## Jobvite-Specific Features

<CardGroup cols={2}>
  <Card title="Internal Job Titles" icon="tag">
    Fetch internal job titles from custom fields separate from public job postings
  </Card>

  <Card title="Requisition ID Tracking" icon="hashtag">
    Automatic requisition ID extraction and appending to job names
  </Card>

  <Card title="Multi-Status Job Fetching" icon="list-check">
    Retrieve jobs across all statuses (Open, Closed, Filled, On Hold, etc.)
  </Card>

  <Card title="PageType Custom Fields" icon="filter">
    Custom fields organized by pageType (Apply, Candidate, Requisition)
  </Card>
</CardGroup>

## Feature Coverage

<AccordionGroup>
  <Accordion title="Streaming Operations" defaultOpen>
    ### Stream Jobs

    **Status:** Supported (Custom Implementation)\
    **Method:** `_stream_jobs(updated_after, statuses)`

    Streams jobs from Jobvite via Kombo with custom requisition ID handling.

    **Custom Enhancement:** `_get_req_id_for_streaming(job)` fetches requisitionId from Jobvite passthrough API and appends it to job names.

    ### Stream Applications

    **Status:** Supported (Inherited)\
    **Method:** `_stream_applications(updated_after, for_job_ids)`

    Streams applications from Jobvite via Kombo. Automatically inherited from BaseKomboAtsClient.

    ### Stream Candidates

    **Status:** Supported (Inherited)\
    **Method:** `_stream_candidates(updated_after)`

    Streams candidates from Jobvite 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 Jobvite 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 Jobvite.
  </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 Jobvite-specific fields and custom fields via passthrough API.

    Retrieves:

    * Job requisition ID from `requisitionId` field
    * All job statuses (Open, Closed, Filled, On Hold, Awaiting Approval, Approved, Rejected, Retracted, Draft)
    * Custom fields via `customField` array
    * Recruiter assignments
    * Salary information
    * Posting type (Internal/External)

    **Helper Method:** `get_jobvite_job(remote_job_id)` - Fetches raw Jobvite job data with all statuses

    ### Batch Jobs

    **Status:** Supported (Custom Implementation)\
    **Method:** `_get_specific_ats_jobs_batch(limit, include_closed, cursor)`

    **Implementation Notes:**\
    Custom batch fetching that queries Jobvite's passthrough API directly. Handles pagination and job status filtering.

    * Default behavior: Only fetches "Open" jobs
    * With `include_closed=True`: Fetches all job states
    * Adds `id` field mapping (`eId` → `id`) for compatibility with base class

    ### Get Internal Job Name

    **Status:** Supported (Custom Implementation)\
    **Method:** `get_job_internal_name(remote_job_id)`

    **Unique to Jobvite:**\
    Retrieves the value of the "internal\_title" custom field, allowing jobs to have separate internal names vs. public posting titles.

    Returns `None` if no internal\_title custom field is found.
  </Accordion>

  <Accordion title="Candidate Management">
    ### Get Candidate by ID

    **Status:** Supported (Inherited)\
    **Method:** `_get_candidate_by_id(candidate_id)`

    Retrieves candidate information from Jobvite 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 (Custom Implementation)\
    **Method:** `_add_note_to_application(application, note, note_action_type)`

    **Custom Implementation:**\
    Uses Jobvite's `comments` field via passthrough API. Updates applications using PUT request with `candidate.application.comments` structure.

    **Important:** Uses shortened note format (see Note Format Limitation below).

    ### Create Call Summary Note

    **Status:** Supported (Custom Implementation)\
    **Method:** `_create_plain_text_summary_from_call(call)`

    **Custom Implementation:**\
    Generates plain text summary notes **without transcripts** to stay within Jobvite's character limit. Set to `include_transcript=False`.

    ### 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 via `/customfield?objectType=Candidate`
    * `_update_application_custom_fields(application, updates)` - Update via PUT `/candidate`

    **Implementation Notes:**\
    Fetches custom fields from Jobvite passthrough API and processes them using `process_jobvite_custom_fields()` which:

    * Deduplicates fields by `fieldCode`
    * Prioritizes `pageType=Apply` fields over other pageTypes
    * Extracts `fieldCode` as `remote_id` and `displayName` as `remote_name`

    Updates use Jobvite's nested payload structure:

    ```json theme={null}
    {
      "candidate": {
        "application": {
          "eId": "application_id",
          "customField": [
            {"fieldCode": "field_id", "value": "new_value"}
          ]
        }
      }
    }
    ```

    ### Candidate Custom Fields

    **Status:** Read-Only Support (Routes to Application Endpoint)\
    **Available Methods:**

    * `get_custom_fields(AtsEntityType.CANDIDATE)` - Routes to `_get_application_custom_fields()`

    **Missing (Jobvite API Limitation):**

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

    **Implementation Notes:**\
    The `get_custom_fields()` method routes both APPLICATION and CANDIDATE entity types to the same endpoint (`/customfield?objectType=Candidate`), as Jobvite treats these similarly. Custom field names include pageType suffix for clarity (e.g., "Phone Number (Application)" vs "Phone Number (Candidate Profile)").

    ### Job Custom Fields

    **Status:** Read-Only Support (Custom Implementation)\
    **Available Methods:**

    * `_get_job_custom_fields()` - Get field definitions via `/customfield?objectType=Job`
    * `_get_job_custom_field_values(job_id)` - Get field values from job data

    **Missing (Jobvite API Limitation):**

    * `_update_job_custom_fields()` - Inherits `NotImplementedError` from BaseKomboAtsClient

    **Implementation Notes:**\
    Job custom fields use `build_internal_representation_job_custom_fields()` which:

    * Filters for `pageType=Requisition` fields only
    * Deduplicates by `fieldCode`
    * Maps field types using `FieldTypeMapper`
  </Accordion>
</AccordionGroup>

## Jobvite Data Model

### JobviteJob Structure

The `JobviteJob` dataclass extends `AtsJob` with 17 Jobvite-specific fields:

```python theme={null}
class JobviteJob(AtsJob):
    provider: Literal["jobvite"] = "jobvite"
    
    # Jobvite Identifiers
    eId: str                      # Jobvite entity ID
    requisitionId: str            # Human-readable req ID (appended to job name)
    companyId: str                # Company identifier
    
    # Job Classification
    category: str                 # Job category
    jobType: str                  # Full-time, Contract, etc.
    jobState: str                 # Open, Closed, Filled, On Hold, etc.
    openings: str                 # Number of openings
    
    # Salary Information
    salaryCurrency: str
    salaryFrequency: str
    salaryMin: str
    salaryMax: str
    
    # Links and Posting
    applyLink: str                # External apply URL
    detailLink: str               # Job details URL
    postingType: str              # "Internal" or "External"
    internalOnly: bool            # Posting visibility flag
    is_external: bool             # Computed: postingType == "external" or not internalOnly
    
    # Related Data
    recruiters: list[JobviteJobRecruiter]       # Assigned recruiters
    custom_field_values: list[CustomFieldValue]  # Custom field data
```

### JobviteJobRecruiter Structure

```python theme={null}
class JobviteJobRecruiter:
    email: str
    firstName: str
    lastName: str
    userId: str
    userName: str
    employeeId: str
```

## Implementation Notes

### Requisition ID Handling

Jobvite stores requisition IDs in the `requisitionId` field. Our implementation automatically appends this to job names for display:

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

The req ID is extracted via:

* **Enhanced jobs:** `_get_req_id_for_job(job, raw_data)` from passthrough data
* **Streaming jobs:** `_get_req_id_for_streaming(job)` via additional API call

### Custom Field PageType Architecture

Jobvite organizes custom fields by `pageType`:

| PageType      | Description              | Used For                                |
| ------------- | ------------------------ | --------------------------------------- |
| `Apply`       | Application form fields  | Application custom fields (prioritized) |
| `Candidate`   | Candidate profile fields | Candidate custom fields                 |
| `Requisition` | Job/requisition fields   | Job custom fields                       |

Our `process_jobvite_custom_fields()` method:

1. Deduplicates fields by `fieldCode`
2. Prioritizes `Apply` pageType when duplicates exist
3. Adds pageType suffix to field names for clarity

### Multi-Status Job Fetching

<Info>
  **Important:** Jobvite API defaults to returning only "Open" jobs. Our implementation explicitly passes all job statuses when fetching job data:
</Info>

Supported statuses:

* Open
* Closed
* Filled
* On Hold
* Awaiting Approval
* Approved
* Rejected
* Retracted
* Draft

The `get_jobvite_job()` method constructs status parameters like:

```
?ids={job_id}&jobStatus=Open&jobStatus=Closed&jobStatus=Filled...
```

### Internal Job Titles

Jobvite supports a special `internal_title` custom field that allows jobs to have different internal vs. external names. Use `get_job_internal_name()` to retrieve this value.

**Use cases:**

* Hide sensitive project codenames from external candidates
* Use simplified internal references
* Maintain different naming conventions for internal tracking

## Known Limitations

<Warning>
  **Note Character Limit**\
  Jobvite has a strict character limit on application notes. To work within this constraint, our `_create_plain_text_summary_from_call()` method generates **shortened summaries WITHOUT transcripts** (`include_transcript=False`). Full interview transcripts are available as attachments if needed.
</Warning>

<Note>
  **Custom Field Update Limitations (Jobvite API)**

  * **Candidate custom fields:** Read-only. Updates not supported by Jobvite API through Kombo.
  * **Job custom fields:** Read-only. Updates not supported by Jobvite API through Kombo.
  * **Application custom fields:** Fully supported (read and write).
</Note>

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

### Error Handling

Jobvite 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 Jobvite response.

Example of nested error:

```json theme={null}
{
  "status": 200,  // Kombo says OK
  "data": {
    "status": 400,  // But Jobvite rejected it!
    "data": {
      "message": "Invalid field value"
    }
  }
}
```

## Related Files

* Implementation: `server/ats/jobvite_ats_client.py` (\~625 lines)
* Data Models: `JobviteJob`, `JobviteJobRecruiter`, `JobviteJobLocation`
* Field Mappings: `server/ats/field_type_mapper.py` (JOBVITE\_FIELD\_MAPPINGS)
* Base Class: `server/ats/base_kombo_ats_client.py`
* Registration: `server/ats/ats_factory.py`

## See Also

* [ATS Coverage Matrix](/internal/ats-coverage)
* [Jobvite Developer Documentation](https://developers.jobvite.com/)
