Stitchflow
Bullhorn logo

Bullhorn User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 17, 2026

Summary and recommendation

Bullhorn's REST API authenticates via OAuth 2.0 but adds a second exchange step: the access token must be traded for a session-scoped BhRestToken via GET /rest-services/login, and that token expires after a configurable idle timeout (default ~10 minutes).

The base restUrl is dynamic and returned at login time - it must never be hardcoded.

All user data is modeled under the CorporateUser entity;

key fields for identity graph construction include id, username, email, enabled, userType (to-one relation), department (to-one relation), externalID, dateLastModified, and isDeleted.

Integrating Bullhorn into an identity graph alongside other workforce systems requires resolving UserType and Department relation ids before writes, and treating externalID as the stable cross-system join key.

API quick reference

Has user APIYes
Auth methodOAuth 2.0 with session token (BhRestToken). OAuth 2.0 issues an access token; a separate /login call exchanges it for a BhRestToken used on all subsequent requests.
Base URLOfficial docs
SCIM availableYes
SCIM plan requiredCorporate/Enterprise

Authentication

Auth method: OAuth 2.0 with session token (BhRestToken). OAuth 2.0 issues an access token; a separate /login call exchanges it for a BhRestToken used on all subsequent requests.

Setup steps

  1. Register your application with Bullhorn to obtain a client_id and client_secret via the Bullhorn developer portal.
  2. Direct the user to the Bullhorn authorization endpoint: https://auth.bullhornstaffing.com/oauth/authorize?client_id=...&response_type=code&redirect_uri=...
  3. Exchange the returned authorization code for an access_token via POST to https://auth.bullhornstaffing.com/oauth/token.
  4. Call GET https://rest.bullhornstaffing.com/rest-services/login?version=2.0&access_token={access_token} to obtain a BhRestToken and the corp-specific REST URL.
  5. Include BhRestToken as a query parameter (BhRestToken={token}) or header on all subsequent API calls to the returned restUrl.

Required scopes

Scope Description Required for
api General REST API access scope required for all API operations. All REST API calls including user management

User object / data model

Field Type Description On create On update Notes
id Integer Unique identifier for the CorporateUser. auto-assigned read-only System-generated; cannot be set.
username String Login username for the user. required optional Must be unique within the corporation.
firstName String User's first name. required optional
lastName String User's last name. required optional
email String Primary email address. required optional
enabled Boolean Whether the user account is active. optional optional Set to false to deactivate without deleting.
userType To-one (UserType) Role/type assigned to the user. required optional References a UserType entity by id.
department To-one (Department) Department the user belongs to. optional optional
mobile String Mobile phone number. optional optional
phone String Office phone number. optional optional
timeZoneOffsetEST Integer User's timezone offset from EST. optional optional
dateAdded Timestamp Date the user record was created. auto-assigned read-only
dateLastModified Timestamp Date the user record was last modified. auto-assigned auto-assigned
isDeleted Boolean Soft-delete flag. auto-assigned (false) optional Bullhorn uses soft deletes; records are not physically removed.
externalID String External system identifier for the user. optional optional Useful for mapping to external IdP or HR system IDs.
loginRestrictions Object IP or time-based login restriction settings. optional optional
occupation String Job title or occupation. optional optional
ssoEnabled Boolean Whether SSO is enabled for this user. optional optional Requires SSO configuration at the corp level.

Core endpoints

Search/List Users

  • Method: GET
  • URL: {restUrl}/search/CorporateUser?query=isDeleted:0&fields=id,username,firstName,lastName,email,enabled&count=20&start=0&BhRestToken={token}
  • Watch out for: The search endpoint uses Lucene query syntax. Always include isDeleted:0 to exclude soft-deleted users.

Request example

GET /rest-services/{corpToken}/search/CorporateUser
  ?query=isDeleted:0
  &fields=id,username,firstName,lastName,email,enabled
  &count=20&start=0
  &BhRestToken={token}

Response example

{
  "total": 45,
  "start": 0,
  "count": 20,
  "data": [
    {"id":101,"username":"jdoe","firstName":"Jane","lastName":"Doe","email":"jdoe@corp.com","enabled":true}
  ]
}

Get Single User

  • Method: GET
  • URL: {restUrl}/entity/CorporateUser/{id}?fields=id,username,firstName,lastName,email,enabled,userType,department&BhRestToken={token}
  • Watch out for: You must explicitly specify the fields parameter; omitting it returns only the id field by default.

Request example

GET /rest-services/{corpToken}/entity/CorporateUser/101
  ?fields=id,username,firstName,lastName,email,enabled,userType
  &BhRestToken={token}

Response example

{
  "data": {
    "id": 101,
    "username": "jdoe",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jdoe@corp.com",
    "enabled": true,
    "userType": {"id": 5}
  }
}

Create User

  • Method: PUT
  • URL: {restUrl}/entity/CorporateUser?BhRestToken={token}
  • Watch out for: Bullhorn uses PUT (not POST) for entity creation. The response returns the new entity id in changedEntityId.

Request example

PUT /rest-services/{corpToken}/entity/CorporateUser
  ?BhRestToken={token}
Body: {
  "username": "newuser",
  "firstName": "New",
  "lastName": "User",
  "email": "newuser@corp.com",
  "userType": {"id": 5}
}

Response example

{
  "changedEntityType": "CorporateUser",
  "changedEntityId": 202,
  "changeType": "INSERT"
}

Update User

  • Method: POST
  • URL: {restUrl}/entity/CorporateUser/{id}?BhRestToken={token}
  • Watch out for: Bullhorn uses POST (not PATCH) for updates to existing entities. Only include fields you want to change.

Request example

POST /rest-services/{corpToken}/entity/CorporateUser/202
  ?BhRestToken={token}
Body: {
  "email": "updated@corp.com",
  "enabled": true
}

Response example

{
  "changedEntityType": "CorporateUser",
  "changedEntityId": 202,
  "changeType": "UPDATE"
}

Deactivate User (Soft Delete)

  • Method: POST
  • URL: {restUrl}/entity/CorporateUser/{id}?BhRestToken={token}
  • Watch out for: Bullhorn does not support hard deletion of CorporateUser records via the REST API. Set enabled:false or isDeleted:true to deactivate.

Request example

POST /rest-services/{corpToken}/entity/CorporateUser/202
  ?BhRestToken={token}
Body: {
  "enabled": false
}

Response example

{
  "changedEntityType": "CorporateUser",
  "changedEntityId": 202,
  "changeType": "UPDATE"
}

Query Users (alternative list)

  • Method: GET
  • URL: {restUrl}/query/CorporateUser?where=isDeleted=0&fields=id,username,firstName,lastName,email&count=20&start=0&BhRestToken={token}
  • Watch out for: The /query endpoint uses SQL-like WHERE syntax, while /search uses Lucene. /search is generally preferred for performance.

Request example

GET /rest-services/{corpToken}/query/CorporateUser
  ?where=isDeleted=0
  &fields=id,username,firstName,lastName,email
  &count=20&start=0
  &BhRestToken={token}

Response example

{
  "start": 0,
  "count": 20,
  "data": [
    {"id":101,"username":"jdoe","firstName":"Jane","lastName":"Doe"}
  ]
}

Get Current User (Session Info)

  • Method: GET
  • URL: {restUrl}/settings/userId,corporationId?BhRestToken={token}
  • Watch out for: Returns the userId of the authenticated session's user. Use this to resolve the current user's CorporateUser record.

Request example

GET /rest-services/{corpToken}/settings/userId,corporationId
  ?BhRestToken={token}

Response example

{
  "userId": 101,
  "corporationId": 9999
}

List User Types

  • Method: GET
  • URL: {restUrl}/query/UserType?where=isDeleted=0&fields=id,name&count=100&BhRestToken={token}
  • Watch out for: UserType ids are required when creating or updating a CorporateUser's role. Retrieve them before provisioning.

Request example

GET /rest-services/{corpToken}/query/UserType
  ?where=isDeleted=0
  &fields=id,name
  &count=100
  &BhRestToken={token}

Response example

{
  "start": 0,
  "count": 5,
  "data": [
    {"id":1,"name":"Administrator"},
    {"id":5,"name":"Recruiter"}
  ]
}

Rate limits, pagination, and events

  • Rate limits: Bullhorn does not publicly document specific rate limit thresholds in its official REST API docs. Rate limiting is enforced at the platform level and varies by contract/plan.

  • Rate-limit headers: No

  • Retry-After header: No

  • Rate-limit notes: No publicly documented rate limit values, headers, or Retry-After behavior found in official docs. Contact Bullhorn support for contract-specific limits.

  • Pagination method: offset

  • Default page size: 20

  • Max page size: 500

  • Pagination pointer: start / count

  • Webhooks available: Yes

  • Webhook notes: Bullhorn supports event-based subscriptions via its Event Subscription API (also called the Events API). Consumers poll a subscription endpoint to retrieve queued entity change events rather than receiving push callbacks.

  • Alternative event strategy: Because the Events API is poll-based rather than push-based, consumers must periodically GET /event/subscription/{subscriptionId} to retrieve new events. True outbound HTTP webhooks are not documented in the official REST API docs.

  • Webhook events: INSERTED (entity created), UPDATED (entity updated), DELETED (entity soft-deleted)

SCIM API status

  • SCIM available: Yes

  • SCIM version: 2.0

  • Plan required: Corporate/Enterprise

  • Endpoint: Not documented

  • Supported operations: GET /Users, GET /Users/{id}, POST /Users, PUT /Users/{id}, PATCH /Users/{id}, DELETE /Users/{id}

Limitations:

  • SCIM 2.0 support is gated to Corporate/Enterprise plan tiers; not available on lower-tier plans.
  • Specific SCIM base URL is tenant/environment-specific and not publicly documented; must be obtained from Bullhorn support or account team.
  • No publicly documented list of supported SCIM schema extensions or Group provisioning details in official docs.
  • IdP connector configuration (e.g., Okta, Entra) details are not publicly documented by Bullhorn.

Common scenarios

Three automation scenarios cover the primary provisioning lifecycle.

To provision a new recruiter: authenticate, retrieve available UserType ids via GET /query/UserType, then issue a PUT to /entity/CorporateUser

note that Bullhorn inverts standard REST verb semantics, using PUT for creation and POST for updates.

To deactivate a departed employee: locate the user via /search/CorporateUser using Lucene syntax, then POST {"enabled": false} to the entity endpoint;

hard deletion is not supported and deactivated records persist unless filtered with isDeleted:0.

To sync user changes into a downstream identity graph or HR system: create an event subscription scoped to CorporateUser INSERTED/UPDATED/DELETED events, then poll GET /event/subscription/{subscriptionId} on a schedule

the Events API is poll-based, not push-based, and events may be lost if the subscription lapses beyond the retention window.

Provision a new recruiter user

  1. Authenticate via OAuth 2.0 to obtain an access_token.
  2. Call GET /rest-services/login?access_token={token} to obtain BhRestToken and restUrl.
  3. Call GET {restUrl}/query/UserType?where=isDeleted=0&fields=id,name to retrieve available UserType ids.
  4. Call PUT {restUrl}/entity/CorporateUser with JSON body containing username, firstName, lastName, email, and userType:{id}.
  5. Capture the changedEntityId from the response as the new user's id.
  6. Optionally call POST {restUrl}/entity/CorporateUser/{id} to set additional fields such as department or externalID.

Watch out for: Use PUT for creation (not POST). Missing the userType field will cause a validation error.

Deactivate a departed employee

  1. Authenticate and obtain BhRestToken.
  2. Call GET {restUrl}/search/CorporateUser?query=username:{username}&fields=id,enabled to locate the user's id.
  3. Call POST {restUrl}/entity/CorporateUser/{id} with body {"enabled": false} to deactivate the account.
  4. Optionally set isDeleted:true if the record should be excluded from standard queries.

Watch out for: Hard deletion is not supported. Deactivated users remain in the system and will appear in queries unless isDeleted:0 filter is applied.

Sync user changes via event polling

  1. Create an event subscription via PUT {restUrl}/event/subscription/{subscriptionId}?type=entity&names=CorporateUser&eventTypes=UPDATED,INSERTED,DELETED.
  2. Periodically poll GET {restUrl}/event/subscription/{subscriptionId} to retrieve queued events.
  3. For each event, extract the entityId and call GET {restUrl}/entity/CorporateUser/{entityId}?fields=... to fetch the current state.
  4. Process changes (e.g., sync to downstream IdP or HR system) and acknowledge by advancing the subscription.

Watch out for: The Events API is poll-based, not push-based. Events may be lost if the subscription is not polled within the retention window. Confirm retention period with Bullhorn support.

Why building this yourself is a trap

Several API behaviors diverge from common REST conventions and will cause silent failures if not handled explicitly. The fields parameter is mandatory on all entity and search endpoints; omitting it returns only the id field with no error.

The /search endpoint uses Lucene syntax while /query uses SQL-like WHERE clauses - they are not interchangeable and mixing them produces unexpected results. Rate limit thresholds are not publicly documented and vary by contract; no Retry-After headers are emitted, so retry logic must be implemented conservatively without platform guidance.

SCIM 2.0 is available on Corporate/Enterprise tiers but the endpoint URL is not discoverable - it must be provisioned by Bullhorn support, making it unsuitable for self-service IdP connector setup without account team involvement.

Automate Bullhorn workflows without one-off scripts

Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 17, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

15Five logo

15Five

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

15Five uses a fixed role-based permission model with six predefined roles: Account Admin, HR Admin, Billing Admin, Group Admin, Manager, and Employee. No custom roles can be constructed. User management lives at Settings gear → People → Manage people p

1Password logo

1Password

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

1Password's admin console at my.1password.com covers the full user lifecycle — invitations, group assignments, vault access, suspension, and deletion — without any third-party tooling. Like every app that mixes role-based and resource-level permissions

8x8 logo

8x8

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

8x8 Admin Console supports full lifecycle user management — create, deactivate, and delete — across its X Series unified communications platform. Every app a user can access (8x8 Work desktop, mobile, web, Agent Workspace) is gated by license assignmen