SKILL.md
BambooHR Performance Tuning
Overview
Optimize BambooHR API performance through request reduction, caching, incremental sync, and connection pooling. The biggest wins come from eliminating N+1 query patterns using custom reports and the changed-since endpoint.
Prerequisites
- BambooHR API client configured
- Redis or in-memory cache available (optional)
- Performance monitoring in place
Instructions
Step 1: Eliminate N+1 Queries with Custom Reports
The single biggest performance improvement: use POST /reports/custom instead of individual employee GETs.
// BAD: 501 API calls for 500 employees
const dir = await client.getDirectory(); // 1 call
for (const emp of dir.employees) {
await client.getEmployee(emp.id, ['salary', 'hireDate']); // 500 calls
}
// GOOD: 1 API call for all employees with all needed fields
const report = await client.customReport([
'firstName', 'lastName', 'department', 'jobTitle',
'hireDate', 'workEmail', 'status', 'location',
'supervisor', 'employeeNumber',
]);
// 1 call, returns all employees with all fields
Performance impact: 500x reduction in API calls. Custom reports return all active employees in one request.
Step 2: Incremental Sync with Changed-Since
import { readFileSync, writeFileSync } from 'fs';
const LAST_SYNC_FILE = '.bamboohr-last-sync';
async function incrementalSync(client: BambooHRClient): Promise<string[]> {
// Read last sync timestamp
let lastSync: string;
try {
lastSync = readFileSync(LAST_SYNC_FILE, 'utf-8').trim();
} catch {
lastSync = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); // Default: 24h ago
}
// GET /employees/changed/?since=... — returns only changed employee IDs
const changed = await client.request<{
employees: Record<string, { id: string; lastChanged: string }>;
}>('GET', `/employees/changed/?since=${lastSync}`);
const changedIds = Object.keys(changed.employees || {});
console.log(`${changedIds.length} employees changed since ${lastSync}`);
if (changedIds.length === 0) return [];
// Fetch only changed employees' details
// For large sets, use custom report with filter; for small sets, individual GETs
if (changedIds.length > 20) {
// Bulk: use custom report (returns all, then filter client-side)
const report = await client.customReport([
'firstName', 'lastName', 'department', 'status',
]);
const changedData = report.employees.filter(e =>
changedIds.includes(e.id?.toString()),
);
// Process changedData...
} else {
// Small set: individual GETs are fine
for (const id of changedIds) {
const emp = await client.getEmployee(id, ['firstName', 'lastName', 'department', 'status']);
// Process emp...
}
}
// Save sync timestamp
writeFileSync(LAST_SYNC_FILE, new Date().toISOString());
return changedIds;
}
