> ## Documentation Index
> Fetch the complete documentation index at: https://www.composer.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Bullhorn

> Bullhorn (ATS): the API posture, publish target and field mapping composerID publishes a decision reference through.

This page records how composerID publishes into Bullhorn: the vendor's API posture, the publish target and field mapping, and the documentation each claim rests on. It is for anyone evaluating the connection or building against it.

Bullhorn is the staffing-industry ATS and CRM: the JobOrder is the demand record, JobSubmission the pipeline and Placement the filled engagement, whether permanent or contract. The REST API is entity-oriented (one create, update, query and search surface per entity type) behind OAuth 2.0 plus a per-session BhRestToken, and change events arrive through a subscription queue the client drains rather than through webhooks. composerID creates the JobOrder for the approved decision with externalID = Intent ID, reads it back by querying that field, and follows Placement changes on the same JobOrder to close the loop.

<Info>
  Category: ATS. Coverage status: Specification mapped. Vendor documentation: public developer portal.
</Info>

## Integration path

How composerID connects to this destination, at a glance.

| Step                 | Detail                                                                                                                                                                                                                                               |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authentication       | OAuth 2.0 authorization-code flow against auth.bullhornstaffing.com (client\_id and client\_secret are issued by Bullhorn to a tenant holding the REST API licence or to a Marketplace partner, and the grant is made as a named Bullhorn API user). |
| composerID publishes | JobOrder: PUT /entity/JobOrder                                                                                                                                                                                                                       |
| Intent ID lands on   | `job_order` `externalID`, written into the destination record                                                                                                                                                                                        |
| Back-sync            | Webhooks and polling: some lifecycle events are pushed, the rest are read on a schedule.                                                                                                                                                             |

## Vendor documentation

<Columns cols={2}>
  <Card title="Bullhorn REST API documentation" icon="external-link" href="https://bullhorn.github.io/rest-api-docs/" horizontal />

  <Card title="Getting started with the Bullhorn REST API (OAuth and login flow)" icon="external-link" href="https://bullhorn.github.io/Getting-Started-with-REST/" horizontal />

  <Card title="Entity reference (JobOrder, Placement, JobSubmission, Candidate)" icon="external-link" href="https://bullhorn.github.io/rest-api-docs/entityref.html" horizontal />

  <Card title="Bullhorn developer portal (bullhorn.github.io)" icon="external-link" href="https://bullhorn.github.io/" horizontal />
</Columns>

## API posture

| Aspect         | Detail                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authentication | OAuth 2.0 authorization-code flow against auth.bullhornstaffing.com (client\_id and client\_secret are issued by Bullhorn to a tenant holding the REST API licence or to a Marketplace partner, and the grant is made as a named Bullhorn API user). The access token is short-lived (about ten minutes) with a long-lived refresh token; a call to /rest-services/login with that access token returns a BhRestToken plus the tenant's restUrl. The BhRestToken is sent on every call and lapses after inactivity: keep one session warm and re-login on 401. |
| Style          | REST, JSON. Entity-oriented: PUT /entity/\{EntityType} creates, POST /entity/\{EntityType}/\{id} updates, GET /entity/\{EntityType}/\{id}?fields= reads; /query/\{EntityType} takes a where clause, /search/\{EntityType} takes Lucene syntax; /meta/\{EntityType} describes each tenant's fields, labels and picklists; /event/subscription is a pull-based change queue.                                                                                                                                                                                     |
| Base URL       | [https://rest\\\{dc\\}.bullhornstaffing.com/rest-services/\\\{corpToken\\}/](https://rest\\\{dc\\}.bullhornstaffing.com/rest-services/\\\{corpToken\\}/)  (the data centre and corpToken are returned per tenant by /rest-services/loginInfo and the login call; auth at [https://auth.bullhornstaffing.com/oauth](https://auth.bullhornstaffing.com/oauth))                                                                                                                                                                                                   |
| Webhooks       | No outbound webhooks. Event subscriptions instead: PUT /event/subscription/\{subscriptionId}?type=entity\&names=JobOrder,Placement,JobSubmission\&eventTypes=INSERTED,UPDATED,DELETED registers a durable queue, GET /event/subscription/\{subscriptionId}?maxEvents= drains it, and a re-request by the previous requestId recovers a missed batch. Events carry the entity id, event type and changed property names, not payloads, so each is followed by an entity read.                                                                                   |
| Events posture | Webhooks and polling                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| Rate limits    | Not published as a fixed figure in the REST reference; Bullhorn throttles per API client and answers HTTP 429 when the ceiling is hit (indicative until confirmed at onboarding). The BhRestToken inactivity timeout applies separately from the OAuth access token lifetime.                                                                                                                                                                                                                                                                                  |
| Objects        | JobOrder (title, employmentType, status, numOpenings, startDate, clientCorporation, clientContact, externalID, payRate, clientBillRate, salary, customText1..40 plus customInt and customDate families), JobSubmission, Placement (candidate, jobOrder, status, dateBegin, dateEnd, payRate, clientBillRate), Candidate, ClientCorporation, ClientContact, Opportunity, Note, Task, event subscriptions                                                                                                                                                        |

## Publish target and field mapping

Each object below pairs the canonical intent fields with the destination's own fields and operations. The mapping is indicative until it is confirmed against a tenant at onboarding.

<AccordionGroup>
  <Accordion title="JobOrder" icon="arrow-right-left" description="Open the approved demand">
    | Mapping                  | Detail                                                                                                                                                                                                                                             |
    | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Canonical to destination | role\_title -> title, worker\_type -> employmentType (Permanent, Contract, Contract To Hire per tenant picklist), start\_date -> startDate, headcount -> numOpenings, clientCorporation\{id}, clientContact\{id}, description, salary or payRate   |
    | Target                   | PUT /entity/JobOrder                                                                                                                                                                                                                               |
    | Requirement              | Required                                                                                                                                                                                                                                           |
    | Notes                    | clientCorporation and clientContact are tenant records resolved before publish; the full required set and picklist values come from GET /meta/JobOrder per tenant. The response carries changedEntityId, which composerID keeps for the deep link. |
  </Accordion>

  <Accordion title="Intent ID correlation" icon="arrow-right-left" description="Carry the Intent ID">
    | Mapping                  | Detail                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Canonical to destination | intent\_id -> externalID (String) on the JobOrder                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | Target                   | JobOrder.externalID, written on PUT /entity/JobOrder and read back with GET /query/JobOrder?where=externalID='\{intent\_id}'\&fields=id,title,status                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | Requirement              | Required                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | Notes                    | externalID is a stock client-supplied reference on JobOrder, but the entity reference lists it as a short string (about 30 characters as we recall it), so a 64-character Intent ID may not fit: confirm the length and its use in a /query where clause at onboarding. If it is too short, or already used by another integration, a customText\{n} field (String, 100 characters) with a renamed label is the documented fallback and is likewise queryable. Classic UI route \{tenant\_host}/BullhornSTAFFING/OpenWindow\.cfm?Entity=JobOrder\&id=\{id} (illustrative; the Novo UI has its own routes). |
  </Accordion>

  <Accordion title="Placement" icon="arrow-right-left" description="Read the fill">
    | Mapping                  | Detail                                                                                                                                                        |
    | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Canonical to destination | status, candidate\{id}, dateBegin, dateEnd, payRate, clientBillRate, jobOrder\{id}                                                                            |
    | Target                   | GET /query/Placement?where=jobOrder.id=\{id} or GET /entity/Placement/\{id}?fields=                                                                           |
    | Requirement              | Read scope only                                                                                                                                               |
    | Notes                    | A Placement against the JobOrder is the executed engagement for both permanent and contract channels; tied back to the Intent ID through jobOrder.externalID. |
  </Accordion>

  <Accordion title="Change feed" icon="arrow-right-left" description="Follow status without polling every record">
    | Mapping                  | Detail                                                                                                                                                      |
    | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Canonical to destination | JobOrder, JobSubmission and Placement INSERTED / UPDATED events by entity id                                                                                |
    | Target                   | PUT then GET /event/subscription/\{subscriptionId}                                                                                                          |
    | Requirement              | Recommended                                                                                                                                                 |
    | Notes                    | Pull model: the subscription is drained on a schedule and each event is followed by an entity read of the named fields. No signed webhook exists to verify. |
  </Accordion>
</AccordionGroup>

<Note>
  API credentials are not self-service: the client\_id and client\_secret are issued by Bullhorn Support for a tenant holding the REST API licence (or through the Marketplace partner programme), and calls run as a named Bullhorn API user whose entitlements bound what the integration can see and write. The restUrl and corpToken differ per data centre and are discovered at login, so nothing is hardcoded. Field labels, required fields, picklists (employmentType, status) and the meaning of customText fields are tenant-configured: read GET /meta/JobOrder during preflight. Confirm at onboarding that externalID is free for composerID's use, long enough for the tenant's Intent IDs and usable in a /query where clause; otherwise reserve a customText field and record which one.
</Note>

## Next steps

<Columns cols={3}>
  <Card title="How composerID connects" icon="plug-zap" href="/guides/connect">
    The five ways composerID reaches a destination, and the minimum a destination must offer.
  </Card>

  <Card title="Publishing" icon="send" href="/concepts/publishing">
    Plan, preflight and publish: how a decision becomes a valid record in a destination.
  </Card>

  <Card title="All destinations" icon="plug" href="/destinations">
    Every destination composerID documents, with its category and coverage status.
  </Card>
</Columns>
