---
title: "Bullhorn to Avionte Migration: APIs, Data Mapping & Limits"
slug: bullhorn-to-avionte-migration-apis-data-mapping-limits
date: 2026-04-23
author: Raaj
categories: [Migration Guide, Avionte, Bullhorn]
excerpt: "A CTO-level technical guide to migrating staffing data from Bullhorn to Avionte BOLD, covering API constraints, object mapping, rate limits, and real failure modes."
tldr: "Bullhorn to Avionte migration requires restructuring relational staffing data across incompatible schemas. CSV exports drop relationships, the Bullhorn API caps at 1,500 req/min, and Avionte has no custom object equivalent."
canonical: https://clonepartner.com/blog/bullhorn-to-avionte-migration-apis-data-mapping-limits/
---

# Bullhorn to Avionte Migration: APIs, Data Mapping & Limits


Migrating from Bullhorn to Avionte BOLD is a data-model translation problem, not a file-transfer exercise. Bullhorn structures staffing data around **ClientCorporation → ClientContact → JobOrder → JobSubmission → Placement** chains with deep relational associations and up to 35 custom object instances per entity type. Avionte BOLD uses a flatter, talent-centric **Company → Contact → Job → Talent → Placement** model with integrated back-office capabilities (payroll, billing, timekeeping) but significantly fewer extension points for custom data.

A naive CSV export from Bullhorn flattens these relational chains. It silently drops note associations, severs Candidate → Submission → Placement histories, and loses custom object data entirely. The core engineering challenge: Bullhorn's API enforces strict rate limits and complex pagination, while Avionte's native bulk import tools are designed for simple lists — not complex historical relationships. Moving data between these systems means merging Bullhorn Candidates into Avionte Talent, converting JobOrders into Jobs, serializing historical activities, and reconciling two fundamentally different ID systems.

This guide covers the architectural differences between both systems, every viable migration method with honest trade-offs, the API constraints that will bottleneck your ETL scripts, concrete object and field mapping, and the edge cases that break most DIY attempts.

For broader ATS migration pitfalls, see [5 "Gotchas" in ATS Migration](https://clonepartner.com/blog/blog/ats-migration-gotchas/). For a general assessment of CSV-based approaches, see [Using CSVs for SaaS Data Migrations: Pros and Cons](https://clonepartner.com/blog/blog/csv-saas-data-migration/).

## Why Staffing Agencies Migrate from Bullhorn to Avionte

The migration drivers are operational, not cosmetic:

- **Integrated back office:** Avionte BOLD combines front-office ATS/CRM with payroll, billing, and tax management in a single platform. Bullhorn requires third-party back-office integrations (1Staff, Tempworks) that add cost and integration maintenance.
- **Light industrial and clerical focus:** Avionte was purpose-built for high-volume temp staffing verticals — clerical, light industrial, IT staffing. Agencies in these verticals often find Avionte's workflows more aligned with their day-to-day operations.
- **Cost structure:** Bullhorn's per-user pricing plus add-on costs for Automation, Marketplace integrations, and back-office connectors can push total cost of ownership significantly higher than Avionte's bundled model.
- **Simplicity over extensibility:** Bullhorn offers deep customization (custom objects, Data Hub, extensive API). Some agencies don't need that complexity and prefer Avionte's more opinionated, streamlined workflow.

The trade-off is real: Avionte's simpler schema means you lose structural flexibility during migration. Custom objects, deep automation chains, and Bullhorn-specific integrations need to be rebuilt or retired.

## The Data Model Gap: Bullhorn vs. Avionte BOLD

Understanding the architectural mismatch is the prerequisite for every migration decision.

**Bullhorn** models staffing data as a rich relational graph. The primary entities — ClientCorporation, ClientContact, Candidate, JobOrder, JobSubmission, Placement, Opportunity, Note, Appointment — are linked through one-to-one, one-to-many, and many-to-many associations. Custom objects (up to 35 per entity type for Person and ClientCorporation entities) extend this model further. Every entity carries `customText`, `customInt`, `customFloat`, and `customDate` fields.

**Avionte BOLD** uses a flatter, more opinionated model. The core entities are Company, Contact, Talent (the equivalent of Candidate), Job/Order, and Placement. Activities and notes are stored on the Talent record. The system has fewer custom field extension points and no equivalent to Bullhorn's custom object instances.

| Concept | Bullhorn Entity | Avionte BOLD Equivalent | Notes |
|---|---|---|---|
| Client company | ClientCorporation | Company | Direct mapping; Bullhorn auto-creates an archived ClientContact when creating a ClientCorporation |
| Hiring manager | ClientContact | Contact | Contacts in Avionte are tied to Companies |
| Candidate/worker | Candidate | Talent | Avionte uses "Talent" for all candidate types |
| Job requisition | JobOrder | Job/Order | Map status values manually (e.g., "Accepting Candidates" → "Open") |
| Submission | JobSubmission | Pipeline Stage | Avionte pipelines candidates to jobs differently |
| Placement | Placement | Placement | Back-office fields (pay rate, bill rate) map natively in integrated BOLD |
| Sales pipeline | Opportunity | No direct equivalent | Must flatten to notes or custom fields |
| Notes | Note | Activity/Note | Bullhorn Notes reference Candidate, ClientContact, JobOrder, or Placement; Avionte activities live on the Talent record |
| Appointments | Appointment | Activity | Collapse into activity records |
| Custom objects | PersonCustomObjectInstance1–35 | No equivalent | Data must be serialized into custom fields or discarded |
| File attachments | CandidateFileAttachment | Resume/Document | Avionte supports resume parsing on import; other file types need API-level handling |

> [!WARNING]
> Bullhorn's Opportunity entity has no direct equivalent in Avionte BOLD. If your agency uses Opportunities for sales pipeline tracking, that data must be flattened into notes, exported to a separate system, or abandoned. Plan this decision early. Similarly, if you heavily utilize Bullhorn's custom objects (e.g., tracking specialized certifications in a standalone table), you will need to flatten this data into custom fields on the Avionte Talent profile — or accept losing it.

## Migration Approaches: CSV, API, Middleware, and Managed Service

### Method 1: Native CSV Export/Import

**How it works:** Export data from Bullhorn's list views or Bullhorn Automation as CSV files. Clean and reformat the CSVs to match Avionte's import templates. Import into Avionte using the Talent Multi-Upload tool (for candidates) or the Bulk Import feature (for Jobs, Placements, and Companies).

**When to use it:** Small datasets (under 10,000 records), simple flat data with minimal relationships, or as a supplement to API-based migration for specific entity types.

**Constraints:**
- Bullhorn Automation CSV exports are capped at **50,000 records per export**.
- Bullhorn's native list-view export is affected by column count — hyperlinked columns produce additional data and can cause the export to fail or time out. Including the Submissions column on a Candidate list view will export the five most recent submissions per record, potentially quintupling your result size.
- Bullhorn's Custom Import tool only supports Candidates, Contacts, and Leads — **up to 1,000 records per batch**.
- Avionte's Talent Multi-Upload accepts CSV files with thousands of records, but duplicate checking runs only against existing system data, not within the file itself.
- Avionte's Bulk Import supports Jobs, Placements, and Companies — but **does not handle relational data** like historical activities, notes, or submission histories.

| Pros | Cons |
|---|---|
| No engineering required | Flattens all relational data |
| Free / built-in | Drops notes, activities, attachments |
| Good for quick data spot-checks | 50K record cap on Bullhorn side |
| | Manual field-mapping in spreadsheets |
| | No custom object export |

**Complexity: Low** | **Risk: High for enterprise data** | **Scalability: Poor**

For more on file-based migration limitations, see [Using CSVs for SaaS Data Migrations: Pros and Cons](https://clonepartner.com/blog/blog/csv-saas-data-migration/).

### Method 2: API-Based Custom ETL Pipeline

**How it works:** Build extraction scripts against the Bullhorn REST API to pull all entities with their full field sets and associations. Transform the data in a staging layer (normalize IDs, map picklists, restructure relationships). Load into Avionte using the Avionté Partner API (Talent Service, Company Service, Order Service).

**When to use it:** Enterprise datasets (50K+ records), when you need to preserve relational chains (Candidate → Submission → Placement), or when custom objects and attachments must migrate.

**Constraints — Bullhorn side:**
- **Authentication:** Bullhorn uses a two-phase OAuth 2.0 flow. First, obtain an access token. Then, exchange it via a `/login` call for a **BhRestToken** and a dynamic `restUrl`. The BhRestToken expires after approximately 10 minutes of inactivity — your script must handle token refresh via the OAuth refresh token flow.
- **Rate limit:** The maximum number of API calls is **1,500 per minute** per API user. Exceeding this returns a `429 Too Many Requests` response.
- **Pagination:** Query and search results are capped at **500 records per request** (default is 30). You must use `start` and `count` parameters to paginate through large result sets.
- **The restUrl is dynamic** — it is returned at login time and must never be hardcoded.

**Constraints — Avionte side:**
- The Avionté API program requires registration through the developer portal. Technology providers must apply to the API program and be approved.
- The CreateTalent API enforces **duplicate checking** based on SSN → Email → First+Last Name (in that priority order). If SSN is provided, it's the only field used for dedup. If absent, Email is used. If neither is present, it falls back to name matching.
- Not all fields configurable in BOLD's "New Talent Requirements" are available through the API.
- **Rate limits for the Avionte API are not publicly documented.** You must contact your Avionte account manager for tenant-specific limits.

| Pros | Cons |
|---|---|
| Full control over relational data | Requires dedicated engineering |
| Can preserve notes, activities, attachments | Bullhorn rate limit requires throttling |
| Handles custom objects | Avionte API access requires approval |
| Scriptable and repeatable | Avionte rate limits undocumented |

**Complexity: High** | **Risk: Medium (with proper error handling)** | **Scalability: Excellent**

If you are considering building your own pipeline, read [The Data Migration Risk Model: Why DIY AI Scripts Fail](https://clonepartner.com/blog/blog/why-ai-migration-scripts-fail/).

### Method 3: Middleware / Integration Platforms (Zapier, Make)

**How it works:** Use a no-code/low-code platform to connect Bullhorn triggers to Avionte actions. Typically used for ongoing sync rather than bulk historical migration.

**When to use it:** Only for ongoing sync of new records after the initial migration is complete. Not viable for bulk historical data.

**Why it fails for migration:** Middleware platforms process records one at a time. At 1,500 req/min on Bullhorn's side (and unknown limits on Avionte's), migrating 200K candidates would take days of continuous execution — assuming zero errors. These platforms also lack the transformation layer needed to restructure Bullhorn's relational model into Avionte's flatter structure.

**Complexity: Low** | **Risk: High for bulk data** | **Scalability: Very Poor**

### Method 4: Managed Migration Service

**How it works:** A dedicated migration engineering team builds custom extraction, transformation, and loading scripts tailored to your specific Bullhorn configuration and Avionte target environment. They handle API authentication, rate limiting, relationship preservation, and validation.

**When to use it:** When you have 50K+ records, complex relational data, custom objects, or when your engineering team doesn't have bandwidth to build and maintain single-use migration scripts.

| Pros | Cons |
|---|---|
| Preserves relational chains | Cost |
| Handles edge cases and error recovery | Vendor selection required |
| No internal engineering time | Less direct control |
| Typically includes validation and rollback planning | |

**Complexity: Low (for you)** | **Risk: Low** | **Scalability: Excellent**

### Approach Comparison

| Factor | CSV Export/Import | Custom API ETL | Middleware | Managed Service |
|---|---|---|---|---|
| Preserves relationships | ❌ | ✅ | ❌ | ✅ |
| Handles custom objects | ❌ | ✅ | ❌ | ✅ |
| Migrates attachments | ❌ | ✅ | ❌ | ✅ |
| Engineering effort | None | High | Low | None |
| Scalability | < 50K records | Unlimited | < 1K records | Unlimited |
| Risk of data loss | High | Medium | High | Low |
| Cost | Free | Engineering hours | Subscription | Service fee |
| Best for | Quick checks, small datasets | Enterprise with dev team | Post-migration sync only | Enterprise without dev bandwidth |

## Pre-Migration Planning & Data Audit

Before touching production data on either side, define the scope. A migration is the best time to purge legacy data.

### Data Audit Checklist

- **Count records** by entity type in Bullhorn: Candidates, ClientCorporations, ClientContacts, JobOrders, Submissions, Placements, Notes, Opportunities.
- **Identify custom objects** in use: query Bullhorn's metadata API (`/meta/Candidate?fields=*`) to discover which `customObject{#}s` instances are active.
- **Catalog custom fields**: map every `customText`, `customInt`, `customFloat`, `customDate` field to determine which carry real data vs. legacy/unused fields.
- **Map picklists:** Bullhorn dropdowns often differ from Avionte's standard taxonomies. Document every picklist mapping.
- **Measure attachment volume**: file attachments (resumes, signed documents) can be the largest data payload. Size this early.
- **Flag duplicate and stale records**: Bullhorn does not hard-delete most entities by default — `isDeleted` soft-delete flags must be filtered before export.

### Define Migration Scope

Not everything should migrate. Make explicit decisions:

- **Exclude** candidates with `isDeleted=true` and no activity in 24+ months.
- **Exclude** test records, training data, and placeholder companies (e.g., "TBD" companies created for contactless imports).
- **Decide** on Opportunities: migrate as notes, export to a separate CRM, or discard.
- **Decide** on custom objects: serialize to Avionte custom fields, export to a data warehouse, or discard.

### Migration Strategy

| Strategy | Best For | Risk |
|---|---|---|
| **Big bang** | Small agencies (< 5K records), low complexity | All-or-nothing; rollback is full revert |
| **Phased by entity** | Mid-size agencies; migrate Companies → Contacts → Talent → Jobs → Placements in order | Manageable; partial rollback possible |
| **Incremental with cutover** | Enterprise; migrate historical data first, sync delta during parallel-run period, then cut over | Lowest risk; highest engineering effort |

For most staffing agencies with 50K+ records, the **phased by entity** approach works best. It respects dependency order (Companies must exist before Contacts can be linked) and allows validation checkpoints between phases.

## Migration Architecture & API Constraints

### Data Flow: Extract → Transform → Load

```
Bullhorn REST API ──► Staging DB / JSON Files ──► Transformation Layer ──► Avionte Partner API
       │                      │                          │                        │
   OAuth 2.0 +            ID mapping               Field mapping,           Bearer Token
   BhRestToken            tables                   picklist translation     authentication
```

### Bullhorn Extraction

The Bullhorn REST API exposes entity data via `/entity`, `/search`, and `/query` endpoints.

- **`/search`** uses Lucene syntax, returns up to 500 results per page. Best for full-text search across indexed fields.
- **`/query`** uses SQL-like WHERE clauses, also capped at 500 per page. Best for structured extraction by date range or status.
- **`/entity/{type}/{id}`** retrieves specific records by ID. Supports comma-separated IDs for batch gets.

For large-scale extraction, use `/query` with date-range windowing:

```python
# Paginated extraction from Bullhorn
import requests
import time

BASE_URL = "{restUrl}"  # Dynamic, from login response
TOKEN = "{BhRestToken}"  # From login response

def extract_entity(entity_type, fields, where_clause):
    all_records = []
    start = 0
    count = 500  # Max per request
    total = None
    
    while total is None or start < total:
        params = {
            "where": where_clause,
            "fields": fields,
            "count": count,
            "start": start,
            "showTotalMatched": "true",
            "BhRestToken": TOKEN
        }
        
        resp = requests.get(f"{BASE_URL}/query/{entity_type}", params=params)
        
        if resp.status_code == 429:
            time.sleep(1)  # Back off on rate limit
            continue
        
        if resp.status_code == 401:
            refresh_token()  # BhRestToken expired
            continue
            
        data = resp.json()
        total = data.get("total", 0)
        all_records.extend(data.get("data", []))
        start += count
        
        # Throttle to stay under 1,500 req/min
        time.sleep(0.05)
    
    return all_records

# Extract all active candidates
candidates = extract_entity(
    "Candidate",
    "id,firstName,lastName,email,phone,status,dateAdded,customText1,customText2",
    "isDeleted=false AND status='Active'"
)
```

> [!NOTE]
> Bullhorn's BhRestToken has a default idle timeout of approximately 10 minutes. For long-running extraction jobs, implement a background token-refresh loop rather than refreshing only on 401 responses. Generating a new token for every request will flag your account and throttle your API access.

### Avionte Loading

Avionte exposes separate service APIs for each domain: **Talent Service**, **Company Service**, and **Order Service**. Authentication uses Bearer tokens obtained through the Avionté Security Token Service.

```python
# Create Talent record in Avionte
import requests

AVIONTE_BASE = "https://api.avionte.com"  # Tenant-specific
BEARER_TOKEN = "{token_from_STS}"

def create_talent(talent_data):
    headers = {
        "Authorization": f"Bearer {BEARER_TOKEN}",
        "Content-Type": "application/json",
        "Origin": "MigrationScript"  # Must be consistent across all calls
    }
    
    payload = {
        "FirstName": talent_data["firstName"],
        "LastName": talent_data["lastName"],
        "Email": talent_data["email"],
        "Phone": talent_data.get("phone", ""),
        "Status": map_status(talent_data["status"]),
        "useNewTalentRequirements": False  # Bypass BOLD field requirements
    }
    
    resp = requests.post(
        f"{AVIONTE_BASE}/talent/v1/CreateTalent",
        json=payload,
        headers=headers
    )
    
    return resp.json()
```

> [!WARNING]
> The Avionte CreateTalent API checks for duplicates using SSN first, then Email, then First+Last Name. If you're migrating records that share email addresses (common in staffing when a candidate re-applies through different channels), plan your dedup strategy before loading.

## Step-by-Step Migration Process

### Phase 1: Extract from Bullhorn

1. **Authenticate** — complete the OAuth 2.0 flow, obtain BhRestToken and restUrl.
2. **Extract reference data first** — Skills, BusinessSectors, Categories, Sources. These are lookup entities needed to resolve foreign keys.
3. **Extract Companies** — query `ClientCorporation` with `isDeleted=false`. Include address, status, custom fields.
4. **Extract Contacts** — query `ClientContact`, include the `clientCorporation.id` foreign key.
5. **Extract Candidates** — query `Candidate`, include skills (via association endpoint), custom fields, custom objects.
6. **Extract JobOrders** — include `clientCorporation.id`, status, start/end dates.
7. **Extract Submissions** — `JobSubmission` records link Candidates to JobOrders.
8. **Extract Placements** — include pay rate, bill rate, dates, associated Candidate and JobOrder IDs.
9. **Extract Notes** — Bullhorn Notes can reference Candidates, ClientContacts, JobOrders, or Placements via `personReference`, `jobOrder`, and `placements` fields.
10. **Extract File Attachments** — use `/entity/{entityType}/{id}/fileAttachments` to get metadata, then download each file.

Store all raw JSON payloads in a staging database (PostgreSQL or MongoDB). Do not transform data in memory during extraction — if the script crashes, you lose your place.

### Phase 2: Transform

1. **Build an ID mapping table** — Bullhorn integer IDs → Avionte IDs (populated during load). This is the backbone of the entire migration.
2. **Map picklist/status values** — Bullhorn's candidate statuses, job statuses, and sources won't match Avionte's. Create an explicit mapping dictionary.
3. **Flatten custom objects** — serialize Bullhorn `PersonCustomObjectInstance` data into Avionte custom fields. If Avionte doesn't have enough custom field slots, decide what to drop.
4. **Restructure Notes** — Bullhorn Notes are entity-agnostic (linked via personReference/jobOrder/placement). Avionte Activities live on the Talent record. Map Note → Talent Activity, preserving the original timestamp and author.
5. **Normalize addresses** — Bullhorn stores addresses as nested objects. Avionte expects flat fields.
6. **Cleanse HTML** from text fields and rich-text notes.
7. **Handle multi-value fields** — Bullhorn Skills are many-to-many associations. Avionte uses tag-based competencies (Category + Skill). Map the skill taxonomy explicitly.

### Phase 3: Load into Avionte

Load in strict dependency order:

1. **Companies** — create Company records, capture Avionte Company IDs, update the mapping table.
2. **Contacts** — link to Companies using the mapping table.
3. **Talent** — create Talent records with mapped statuses and skills.
4. **Jobs** — link to Companies.
5. **Placements** — link to Talent and Jobs.
6. **Activities/Notes** — attach to Talent records.
7. **Attachments** — resumes and documents.

### Phase 4: Validate

1. **Record count comparison** — total records per entity type in Bullhorn vs. Avionte.
2. **Field-level sampling** — randomly select 50–100 records per entity type and compare field values.
3. **Relationship validation** — verify that Contact → Company, Talent → Placement, and Placement → Job links are intact.
4. **Attachment verification** — spot-check that resumes and documents are accessible in Avionte.

## Common Failure Modes & Edge Cases

### Broken Multi-Level Relationships

The chain **ClientCorporation → ClientContact → JobOrder → JobSubmission → Placement** is the core of staffing data. If you load entities out of order, or if a Company fails to create and you proceed to load its Contacts, you end up with orphaned records. Always load in dependency order and halt on parent-entity failures.

### Custom Objects with No Home

Bullhorn supports up to 35 PersonCustomObjectInstances and 10 per JobOrder, Placement, Opportunity, and ClientCorporation. Avionte has no equivalent structure. Your options:

- Serialize critical custom object data into Avionte's available custom fields (limited slots)
- Export to a separate data warehouse for reference
- Accept data loss and document what was dropped

### Duplicate Records in Avionte

Avionte's CreateTalent API duplicate checking is aggressive. If two Bullhorn Candidates share an email address (common when the same person applied through different channels), the second create will be flagged as a duplicate and rejected. Pre-deduplicate on the Bullhorn side, or use the `useNewTalentRequirements: false` flag and handle dedup manually.

### Orphaned Notes and Records

If a Bullhorn `Note` is tied to a `Candidate` that was deleted, the API might still export the Note. Avionte will reject it during import because the parent ID does not exist. Filter orphaned records during the transform phase.

### Attachment Volume

Bullhorn file attachments are associated with Candidates, ClientContacts, ClientCorporations, JobOrders, Opportunities, or Placements. Avionte's Bulk Import does not handle file attachments — these must go through the API. Budget separately for attachment migration; for agencies with 100K+ candidate files, this can be the longest phase.

### API Rate Limit Exhaustion

Bullhorn's 1,500 req/min limit sounds generous, but extracting a full dataset with associated entities, notes, and files burns through it fast. A single Candidate with skills (1 request), notes (1 request), files (1 request), and custom objects (1 request) costs 4 API calls. For 100K candidates, that's 400K requests — roughly 4.5 hours of continuous extraction at max throughput, assuming zero retries.

### Multi-Level Company Hierarchies

Bullhorn allows deeply nested parent-child company hierarchies. Avionte handles this differently, often requiring you to flatten the hierarchy or use custom tagging.

## Limitations & Data Loss Scenarios

Be explicit with stakeholders about what *cannot* make the trip:

| Data | Can It Migrate? | Notes |
|---|---|---|
| Candidate/Talent core fields | ✅ | Direct mapping |
| Company and Contact records | ✅ | Direct mapping |
| Job Orders | ✅ | Status values need mapping |
| Placements | ✅ | Back-office fields map natively in integrated BOLD |
| Notes and Activities | ⚠️ | Requires API; loses multi-entity association (flattens to Talent) |
| Custom Objects | ❌ | Must serialize to custom fields or discard |
| Opportunities | ❌ | No Avionte equivalent |
| File Attachments | ⚠️ | API only; Bulk Import doesn't support |
| Automation rules & workflows | ❌ | Must be rebuilt in Avionte |
| Email history | ⚠️ | Partial; depends on what Bullhorn stored vs. email provider |
| Submission history | ⚠️ | Must be restructured into Avionte's pipeline model |
| Bullhorn Data Hub custom schemas | ❌ | No equivalent in Avionte |

## Validation & Testing Protocol

**Before go-live**, run this protocol:

1. **Test migration on sandbox** — both Bullhorn and Avionte offer sandbox/test environments. Run the full migration pipeline against test data first.
2. **Record count reconciliation** — automated comparison of source counts vs. target counts per entity type. Aim for 100% on core entities.
3. **Field-level spot check** — randomly sample 2% of records (minimum 100) per entity type. Compare every field value.
4. **Relationship integrity check** — for sampled records, verify that linked entities resolve correctly in Avionte.
5. **User Acceptance Testing (UAT)** — have 3–5 recruiters perform their normal workflows in Avionte using migrated data. Ask them to find specific candidates, verify placement histories, and check that notes appear correctly.
6. **Rollback plan** — Avionte doesn't have a one-click rollback. Your rollback plan is your Bullhorn instance, which should remain active and unchanged until validation is complete. Do not decommission Bullhorn until UAT passes.

## Post-Migration Tasks

- **Rebuild automations** — Bullhorn Automation workflows do not export. Recreate equivalent workflows in Avionte.
- **Reconfigure integrations** — job board feeds, VMS connections, background check providers, and onboarding tools need to be reconnected to Avionte.
- **User training** — Avionte BOLD has a different UI paradigm. Budget 1–2 weeks for recruiter training before cutting over.
- **Monitor for data drift** — if you ran a parallel period, verify that any records created in Bullhorn during migration were captured in the final sync.
- **Archive Bullhorn** — maintain read-only access to Bullhorn for at least 90 days post-cutover for reference and compliance purposes.

## Sample Field Mapping Reference

| Bullhorn Field | Bullhorn Entity | Avionte BOLD Field | Avionte Entity | Transform Notes |
|---|---|---|---|---|
| `id` | Candidate | N/A (internal) | Talent | Store in mapping table |
| `firstName` | Candidate | `FirstName` | Talent | Direct |
| `lastName` | Candidate | `LastName` | Talent | Direct |
| `email` | Candidate | `Email` | Talent | Direct; used for dedup |
| `phone` | Candidate | `Phone` | Talent | Strip formatting |
| `status` | Candidate | `Status` / `StatusId` | Talent | Picklist mapping required |
| `address.address1` | Candidate | `Address1` | Talent | Flatten nested object |
| `address.city` | Candidate | `City` | Talent | Direct |
| `address.state` | Candidate | `State` | Talent | Direct |
| `address.zip` | Candidate | `Zip` | Talent | Direct |
| `customText1`–`customText20` | Candidate | Custom fields (limited) | Talent | Map selectively |
| `name` | ClientCorporation | `CompanyName` | Company | Direct |
| `clientContacts` | ClientCorporation | Contacts (linked) | Contact | Rebuild association via mapping table |
| `title` | JobOrder | `JobTitle` | Job | Direct |
| `startDate` | JobOrder | `StartDate` | Job | Date format conversion |
| `clientCorporation.id` | JobOrder | `CompanyId` | Job | Resolve via mapping table |
| `comments` | Note | `ActivityNotes` | Activity | Flatten; lose multi-entity linkage |
| `dateAdded` | Note | `ActivityDate` | Activity | Preserve timestamp |
| `payRate` | Placement | `PayRate` | Placement | Direct |
| `clientBillRate` | Placement | `BillRate` | Placement | Direct |

## Best Practices Summary

- **Back up everything** before starting. Export Bullhorn data to a separate archive, not just the migration staging area.
- **Run test migrations** against sandbox environments. At least two full dry runs before touching production.
- **Load in dependency order**: Companies → Contacts → Talent → Jobs → Placements → Notes → Attachments.
- **Maintain an ID mapping table** throughout. Bullhorn integer IDs to Avionte IDs is the single most important artifact of the migration.
- **Validate incrementally** — don't wait until the end to discover that 10K records failed.
- **Plan for data you'll lose** — custom objects, Opportunities, and complex automation rules won't survive. Document these decisions for stakeholders before migration begins.
- **Keep Bullhorn active** for at least 90 days post-cutover as a read-only reference.

## When to Use a Managed Migration Service

Build in-house if:
- You have < 10K total records with flat data (no custom objects, minimal notes)
- You have a dedicated developer who can spend 3–6 weeks on the project
- You're comfortable with Avionte's API program application process and undocumented rate limits

Don't build in-house if:
- You have 50K+ records with deep relational chains
- Custom objects carry business-critical data that needs to survive
- You need zero downtime — recruiters can't stop working during migration
- Your engineering team is already at capacity
- You've never worked with either API before

The hidden cost of DIY migration isn't the initial build — it's the debugging. Bullhorn's API returns partial successes silently, Avionte's duplicate checking rejects records without clear remediation paths, and relationship breakages only surface when a recruiter can't find a placement three weeks after go-live.

At ClonePartner, we've handled ATS migrations with hundreds of thousands of records where relational integrity was non-negotiable. Our approach: extract with full association traversal, transform in a staging layer with complete ID mapping, load in dependency order with per-record error capture, and validate before cutover. We handle the Bullhorn rate-limit throttling, Avionte API enrollment, and the edge cases documented above so your team can keep recruiting.

For more on our approach to zero-downtime migration, see [Zero Downtime Guaranteed](https://clonepartner.com/blog/blog/zero-downtime-data-migration/). For a deeper look at why AI-generated migration scripts fail at scale, see [Why DIY AI Scripts Fail and How to Engineer Accountability](https://clonepartner.com/blog/blog/why-ai-migration-scripts-fail/).

> Migrating from Bullhorn to Avionte with complex relational data, custom objects, or high record volumes? Our engineering team has built extraction pipelines against Bullhorn's API and loaded into Avionte BOLD for staffing agencies with hundreds of thousands of records. We handle the rate limits, the relationship mapping, and the validation — you keep recruiting.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30&utm_source=blog&utm_medium=button&utm_campaign=demo_bookings&utm_content=cta_click&utm_term=demo_button_click)

## Frequently asked questions

### Can I export data from Bullhorn to Avionte using CSV?

Yes, but with major limitations. Bullhorn Automation exports cap at 50,000 records, and CSV flattens all relational data. Avionte's Bulk Import handles Jobs, Placements, and Companies but not notes, activities, or attachments. For anything beyond simple flat data, you need the API.

### What is the Bullhorn API rate limit?

Bullhorn enforces a maximum of 1,500 API calls per minute per API user. Exceeding this returns a 429 Too Many Requests error. The BhRestToken also expires after approximately 10 minutes of idle time, requiring a refresh token flow.

### Does Avionte BOLD support custom objects like Bullhorn?

No. Bullhorn supports up to 35 PersonCustomObjectInstances and 10 per JobOrder, Placement, and ClientCorporation. Avionte BOLD has no equivalent structure. Custom object data must be serialized into available custom fields or exported to a separate system.

### How long does a Bullhorn to Avionte migration take?

For small agencies (under 10K records) using CSV, a few days. For enterprise datasets (50K+ records) using API-based migration with full relational preservation, expect 2–6 weeks including test migrations, validation, and UAT. The Bullhorn extraction phase alone can take 4+ hours for 100K candidates due to rate limits.

### What data is lost when migrating from Bullhorn to Avionte?

Bullhorn Opportunities have no Avionte equivalent and cannot migrate directly. Custom object instances must be flattened or discarded. Automation workflows must be rebuilt. Notes lose their multi-entity associations (they flatten to Talent-level activities). Submission history must be restructured into Avionte's pipeline model.
