Platform Evolution
- Original: Zenefits standalone platform
- Current: TriNet Zenefits (acquired 2022)
- Focus: SMB market (10-500 employees)
- Strength: Benefits administration
Zenefits, now part of TriNet, is an all-in-one HR platform designed for small and medium businesses. While they offer comprehensive HR, benefits, payroll, and compliance features, their API access is restricted and requires approval, making integration more challenging than modern alternatives.
Platform Evolution
Integration Challenges
Getting API access to Zenefits is not straightforward:
Initial Application
Technical Review
Business Development
Approval & Credentials
To: [Contact Zenefits/TriNet developer support]Subject: API Access Request - Tallyfy Integration
Dear Zenefits Developer Team,
We would like to request API access to integrate Tallyfy with Zenefits.
Company: [Your Company Name]Use Case: Workflow automation for HR processesExpected Volume: [Number of customers]Technical Contact: [Your email]Integration Type: [Direct API / Marketplace]
Please provide information on:1. API access requirements2. Technical documentation3. Sandbox environment availability4. Partnership requirements
Thank you,[Your Name]
Given the challenges with direct API access, consider these alternatives:
// Example: Using Finch for Zenefits dataconst Finch = require('@finch-hq/client');
const finch = new Finch({ apiKey: 'your_finch_api_key' // Get this in days, not months});
// Access Zenefits data immediatelyconst getZenefitsEmployees = async (accessToken) => { const employees = await finch.hris.directory.list({ accessToken: accessToken // Customer's Zenefits connection });
return employees.individuals.map(emp => ({ id: emp.id, name: `${emp.first_name} ${emp.last_name}`, email: emp.emails?.[0]?.data, department: emp.department?.name, manager: emp.manager?.id }));};
Benefits:
# Example: Puppeteer automation for Zenefitsfrom playwright.sync_api import sync_playwrightimport json
class ZenefitsAutomation: def __init__(self, username, password): self.username = username self.password = password
def extract_employee_data(self): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page()
# Login to Zenefits page.goto('https://secure.zenefits.com/accounts/login/') page.fill('#username', self.username) page.fill('#password', self.password) page.click('button[type="submit"]')
# Wait for dashboard page.wait_for_selector('.dashboard', timeout=10000)
# Navigate to employees page.click('a[href*="/people/directory"]') page.wait_for_selector('.employee-list')
# Extract data employees = page.evaluate(''' () => { const rows = document.querySelectorAll('.employee-row'); return Array.from(rows).map(row => ({ name: row.querySelector('.name')?.textContent, email: row.querySelector('.email')?.textContent, department: row.querySelector('.department')?.textContent, status: row.querySelector('.status')?.textContent })); } ''')
browser.close() return employees
Many Zenefits customers resort to scheduled exports:
// Process Zenefits CSV exportsconst processZenefitsExport = async (csvPath) => { const csv = require('csv-parser'); const fs = require('fs'); const employees = [];
fs.createReadStream(csvPath) .pipe(csv()) .on('data', (row) => { employees.push({ employeeId: row['Employee ID'], name: row['Full Name'], email: row['Work Email'], department: row['Department'], manager: row['Manager'], hireDate: row['Hire Date'], status: row['Employment Status'] }); }) .on('end', async () => { // Process employees in Tallyfy for (const employee of employees) { if (employee.status === 'Active' && isNewHire(employee.hireDate)) { await createOnboardingProcess(employee); } } });};
// Schedule daily importconst cron = require('node-cron');cron.schedule('0 9 * * *', () => { const today = new Date().toISOString().split('T')[0]; const exportPath = `/imports/zenefits_${today}.csv`;
if (fs.existsSync(exportPath)) { processZenefitsExport(exportPath); }});
// Zenefits OAuth flowclass ZenefitsOAuth { constructor(clientId, clientSecret) { this.clientId = clientId; this.clientSecret = clientSecret; this.baseUrl = 'https://secure.zenefits.com'; }
getAuthorizationUrl(redirectUri, state) { const params = new URLSearchParams({ response_type: 'code', client_id: this.clientId, redirect_uri: redirectUri, scope: 'read write', state: state });
return `${this.baseUrl}/oauth/authorize?${params}`; }
async exchangeCodeForToken(code, redirectUri) { const response = await fetch(`${this.baseUrl}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'authorization_code', code: code, client_id: this.clientId, client_secret: this.clientSecret, redirect_uri: redirectUri }) });
return response.json(); }
async refreshToken(refreshToken) { const response = await fetch(`${this.baseUrl}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: this.clientId, client_secret: this.clientSecret }) });
return response.json(); }}
// Common Zenefits API operationsclass ZenefitsAPI { constructor(accessToken) { this.accessToken = accessToken; this.baseUrl = 'https://api.zenefits.com/v1'; }
async request(endpoint, options = {}) { const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers: { 'Authorization': `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', ...options.headers } });
if (!response.ok) { throw new Error(`Zenefits API error: ${response.statusText}`); }
return response.json(); }
// Get all employees async getEmployees(limit = 100) { return this.request('/employees', { method: 'GET', params: { limit } }); }
// Get specific employee async getEmployee(employeeId) { return this.request(`/employees/${employeeId}`); }
// Get time off requests async getTimeOffRequests() { return this.request('/time_off_requests'); }
// Get benefits enrollments async getBenefitsEnrollments() { return this.request('/benefits/enrollments'); }}
// Handle Zenefits paginationasync function getAllRecords(api, endpoint) { const allRecords = []; let page = 1; let hasMore = true;
while (hasMore) { const response = await api.request(endpoint, { params: { limit: 100, page: page } });
allRecords.push(...response.data);
// Check if more pages exist hasMore = response.has_more || false; page++;
// Respect rate limits await sleep(100); }
return allRecords;}
// Monitor benefits enrollment periodsclass BenefitsEnrollmentAutomation { async checkEnrollmentStatus() { // Get all employees const employees = await this.zenefits.getEmployees();
for (const employee of employees) { // Check enrollment window if (this.isInEnrollmentWindow(employee)) { // Create Tallyfy process await this.tallyfy.createProcess({ template: 'benefits-enrollment', data: { employeeId: employee.id, employeeName: employee.full_name, enrollmentDeadline: employee.enrollment_deadline, currentBenefits: employee.current_benefits, eligiblePlans: await this.getEligiblePlans(employee) } }); } } }
isInEnrollmentWindow(employee) { const today = new Date(); const windowStart = new Date(employee.enrollment_start); const windowEnd = new Date(employee.enrollment_end);
return today >= windowStart && today <= windowEnd; }
async getEligiblePlans(employee) { // Fetch available benefit plans for employee return this.zenefits.request(`/benefits/eligible_plans?employee_id=${employee.id}`); }}
// Sync time-off requests with approval workflowsconst syncTimeOffRequests = async () => { const requests = await zenefits.getTimeOffRequests();
for (const request of requests) { if (request.status === 'pending') { // Check if process exists const existing = await tallyfy.findProcess({ externalId: `zenefits_pto_${request.id}` });
if (!existing) { // Create approval workflow await tallyfy.createProcess({ template: 'pto-approval', externalId: `zenefits_pto_${request.id}`, data: { requestId: request.id, employee: request.employee_name, type: request.time_off_type, startDate: request.start_date, endDate: request.end_date, days: request.days_requested, manager: request.approver_name, reason: request.reason } }); } } }};
// Handle approval decisionstallyfy.on('process.completed', async (process) => { if (process.template === 'pto-approval') { const decision = process.outcome; const requestId = process.data.requestId;
if (decision === 'approved') { await zenefits.approveTimeOff(requestId); } else { await zenefits.denyTimeOff(requestId, process.data.denialReason); } }});
// Secure storage of sensitive dataclass SecureZenefitsIntegration { constructor() { // Encrypt tokens at rest this.tokenStorage = new EncryptedStorage();
// Audit all API calls this.auditLog = new AuditLogger(); }
async makeSecureRequest(endpoint, method, data) { const startTime = Date.now();
try { // Log request (without sensitive data) await this.auditLog.log({ action: 'zenefits_api_call', endpoint: endpoint, method: method, timestamp: new Date().toISOString() });
// Make request const response = await this.zenefits.request(endpoint, { method, body: data });
// Log success await this.auditLog.log({ action: 'zenefits_api_success', endpoint: endpoint, duration: Date.now() - startTime });
return response; } catch (error) { // Log failure await this.auditLog.log({ action: 'zenefits_api_error', endpoint: endpoint, error: error.message, duration: Date.now() - startTime });
throw error; } }}
If considering migration:
Export Data
Choose New Platform
Run Parallel
Cut Over
Zenefits (now TriNet Zenefits) presents significant integration challenges due to restricted API access and lengthy approval processes. For most organizations, using a unified API provider like Finch or Merge offers a much faster path to integration. If you must integrate directly with Zenefits, be prepared for a multi-month process and consider file-based integration as an interim solution.