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

# Bullhorn ATS

> Feature coverage and implementation details for Bullhorn integration

## Implementation Overview

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

## Configuration

| Property               | Value                           |
| ---------------------- | ------------------------------- |
| Provider Enum          | `bullhorn`                      |
| Note Format            | `HTML`                          |
| Disposition Reasons    | Supported                       |
| All Application Stages | Supported                       |
| Passthrough URL        | `/passthrough/bullhorn/default` |

## Feature Summary

All core ATS features are supported through Kombo's unified API with extensive custom implementations for Bullhorn-specific functionality.

<AccordionGroup>
  <Accordion title="Streaming Operations (All Supported)" defaultOpen>
    * Stream Jobs (Inherited)
    * Stream Applications (Inherited)
    * Stream Candidates (Inherited)
  </Accordion>

  <Accordion title="Application Management (All Supported)">
    * Move to Stage (Inherited)
    * Get/Use Rejection Reasons (Inherited)
    * Reject Applications (Single & Bulk) (Inherited)
    * Create Applications (Inherited)
    * Get All Stages (Inherited)
  </Accordion>

  <Accordion title="Job Management (All Supported)">
    * Get Job by ID (Inherited)
    * Get Enhanced Job (Custom Implementation)
    * Batch Job Operations (Supported)
  </Accordion>

  <Accordion title="Candidate Management (All Supported)">
    * Get Candidate by ID (Inherited)
    * Find by Details (Inherited)
    * Get Resume (Inherited)
  </Accordion>

  <Accordion title="Notes & Attachments (All Supported)">
    * Application Notes (Inherited)
    * Candidate Notes (Inherited)
    * Application Attachments (Inherited)
    * Candidate Attachments (Inherited)
  </Accordion>

  <Accordion title="Custom Fields (All Supported)">
    * Application Custom Fields (Inherited)
    * Candidate Custom Fields (Custom Implementation)
    * Job Custom Fields (Custom Implementation)

    ### Updatable Candidate Fields

    Bullhorn supports updating these standard candidate fields through AI extraction:

    * **Desired Salary** (`salary`) - Candidate's salary expectations in USD
    * **Current Pay Rate** (`hourlyRateLow`) - Candidate's current hourly rate
    * **Current Company** (`companyName`) - Name of candidate's current employer
    * **Date Available** (`dateAvailable`) - When candidate can start work
    * **Employment Preference** (`employmentPreference`) - Direct Hire, Contract, or Contract To Hire

    These fields can be automatically extracted from interview transcripts and updated in Bullhorn.
  </Accordion>
</AccordionGroup>

## Bullhorn-Specific Features

### Custom Field Querying

When fetching jobs, we automatically query **64 custom fields** from Bullhorn:

* **Custom Date Fields:** `customDate1` through `customDate3` (3 fields)
* **Custom Float Fields:** `customFloat1` through `customFloat3` (3 fields)
* **Custom Integer Fields:** `customInt1` through `customInt8` (8 fields)
* **Custom Text Fields:** `customText1` through `customText40` (40 fields)
* **Custom Text Block Fields:** `customTextBlock1` through `customTextBlock5` (5 fields)

This ensures we capture all possible custom data configured in any Bullhorn instance.

### Note Action Types

We use custom action types when adding notes to candidates and applications. Bullhorn restricts action names to 30 characters:

* `TZ - Call Summary` - General call summaries
* `TZ - Call Summary - Passed` - Successful screenings
* `TZ - Call Summary - Failed` - Failed screenings
* `TZ - Inbound SMS` - Incoming text messages
* `TZ - Outbound SMS` - Outgoing text messages
* `TZ - Inbound Email` - Incoming emails
* `TZ - Outbound Email` - Outgoing emails
* `Tenzo:Not Interested` - Candidate declined
* `TZ - Note` - Generic notes

### Placements

Custom implementation for Bullhorn placement records:

* Stream placements with pagination
* Track placement dates (dateAdded, dateBegin)
* Parse placement status and candidate/job associations
* Filter by timestamp for incremental syncing

### Tearsheets (Hotlists)

Support for Bullhorn tearsheet functionality:

* Stream tearsheets with pagination
* Handle large tearsheets (500+ candidates/jobs per tearsheet)
* Retrieve candidate and job IDs from tearsheets
* Filter private/deleted tearsheets automatically

### Job Creation

Custom implementation for creating jobs in Bullhorn:

* Create JobOrder entities via passthrough API
* Support for custom field values on job creation
* Returns Bullhorn job ID for immediate use

## Implementation Notes

### Custom Data Models

* **BullhornJob:** Extended job model with custom field values (includes all 64 custom fields)
* **BullhornCandidate:** Candidate model with category, owner, status metadata
* **BullhornPlacement:** Placement records with submission tracking, dates, and status
* **BullhornTearsheet:** Tearsheet linking candidates to jobs with pagination support

### Field Type Mappings

Bullhorn field types are mapped to generic types via `BULLHORN_FIELD_MAPPINGS`:

* `BigDecimal` → `float`
* `Integer` → `integer`
* `String` → `string`
* `Timestamp` → `date`
* `Boolean` → `boolean`

### Candidate Field Updates

We can update both **standard fields** and **custom fields** on candidates:

* **Standard fields:** Updated via `POST /entity/Candidate/{id}` with automatic type conversion
* **Custom fields:** Updated via custom field update endpoints with schema validation

### Job Caching

Job data is cached in-memory to avoid repeated API calls when processing multiple applications for the same job. Cache can be cleared with `clear_caches()` method.

### Pagination Limits

Bullhorn has a maximum record request count of **500 records per request** (`BULLHORN_RECORD_REQUEST_COUNT_MAX`). All streaming operations handle this automatically with proper pagination.

## Tearsheet sync (Service Bus queue)

When `USE_SERVICE_BUS_TEARSHEET_SYNC` is enabled (Bullhorn integrations only):

1. **Publish phase** (ATS sync cron): streams tearsheets modified since `tearsheets_last_synced_at`, resolves sourcing campaigns per job, and publishes one Service Bus message per **(campaign\_id, Kombo candidate\_id)** pair.
2. **Worker** (`tearsheet-sync` topic / `sync_consumer` or `ats_tearsheet_sync_worker`): processes receive batches like applicant/candidate sync — skips pairs that already have CCI, batch-indexes candidates that need it, then `add_to_campaign` with source `TEARSHEETS` (CCI re-check before add to avoid duplicate ATS applications).

**Azure topic requirements:**

* **Sessions required** on the topic and worker subscription (`session_id` = integration id).
* If the topic is **partitioning-enabled with duplicate detection**, each message sets:
  * `message_id` = `{integration_id}:{campaign_id}:{ats_candidate_id}` (stable pair key)
  * `partition_key` = integration id (must match `session_id`)
* Azure duplicate detection suppresses accidental double-publishes of the same pair within the topic window; workers also skip pairs already on the campaign via CCI.
* Workers skip pairs already on the campaign (`get_already_synced_ats_candidate_ids`); `tearsheets_last_synced_at` advances after a **successful publish**, not after workers finish.

JobDiva hotlist sync remains in-memory and is not queued in this phase.

## Related Files

* Implementation: `server/ats/bullhorn_ats_client.py`
* Tearsheet queue: `server/ats/service_bus/tearsheet_sync_service_bus.py`, `bullhorn_tearsheet_sync_stream.py`, `tearsheet_sync_worker_service.py`
* Field Mappings: `server/ats/field_type_mapper.py` (BULLHORN\_FIELD\_MAPPINGS)
* Base Class: `server/ats/base_kombo_ats_client.py`

## See Also

* [ATS Coverage Matrix](/internal/ats-coverage)
