Skip to content

Bullhorn to Crelate Migration: The CTO's Technical Guide

Technical guide to migrating from Bullhorn to Crelate: API rate limits, data model mapping, custom field constraints, and step-by-step migration process for CTOs.

Raaj Raaj · · 22 min read
Bullhorn to Crelate Migration: The CTO's Technical Guide
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,200+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Migrating from Bullhorn to Crelate is a data-model compression problem disguised as a vendor switch. Bullhorn is built around a deeply relational staffing data model — Candidate, ClientContact, ClientCorporation, JobOrder, JobSubmission, Placement — with up to 35 custom object instances per entity and virtually unlimited custom field mappings. Crelate uses a streamlined ATS+CRM schema where Contacts, Companies, Jobs, and Opportunities share a unified data model, but custom fields are capped at 20 per entity. (bullhorn.github.io)

The core engineering challenge: Bullhorn enforces the full staffing lifecycle — Candidates are submitted to JobOrders via JobSubmissions, which become Placements when filled. Crelate collapses this into a pipeline-stage model where Contacts flow through workflow stages on Jobs. Moving data between these two systems means compressing Bullhorn's deeply nested, multi-table relational structure into Crelate's flatter schema — while preserving placement history, candidate-to-job relationships, and activity timelines that your recruiters depend on daily.

The rate-limit mismatch makes this harder. Bullhorn allows extraction at 1,500 requests per minute, but Crelate's API v3 throttles ingestion at 120 requests per minute per IP. Without careful queue management, your migration script will spend more time waiting than working. (kb.bullhorn.com)

This guide covers the exact API constraints on both sides, concrete object-mapping decisions, every realistic migration approach, and the edge cases that cause silent data loss.

For broader ATS migration pitfalls, see 5 "Gotchas" in ATS Migration. For patterns on when CSVs work versus when they break, read Using CSVs for SaaS Data Migrations.

Warning

Crelate deprecated API v1 and v2 endpoints as of November 1, 2025, throttling them to 30 requests per minute. All new migration scripts must target API v3. If you're reusing old integration code, rewrite it before starting. (postman.com)

Why Companies Migrate from Bullhorn to Crelate

The migration drivers are predictable:

  • Cost: Crelate starts at $99/user/month. Bullhorn's pricing scales significantly higher, especially with add-ons for automation, analytics, and back-office integrations. For a 15-person agency, the annual savings can reach $30K+.
  • UI and recruiter adoption: Crelate delivers a modern, intuitive interface compared to Bullhorn's steeper learning curve. A common framing from agency operators: "Crelate is Bullhorn for people who hate Bullhorn's interface."
  • Implementation speed: Crelate advertises a 14-day implementation timeline. Bullhorn implementations, especially for multi-division agencies, frequently take 6–12 weeks.
  • Agency fit: Crelate's integrated ATS+CRM works well for small-to-midsize staffing firms (3–50 recruiters) that don't need Bullhorn's VMS integrations, back-office suite, or enterprise compliance features.
Info

Bullhorn is purpose-built for high-volume, transactional staffing with deep back-office integrations (payroll, VMS, credentialing). If your agency depends on Fieldglass/Beeline VMS integrations or Bullhorn Back Office, Crelate may not be a like-for-like replacement. Audit your integration dependencies before committing.

Data Model Differences: Bullhorn vs Crelate

This is where most migration plans break. The two platforms don't just use different field names — they use fundamentally different data architectures.

Bullhorn's Entity Model

Bullhorn organizes staffing data across deeply interconnected entities:

  • Candidate — the person seeking work
  • ClientContact — the hiring manager or client-side contact
  • ClientCorporation — the client company
  • JobOrder — the open requisition
  • JobSubmission — a candidate formally submitted to a JobOrder
  • Placement — a filled position (one Candidate, one JobOrder)
  • Opportunity / Lead — pre-job business development records
  • Note / Appointment / Task — activity records linked to any entity
  • Custom Objects — up to 35 instances per Person entity and 10 per JobOrder, Placement, or Opportunity entity

Bullhorn supports extensive custom field mappings per entity (customText1 through customTextN, customDate fields, etc.), plus full custom objects that function as child tables with their own fields. (bullhorn.github.io)

Crelate's Entity Model

Crelate uses a flatter, more unified structure:

  • Contacts — all people (candidates, hiring managers, references)
  • Companies — client organizations
  • Jobs — open requisitions with pipeline workflow stages
  • Opportunities — sales/business development pipeline items
  • Activities — calls, emails, meetings, tasks
  • Notes — text records attached to any entity
  • Placements — filled positions

The critical constraint: Crelate limits custom fields to 20 per entity (Jobs, Contacts, Companies). If your Bullhorn instance uses 40+ custom fields on Candidates — common in healthcare, government, or specialized staffing — you need to consolidate, archive, or serialize overflow data into note fields. This is the single most impactful architectural limitation for Bullhorn-to-Crelate migrations. (postman.com)

When mapping Bullhorn's custom fields to Crelate, you have three options for handling overflow:

  1. Map the top 20 to Crelate's native custom fields, prioritizing by usage and business value.
  2. Serialize the remainder into a structured JSON string or formatted text block appended as a pinned Note on the Crelate Contact record.
  3. Convert boolean and picklist fields into Crelate Tags, which do not count against the 20-field limit.

For more strategies on handling custom field limitations during a platform switch, see 5 "Gotchas" in ATS Migration.

Object Mapping Reference

Mapping Bullhorn to Crelate requires translating distinct entities into Crelate's unified structure.

Bullhorn Entity Crelate Equivalent Mapping Notes
Candidate Contact (with candidate tag/status) Crelate uses a unified Contact record; candidates are differentiated by workflow status and tags
ClientContact Contact (with client tag) Same entity type — use tags or custom fields to distinguish from candidates
ClientCorporation Company Direct mapping; parent/child company hierarchies require manual reconstruction
JobOrder Job Stages map to Crelate workflow stages; Crelate supports up to 30 stages (Enterprise)
JobSubmission Pipeline stage on a Job No direct equivalent — submissions become Contacts placed at a stage on a Job's pipeline
Placement Placement Crelate has native Placement records with bill/pay rate support
Opportunity Opportunity (Sales Pipeline) Choose the correct Opportunity Type at creation — Crelate does not allow changing it after creation (help.crelate.com)
Lead Opportunity or Contact Depends on pipeline stage; early-stage leads → BD Pipeline, qualified leads → Sales Pipeline
Note Note Direct mapping; attach to appropriate parent entity by ID
Appointment Activity Map to Crelate's activity types (Meeting, Call, etc.)
Task Task Direct mapping; Crelate tasks attach to Contacts, Jobs, or Companies
Custom Objects No direct equivalent Must be flattened into custom fields (max 20), tags, or serialized into notes
FileAttachment Document / Artifact Migrate in a separate binary pass via API

Two mapping decisions matter more than the rest:

JobSubmission is the hidden join record. It explains why a candidate was attached to a job, what stage they reached, and how the submittal history unfolded. In Crelate, this becomes a second-pass load after Contact and Job already exist. Every CSV migration plan loses this data. (bullhorn.github.io)

Opportunity Type is schema, not data. Crelate does not let you change the Opportunity Type on an existing record. A wrong type decision during initial load forces record recreation or manual cleanup. Decide this before your first test run, not after. (help.crelate.com)

API Rate Limits: The 1,500/min → 120/min Bottleneck

This rate-limit mismatch is the single biggest engineering constraint in a Bullhorn-to-Crelate migration.

Bullhorn API Limits

Bullhorn's official rate limits apply to all ATS editions except ATS Growth (formerly Team Edition), which does not include API access: (kb.bullhorn.com)

  • 1,500 requests per minute
  • 100,000 total API calls per month (unless negotiated)
  • 50 concurrent active sessions
  • 50 active API subscriptions

API usage from validated Bullhorn Marketplace partners does not count against your limits. Custom extraction pipelines do count. Bullhorn returns 429 Too Many Requests when you exceed per-minute limits; rate-limited calls don't count against your monthly total.

Warning

Bullhorn API access is not included in the ATS Growth (formerly Team Edition) tier. If you are on this plan, you must rely on manual CSV exports or upgrade to Corporate/Enterprise editions to use the REST API for migration. (kb.bullhorn.com)

Crelate API v3 Limits

Crelate's constraints are tighter: (postman.com)

  • 120 requests per minute per IP address
  • Aggressive throttling applied earlier for resource-heavy operations or repeated error codes (400, 401, 403, 500)
  • Pagination: max 100 records per page (offset-based)
  • Authentication: API key passed as query parameter or header
  • Date format: All dates must be ISO 8601 UTC (e.g., 2026-01-20T15:00:00Z) — non-UTC dates may fail silently
  • Creating new custom picklist fields is not supported by the published API — configure these in Crelate's UI before migration

What This Means for Your Migration

At 120 requests/min on the Crelate side, importing 50,000 candidate records — each requiring a POST to create the contact, a second call to attach tags, and potentially a third to upload a resume — means approximately 150,000 API calls on the import side alone. At 120/min, that's ~20 hours of continuous, error-free execution.

Your extraction from Bullhorn can run 12.5× faster than your load into Crelate. Build accordingly:

  1. Extract first, load second. Pull all Bullhorn data into a local staging database before touching Crelate's API.
  2. Implement exponential backoff with jitter on 429 responses from Crelate — not just a flat retry.
  3. Monitor your Bullhorn monthly budget. At 100,000 calls/month, a single full extraction of a large Bullhorn instance can consume your entire monthly allowance. Plan extraction during a billing cycle where you can absorb the usage.
import time
import random
import requests
 
def crelate_post_with_backoff(url, headers, payload, max_retries=5):
    for attempt in range(max_retries):
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code in (200, 201):
            return response.json()
        if response.status_code == 429:
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f"Rate limited. Retrying in {wait:.1f}s...")
            time.sleep(wait)
            continue
        response.raise_for_status()
    raise Exception("Max retries exceeded on Crelate API")
Danger

Crelate's documentation explicitly warns that repeated error status codes (400, 401, 403, 500) will aggressively trigger rate limiting. If your script attempts to link a Placement to a Job UUID that failed to create in a previous step, Crelate returns a 400. If this happens in a loop, your IP will be throttled entirely, halting the migration.

Migration Approaches Compared

There are five realistic paths. Each has sharp trade-offs.

1. Native CSV Export → Crelate Import

How it works: Export list views from Bullhorn as CSV files. Clean and reformat in Excel. Import into Crelate using its spreadsheet import tool (supports Contacts and Companies).

When to use: Small datasets (<5,000 records), flat data with no complex relationships, or when Bullhorn API access isn't available (ATS Growth edition). (kb.bullhorn.com)

Limitations:

  • Bullhorn's CSV export only includes fields visible in your list view columns
  • Browser may time out on large exports; Bullhorn recommends breaking into multiple files
  • Bullhorn Automation caps CSV exports at 50,000 records
  • Crelate's importer handles Contacts and Companies — no direct CSV import for Jobs, Placements, or Activities
  • Relationships (Candidate → JobSubmission → Placement) are completely lost in CSV
  • File attachments and custom object data cannot be exported via CSV
  • Crelate can undo a record-creation import if the run was wrong

For deeper CSV trade-offs, see Using CSVs for SaaS Data Migrations.

2. API-Based Custom ETL Pipeline

How it works: Write custom scripts to extract data from Bullhorn's REST API, transform it in a staging layer, and load it into Crelate's API v3.

When to use: Mid-to-large migrations (10K–500K records), when preserving relationships and placement history is required, when you have dedicated engineering capacity.

Key steps:

  1. Authenticate to Bullhorn via OAuth 2.0 → obtain BhRestToken and restUrl
  2. Extract entities in dependency order using search (Lucene-based) and query (JPQL-based) endpoints
  3. Store extracted JSON in a staging database with Bullhorn IDs
  4. Transform: consolidate custom fields into Crelate's 20-field limit, convert epoch timestamps to ISO 8601 UTC, normalize picklist values
  5. Load into Crelate API v3 in dependency order, collecting Crelate UUIDs
  6. Build a Bullhorn ID → Crelate ID mapping table to reconstruct relationships

Risks:

  • Bullhorn's 100,000 monthly API call limit can be exhausted in a single extraction run
  • Crelate's 120 req/min throttle makes large loads slow
  • Custom object data requires querying nested customObject1s through customObject35s endpoints
  • File attachments require separate API calls per file per record

3. Third-Party Migration Service

How it works: A specialized migration team handles extraction, transformation, loading, relationship reconstruction, and validation using API-led infrastructure with rehearsals, crosswalks, and post-load validation.

When to use: When engineering bandwidth is limited, when data integrity is non-negotiable, or when the migration needs to complete in days rather than weeks.

Risks: Quality varies across providers. Some flatten too much or run CSV under the hood. Ask how relationships are rebuilt and how validation is performed.

For a deeper analysis of build-vs-buy tradeoffs, see Why Data Migration Isn't Implementation.

4. Custom ETL Platform

How it works: Your team builds dedicated extract workers, a raw landing store, a mapper, a rate-limited load queue, a dead-letter queue, and a QA suite.

When to use: You have a platform team, repeat migrations ahead, or strict compliance requirements.

Risks: Highest engineering cost and longest lead time. For one-time migrations, this is almost always more expensive than teams expect. The pipeline becomes another system to own after the move. For a broader warning, see Why DIY AI Scripts Fail.

5. Middleware/iPaaS (Zapier, Make)

How it works: Trigger-based flows connect Bullhorn and Crelate via API connectors. Crelate has a native Zapier integration.

When to use: Low-volume delta sync after cutover — not historical migration. (postman.com)

Limitations:

  • Designed for event-triggered workflows, not bulk historical data transfer
  • Crelate's Zapier integration covers limited entity types
  • No practical way to migrate 100K+ historical records through trigger-based flows
  • Every record becomes several actions, easily overrunning Crelate's 120 req/min limit
  • Multi-step relationship rebuilding is extremely difficult to orchestrate in iPaaS workflows

Comparison Table

Approach Complexity Preserves Relationships Handles Attachments Scalability Timeline
CSV Export/Import Low ❌ No ❌ No Small only (<5K) 1–3 days
Custom ETL Pipeline High ✅ Yes ✅ Yes (with effort) Large (with throttle mgmt) 2–6 weeks
Managed Migration Service Low (for you) ✅ Yes ✅ Yes Enterprise-scale Days
Custom ETL Platform High ✅ Yes ✅ Yes Enterprise 4–12 weeks
iPaaS (Zapier/Make) Medium ⚠️ Partial ❌ No Small only Ongoing

Recommendations by Scenario

  • Small agency (<5K records), flat data, no placements to preserve: CSV export is sufficient.
  • Mid-size agency (5K–100K records), active placements, custom fields: API-based ETL or managed service.
  • Enterprise agency (100K+ records), complex relationships, compliance requirements: Managed service. The engineering cost of building and debugging a custom pipeline against both APIs typically exceeds the cost of hiring specialists.
  • Ongoing sync after historical migration: iPaaS for new records only.
  • Hybrid approach: Use CSV for reference data (Companies, static Contacts) and API for relationships, activities, placements, and files.

Pre-Migration Planning

Skipping this step is the #1 cause of migration failures.

Data Audit Checklist

Inventory every Bullhorn entity type and record count:

  • Candidates — total active + archived
  • ClientContacts — total, including inactive
  • ClientCorporations — total, including parent/child relationships
  • JobOrders — open + closed + archived
  • JobSubmissions — total across all jobs
  • Placements — active + historical
  • Leads / Opportunities — total pipeline records
  • Notes — total across all entity types
  • Appointments / Tasks — total activity records
  • Custom Objects — list all active custom object types and instance counts
  • File Attachments — total count and estimated storage size
  • Custom Fields — list all custom fields per entity with data types

Mark each dataset as move, transform, archive, or drop. Use Bullhorn's /meta/{Entity} endpoint to inspect schema programmatically. (bullhorn.github.io)

Identify What NOT to Migrate

Most Bullhorn instances accumulate significant data debt. Migrating everything wastes time and budget:

  • Candidates with no activity in 3+ years
  • Duplicate records (run Bullhorn's duplicate check before export)
  • Test/sandbox data
  • Closed jobs older than your reporting window
  • Unused custom object instances
  • Draft or unsent email templates

Cutover Strategy

Strategy Best For Risk Level
Big Bang Small datasets, clear cutover date Higher — no fallback if issues found post-migration
Phased Large datasets, multiple offices/divisions Lower — migrate by entity type or division, validate between phases
Incremental Ongoing operations, can't afford downtime Lowest — migrate historical data first, then sync delta changes
Hybrid CSV works for reference data, API required for relationships Medium — fastest for mixed complexity

For most Bullhorn-to-Crelate migrations, incremental is safest because Crelate's write throughput is the bottleneck. Migrate historical data first, run validation, then sync deltas before cutover.

Risk Mitigation

  • Take a full Bullhorn data backup before starting (request via Bullhorn Support)
  • Run a test migration with a subset (e.g., one division or 1,000 records) before full execution
  • Maintain the Bullhorn instance in read-only mode during final cutover to prevent data drift
  • Document a rollback plan — if Crelate data is corrupt, delete migrated records via Crelate API and re-run from staging
Tip

Build your crosswalk table before the first test run. A crosswalk is a persistent map from Bullhorn integer IDs to Crelate GUIDs. Without it, retries create duplicates and validation becomes guesswork.

Step-by-Step API Migration Process

Step 1: Authenticate to Bullhorn

Bullhorn uses OAuth 2.0 to obtain an access token, then a separate login call to get a REST session:

# 1. Get authorization code
GET https://auth.bullhornstaffing.com/oauth/authorize?
  client_id={client_id}&response_type=code&redirect_uri={redirect_uri}
 
# 2. Exchange for access token
POST https://auth.bullhornstaffing.com/oauth/token?
  grant_type=authorization_code&code={auth_code}&
  client_id={client_id}&client_secret={client_secret}
 
# 3. Get REST session token
GET https://rest.bullhornstaffing.com/rest-services/login?
  version=*&access_token={access_token}
# Response: { "BhRestToken": "xxx", "restUrl": "https://rest99.bullhornstaffing.com/rest-services/e999/" }

Use loginInfo to resolve the correct data center URL before the login call. (bullhorn.github.io)

Step 2: Extract Entities from Bullhorn

Extract in dependency order. Use the search endpoint for indexed entities (Lucene-based) and query for smaller reference entities (JPQL-based). Paginate in batches of 500. Always use the fields parameter to request only the data you need:

def extract_bullhorn_entity(rest_url, token, entity, fields, batch_size=500):
    results = []
    start = 0
    while True:
        resp = requests.get(
            f"{rest_url}search/{entity}",
            params={
                "query": "id:>0",
                "fields": fields,
                "count": batch_size,
                "start": start,
                "BhRestToken": token
            }
        )
        data = resp.json()
        results.extend(data.get("data", []))
        if len(data.get("data", [])) < batch_size:
            break
        start += batch_size
    return results

Store raw JSON payloads in your staging database. You will need them when mapping rules change mid-project.

Step 3: Transform Data in Staging

Key transformations:

  • Consolidate custom fields: Map Bullhorn's customText1customTextN into Crelate's 20 custom field slots. Serialize overflow fields into a structured JSON note.
  • Normalize picklists: Bullhorn picklist values must match Crelate's configured picklist options, or be pre-created in Crelate Settings before migration. Creating new custom picklist fields is not supported by the Crelate API — configure them in the UI. (postman.com)
  • Convert dates: Bullhorn returns timestamps as epoch milliseconds. Crelate requires ISO 8601 UTC strings.
  • Merge Candidates and ClientContacts: Both become Crelate Contacts. Use tags (e.g., {"Default": ["Candidate"]} vs {"Default": ["Client Contact"]}) to differentiate.
  • Detect duplicates: The same person can exist as both a Candidate and a ClientContact in Bullhorn. Merge by email or name+phone, preserving the most complete data from each.
  • Build ID mapping tables: Create a bullhorn_id → crelate_id crosswalk for every entity to reconstruct relationships after loading.

Step 4: Load into Crelate API v3

Load in dependency order: Companies → Contacts → Jobs → Pipeline placements → Activities → Notes → Attachments.

Use Crelate's /info metadata endpoint to inspect logical names and allowed values before building payloads. (postman.com)

# Create a company in Crelate
response = crelate_post_with_backoff(
    url="https://app.crelate.com/api3/companies",
    headers={"Content-Type": "application/json"},
    payload={
        "Name": company_data["name"],
        "Phone": company_data.get("phone"),
        "Website": company_data.get("website"),
        "CustomFields": {
            "bullhorn_id": str(company_data["id"])
        },
        "Tags": {"Default": ["Migrated"]}
    }
)
crelate_company_id = response["Id"]
id_map["companies"][bullhorn_id] = crelate_company_id

Step 5: Rebuild Relationships

This is where most DIY scripts fail. After creating all records:

  1. Link Contacts to Companies using the appropriate lookup fields
  2. Place Contacts on Job pipelines at the correct workflow stage (map Bullhorn submission statuses to Crelate stage names)
  3. Create Placement records referencing the correct Crelate Contact ID and Job ID
  4. Attach Notes and Activities to parent records using the ID mapping table

Load and validate one level at a time. Don't batch-insert Placements before confirming every referenced Contact and Job exists in Crelate.

Step 6: Validate

Run automated checks after every load batch:

  • Record count comparison: Bullhorn entity count vs. Crelate entity count per type
  • Field-level sampling: Pull 50 random records from each entity type, compare field values against Bullhorn source
  • Relationship integrity: Verify that every Placement links to a valid Contact and Job
  • Attachment verification: Spot-check that resumes/files are accessible on the correct Contact records

Common Failure Modes and Edge Cases

Custom Fields Overflow

Bullhorn's custom field capacity dwarfs Crelate's 20-per-entity limit. If your Bullhorn Candidate entity uses customText1 through customText40, your options are:

  1. Prioritize the 20 most-used fields for Crelate's custom field slots. Archive the rest into a structured note or external system.
  2. Concatenate related fields (e.g., merge "Clearance Level" and "Clearance Expiry" into a single Crelate custom field).
  3. Convert categorical fields to Tags, which don't count against the 20-field limit.

There is no workaround — Crelate does not support custom objects or unlimited custom fields.

Custom Objects Have No Equivalent

Bullhorn supports up to 35 PersonCustomObjectInstance records and 10 JobOrderCustomObjectInstance records per entity. These function as child tables — for example, tracking multiple certifications per candidate or multiple interview rounds per submission.

Crelate has no custom object support. Every custom object must be flattened into custom fields (within the 20-field limit), tags (for categorical data), notes (for structured text), or activities (for event-type data). (bullhorn.github.io)

Duplicate Records

Bullhorn maintains separate Candidate and ClientContact entities. The same person can exist as both. In Crelate, they both become Contacts. Your transformation layer must:

  1. Detect duplicates by email or name+phone
  2. Merge records, preserving the most complete data from each
  3. Carry forward both sets of activity history

The Attachment Trap

Bullhorn stores file attachments accessible via the /entityFiles/{entityType}/{entityId} and /file/{entityType}/{entityId}/{fileId} endpoints. Each file requires a separate API call to download. For an agency with 100,000 candidates averaging 2 files each, that's 200,000+ extraction calls — which can consume your monthly Bullhorn API budget twice over.

Uploading is equally slow. At Crelate's 120 requests per minute, uploading 100,000 resumes takes a minimum of 14 hours of continuous, uninterrupted API traffic. Any network timeout or 500 error breaks the sequence if your script lacks robust state management.

Solutions:

  • Request a full data backup from Bullhorn (includes files) instead of extracting via API
  • Negotiate a temporary API limit increase with Bullhorn support for the migration period

Opportunity Type Lock-In

Crelate does not allow changing the Opportunity Type on an existing record. If your initial load assigns the wrong type, you must delete and recreate the record. Decide Opportunity Type mappings during pre-migration planning, not after load. (help.crelate.com)

Tag Overwrites

Crelate's tag updates are full replacement, not additive. If you PATCH a record's tags and omit existing tags from the payload, those tags are deleted. Always read the current tag set before updating. (postman.com)

Danger

Do not PATCH Crelate tags as if they were additive. Crelate documents tag updates as full replacement: any tags omitted from the update payload are lost.

External Primary Key Trap

Crelate's External Primary Key field is only assigned to records created via spreadsheet import. If you're doing an API-led migration, don't assume that field exists on your records. Maintain your own crosswalk store or store the Bullhorn ID in a custom field. (help.crelate.com)

Multi-Level Relationship Chains

Bullhorn's Company → Contact → JobOrder → JobSubmission → Placement chain is five levels deep. Each level depends on IDs from the previous level. If any link in the chain fails to import, downstream records become orphaned. Load and validate one level at a time.

For a deep dive into how scripts break under load, read Why DIY AI Scripts Fail.

Crelate Constraints That Limit Migration Fidelity

Be explicit with stakeholders about what Crelate cannot replicate from Bullhorn:

Bullhorn Capability Crelate Constraint
Unlimited custom fields per entity Max 20 custom fields per entity
10–35 custom objects per entity No custom objects
Separate Candidate and Contact entities Single unified Contact entity
JobSubmission as a discrete entity No direct equivalent — modeled as pipeline stage
Effective-dated entity versions No effective dating
VMS/Back Office integrations No native VMS integration
Workflow stages (unlimited) 10 (Pro), 20 (Business), 30 (Enterprise)
Custom picklist field creation via API Not supported — UI configuration only
Opportunity Type changes Cannot change after record creation

Validation and Testing Protocol

Pre-Go-Live Validation

  1. Record count reconciliation: Total records per entity type in Bullhorn vs. Crelate. Tolerance: 0% variance.
  2. Field-level validation: Sample 5% of records per entity type. Compare every mapped field value.
  3. Relationship integrity audit: For every Placement, verify the linked Contact and Job exist and are correct.
  4. Attachment spot check: Download 20 random files from Crelate and compare to Bullhorn originals.
  5. Picklist validation: Ensure all migrated picklist values match Crelate's configured options.

UAT Process

  • Have 2–3 recruiters use Crelate with migrated data for 2–3 days before full cutover
  • Provide a checklist: find a specific candidate, verify their submission history, check a placement's bill rate, confirm an attached resume
  • Document every discrepancy and trace it back to the transformation layer

Rollback Plan

  • Maintain Bullhorn access for at least 30 days post-migration
  • If Crelate data quality is unacceptable, delete migrated records via Crelate API and re-run from staging
  • Keep the staging database intact as your single source of truth during the transition

Crelate exposes backup endpoints in API v3 and admins can request full exports — use these for checkpoints and rollback insurance. (help.crelate.com)

Post-Migration Tasks

Rebuild in Crelate

  • Workflow stages: Recreate your Bullhorn submission statuses as Crelate workflow stages (Settings → Workflows)
  • Email templates: Bullhorn templates won't transfer — rebuild in Crelate's template editor
  • Automations: Bullhorn Automation rules have no equivalent export format — document and manually recreate in Crelate's automation engine
  • Integrations: Reconnect job boards, email sync, calendar integrations, and any third-party tools

User Training

  • Crelate's unified Contact model (candidates + client contacts in one entity) requires workflow adjustment for recruiters accustomed to Bullhorn's separated entities
  • Train on tag-based filtering to replace Bullhorn's entity-type navigation
  • Demonstrate pipeline views, which replace Bullhorn's submission-based workflows

Monitoring

For the first 30 days:

  • Run weekly record count comparisons
  • Monitor Crelate API error logs for failed relationship links
  • Track recruiter-reported data quality issues and batch-fix from the staging layer

Sample Field-Level Mapping Reference

Bullhorn Field Type Crelate Field Type Notes
firstName String(50) FirstName String Direct map
lastName String(50) LastName String Direct map
email String(100) Email String Direct map
phone String(20) Phone String Direct map
status String(30) EntityStatus Picklist Map values to Crelate statuses
source String(100) Source String Direct map
owner.id Integer PrimaryOwner_Id GUID Map via user ID table
dateAdded Timestamp (ms) CreatedOn ISO 8601 UTC Convert epoch → ISO
customText1customText20 String CustomFields.{logical_name} Varies Map first 20; serialize remainder into notes
skills.name Association Tags.Skills Tag array Convert skill associations to tag array
address.city String City String Direct map
salary BigDecimal Custom field Decimal Map to a custom field slot
clientCorporation.id To-one assoc Company_Id GUID Map via company crosswalk table
Candidate.id Integer Custom field or crosswalk String Store Bullhorn ID for replay and dedupe
JobOrder.numOpenings Integer Opportunity openings Integer Default null to 1 only if business agrees

Best Practices

  • Back up everything before starting — request a full Bullhorn data backup from support
  • Run a test migration with 500–1,000 records from each entity type before committing to full load
  • Validate incrementally — don't wait until the end to check data quality
  • Map custom fields early — the 20-field limit shapes your entire transformation logic
  • Monitor API budgets — track Bullhorn monthly call usage and Crelate per-minute throughput
  • Preserve Bullhorn access for 30+ days post-migration as a safety net
  • Automate validation — write scripts to compare record counts, field values, and relationship integrity between source and target
  • Document every transformation decision — when a recruiter asks "where did my custom field go?" six months later, you'll need the answer
  • Store Bullhorn source IDs somewhere durable; don't rely on Crelate External Primary Key alone for API-loaded records (help.crelate.com)
  • Keep raw extraction payloads — you'll need them when mapping rules change mid-project
  • Version your field maps and lookup translations — treat them as code, not one-time config

When to Use a Managed Migration Service

Build in-house when:

  • Your dataset is small (<10K records) and flat
  • You have a dedicated engineer with API migration experience who can commit 3–4 weeks
  • You don't need to preserve placement history or complex relationships

Hire a specialist when:

  • Your Bullhorn instance has 50K+ records with active placements
  • You have significant custom object usage that needs creative mapping
  • Your Bullhorn API call budget is limited (100K/month won't cover extraction + testing + re-runs)
  • Your recruiters can't tolerate a multi-week migration window
  • You've already attempted a DIY migration and hit rate-limit walls or data integrity issues

The hidden cost of DIY migrations isn't the initial build — it's the debugging. Orphaned records, silently dropped custom fields, and broken placement chains often surface weeks after go-live when a recruiter can't find a candidate's submission history. By then, tracing the issue back to a transformation bug requires re-extracting and re-comparing against the source.

The cleanest Bullhorn-to-Crelate projects start with one uncomfortable step: deciding what the Crelate system of record should look like after Bullhorn's extra entities, custom objects, and field banks are reduced into a smaller schema. Once that target model is explicit, the rest is engineering. If you skip that design step, you don't avoid the work — you just move it into recruiter cleanup after go-live.

How ClonePartner Handles Bullhorn-to-Crelate Migrations

ClonePartner has completed 1,200+ data migrations across CRM, ATS, and helpdesk platforms. For Bullhorn-to-Crelate, the approach addresses the three hardest problems:

  1. Rate-limit bridging: Managed queue infrastructure absorbs Bullhorn's high-speed extraction (1,500 req/min) and meters it into Crelate's tighter import window (120 req/min) with automatic 429 retry handling and IP rotation — so the migration runs continuously without manual intervention.
  2. Custom field compression: Our engineers audit your Bullhorn custom fields and objects, build a prioritized mapping to Crelate's 20-field limit, and serialize overflow data into structured, searchable formats — so nothing is silently dropped.
  3. Relationship reconstruction: Every Candidate → JobSubmission → Placement chain is rebuilt in Crelate with verified ID mappings. Post-load validation confirms zero orphaned records.

Most Bullhorn-to-Crelate migrations complete in 3–5 business days with zero downtime. Your team keeps working in Bullhorn until cutover. We run a final delta sync over the weekend — recruiters log out of Bullhorn on Friday and log into a fully populated Crelate instance on Monday.

Frequently Asked Questions

How long does a Bullhorn to Crelate migration take?
A CSV-only migration of small, flat data can complete in 1–3 days. API-based migrations with relationship preservation typically take 2–6 weeks for DIY builds or 3–5 business days with a managed migration service, depending on data volume and complexity.
What are the Bullhorn and Crelate API rate limits?
Bullhorn allows up to 1,500 requests per minute and 100,000 API calls per month (unless negotiated). Crelate API v3 enforces 120 requests per minute per IP address. Deprecated v1/v2 endpoints are throttled to 30 req/min. Repeated error codes on Crelate trigger even more aggressive throttling.
Can I migrate Bullhorn custom objects to Crelate?
Not directly. Crelate does not support custom objects. Bullhorn custom objects (up to 35 per Person entity) must be flattened into Crelate's custom fields (max 20 per entity), tags, notes, or activity records during transformation.
What Bullhorn data is lost in a CSV export?
CSV exports from Bullhorn flatten relational data, losing all Candidate-to-JobSubmission-to-Placement relationships, file attachments, custom object data, and activity history. Only fields visible in your current list view columns are included.
Does Bullhorn's ATS Growth plan support API access for migration?
No. Bullhorn's ATS Growth edition (formerly Team Edition) does not include API access. Users on this plan must rely on CSV exports or upgrade to Corporate/Enterprise editions to use the REST API.

More from our Blog

5
ATS

5 "Gotchas" in ATS Migration: Tackling Custom Fields, Integrations, and Compliance

Don't get derailed by hidden surprises. This guide uncovers the 5 critical "gotchas" that derail most projects, from mapping tricky custom fields and preventing broken integrations to navigating complex data compliance rules. Learn how to tackle these common challenges before they start and ensure your migration is a seamless success, not a costly failure.

Raaj Raaj · · 14 min read