Documentation Index
Fetch the complete documentation index at: https://f4c7a9e2d8b1-docs.tenzo.ai/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This document explains why we do things differently for each ATS and highlights important implementation quirks you should know about.Kombo-Based vs Direct API
Kombo-Based Integrations
ATS Systems: Greenhouse, Jobvite, Bullhorn, Crelate, JobDiva, Avionte, SuccessFactors, Workday How it works:- We use Kombo’s unified API as an abstraction layer
- Kombo handles auth, rate limiting, and API differences
- We inherit most methods from
BaseKomboAtsClient - Custom implementations only needed for ATS-specific features
- ✅ Faster to implement
- ✅ Automatic handling of API changes
- ✅ Consistent error handling
- ✅ Built-in retry logic
- ⚠️ Limited to what Kombo supports
- ⚠️ Can’t access ATS-specific advanced features easily
- ⚠️ Debugging requires understanding both our code and Kombo’s
Direct API Integrations
ATS Systems: ERecruit, RecruitCRM, Tracker, TempWorks, Public API How it works:- Direct HTTP calls to ATS provider’s API
- We handle all auth, pagination, rate limiting ourselves
- Complete control over all features
- ✅ Access to all ATS features
- ✅ Full control over implementation
- ✅ Can optimize for specific use cases
- ⚠️ More code to maintain
- ⚠️ Need to handle API changes ourselves
- ⚠️ More complex error handling
ATS-Specific Quirks
ERecruit
Key Differences:- Uses TWO separate auth tokens: one for eRecruit, one for Agilus custom API
- Most operations go through Agilus custom endpoints (
https://webapi.myagilus.ca/api) - Standard eRecruit API missing critical functionality
- Heavy use of custom candidate application status tracking
- ❌ All custom fields (returns empty or NotImplementedError)
- ❌ Get all application stages
- ❌ Notes to candidates
- ❌ Attachments to candidates
- ❌ Find candidates by details
RecruitCRM
Key Differences:- Uses “hiring pipelines” instead of traditional application stages
- Each job can have its own pipeline with custom stages
- Candidate-job associations are “assignments” not “applications”
- Rich candidate history tracking across multiple jobs
- ❌ Formal rejection with reasons (API doesn’t expose rejection endpoint)
- ❌ Custom field schemas (can’t fetch field definitions dynamically)
- ❌ Find candidates by details
- Move to “declined” stage instead of formal rejection
- Applications can still move through hiring pipelines
Tracker
Key Differences:- Timezone Issues: Tracker returns local time instead of UTC
- We subtract 8 hours from
updated_aftertimestamps - This is a quirk of their API returning PST/PDT times
- We subtract 8 hours from
- Uses category-based job organization
- Custom workflow states per job
- ❌ Reject application (NotImplementedError)
- ❌ Get all application stages (doesn’t have unified stages)
Bullhorn (Kombo)
Key Differences:- Uses “Placement” records for hired candidates (different from applications)
- Complex entity relationships (Candidate → JobSubmission → Placement)
- Rich custom field support through Kombo passthrough
- Application IDs are actually JobSubmission IDs
- Some operations require knowing the “corp” ID
- Heavy use of custom object types
JobDiva (Kombo)
Key Differences:- Uses hardcoded custom field names (can’t fetch schemas)
- Special “Tenzo Active” field to filter jobs
- Req ID stored in
job_codefield, not standard location - Custom rejection reason endpoint via Kombo passthrough
- ❌ Attachments (application & candidate) - NotImplementedError
- ❌ Dynamic custom field schema fetching
Avionte (Kombo)
Key Differences:- Application custom fields NOT available through Kombo
- Candidate custom field UPDATES not working (API restrictions)
- Uses “talent tags” for candidate categorization
- ❌ Application custom fields (Avionte doesn’t expose via API)
- ❌ Update candidate custom fields (API path changes not allowed)
SuccessFactors (Kombo)
Key Differences:- OData-based API (XML metadata schemas)
- Custom field discovery through OData $metadata endpoint
- Uses SAP SuccessFactors recruiting module
- HTML note format
- ❌ Notes to candidates (NotImplementedError)
- ✅ Can parse OData metadata to discover custom fields dynamically
- ✅ Supports customString*, customNumber*, customDate* field patterns
Crelate (Kombo)
Key Differences:- Uses Kombo passthrough for job batching (direct Crelate API)
- Application/candidate streaming not fully implemented
- Strong job custom field support
- Notes supported but attachments not yet implemented
- ❌ Application custom fields (Crelate doesn’t have these)
- ❌ Bulk reject (NotImplementedError)
- ⚠️ Streaming applications/candidates (Partial - not verified)
TempWorks (Direct API)
What Works:- ✅ Stream jobs
- ✅ Stream candidates
- ✅ Stream applications
- ✅ Get candidate by ID
- ✅ Get resume
- ❌ Move to stage
- ❌ Reject application
- ❌ Create application
- ❌ Add notes
- ❌ Add attachments
- ❌ Custom fields
- ❌ Get enhanced job
Public API (Minimal)
Key Differences:- NOT a traditional ATS - it’s a webhook-based integration
- Jobs stored in
AtsJobCachecosmos DB - No streaming (jobs pushed via API)
- Sends interview results via webhooks
- ✅ Get job by ID (from cache)
- ✅ Create application (webhook trigger)
- ✅ Get candidate (temporary storage during processing)
- ❌ Everything else (returns empty or minimal data)
Tracker Workable (Hybrid)
Key Differences:- Customer-specific hybrid integration (MW Resources only)
- Combines Tracker AND Workable data
- Uses Tracker for jobs, Workable for applications
- Special field mapping between systems
Common Patterns
Custom Field Handling
Three approaches across integrations:-
Dynamic Schema (Best): SuccessFactors, Greenhouse, Bullhorn
- Fetch field definitions from API
- Map field types automatically
- Works with any custom field
-
Hardcoded (Workable): JobDiva
- Field names hardcoded in client
- Must match customer’s configuration
- Fragile but works when API doesn’t expose schemas
-
Not Implemented: ERecruit, RecruitCRM, TempWorks, Avionte (partial)
- API doesn’t support or too complex
- Returns empty or raises NotImplementedError
Rejection Handling
Two patterns:-
Formal Rejection: Greenhouse, Bullhorn, Jobvite, etc.
- Get disposition reasons from ATS
- Reject with specific reason ID
- ATS tracks rejection separately from stage
-
Stage-Based: RecruitCRM, Tracker
- No formal rejection API
- Move to “declined” or “rejected” stage instead
- Workaround when ATS doesn’t have rejection concept
Note Formats
- Plain Text: ERecruit, RecruitCRM, Tracker, TempWorks
- HTML: Greenhouse, Bullhorn, SuccessFactors, Jobvite
ats_note_format property before creating notes.
Debugging Tips
Kombo Integrations
- Check Kombo dashboard for API errors
- Use passthrough for ATS-specific calls
- Remember Kombo returns standardized models - may lose ATS-specific data
Direct API Integrations
- Check access token expiration
- Look for rate limiting (429 errors)
- Verify pagination cursor handling
- Check custom API base URLs (some customers have unique endpoints)
Common Issues
“Custom fields not saving”- Check if ATS supports custom field updates
- Verify field type mapping is correct
- Some ATS require specific field formats
- Check if ATS has formal rejection (many don’t)
- Use stage movement as fallback
- Verify disposition reasons are fetched correctly
- Check
updated_aftertimestamp format (ISO 8601) - Some ATS use local time instead of UTC (Tracker!)
- Verify job status filters (some ATS hide closed jobs)
Related Documentation
- ATS Coverage Matrix - Feature support across all ATS
- Individual ATS pages for detailed method documentation
- Base ATS Client - Common patterns and base class