BambooHR Rate Limits
Overview
BambooHR does not publish exact rate limits, but the API returns 503 Service Unavailable with a Retry-After header when you exceed them. This skill covers detection, backoff, request optimization, and queue-based throttling.
Prerequisites
- BambooHR API client configured
- Understanding of async/await patterns
Instructions
Step 1: Understand BambooHR Rate Limiting Behavior
BambooHR rate limiting details:
| Signal | Value | Description |
|---|
| HTTP Status | 503 | Primary rate limit signal |
Retry-After header | seconds (e.g., 30) | How long to wait before retrying |
X-BambooHR-Error-Message | varies | May contain rate limit detail |
HTTP Status 429 | rare | Some endpoints return 429 for employee count limits |
Key insight: BambooHR uses 503 (not 429) for rate limiting. Failed authentication attempts also count toward rate limits, so ensure your API key is valid before making many requests.
Step 2: Implement Retry-After Aware Backoff
import { BambooHRApiError } from './client';
interface RetryConfig {
maxRetries: number;
baseDelayMs: number;
maxDelayMs: number;
}
const DEFAULT_RETRY: RetryConfig = {
maxRetries: 5,
baseDelayMs: 1000,
maxDelayMs: 60_000,
};
async function withBambooHRRetry<T>(
operation: () => Promise<T>,
config = DEFAULT_RETRY,
): Promise<T> {
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
} catch (err) {
if (attempt === config.maxRetries) throw err;
if (!(err instanceof BambooHRApiError)) throw err;
if (!err.retryable) throw err;
let delay: number;
if (err.meta.retryAfter) {
delay = parseInt(err.meta.retryAfter, 10) * 1000;
} else {
const exponential = config.baseDelayMs * Math.pow(2, attempt);
const jitter = Math.random() * config.baseDelayMs;
delay = Math.min(exponential + jitter, config.maxDelayMs);
}
console.warn(
`BambooHR rate limited (attempt ${attempt + 1}/${config.maxRetries}). ` +
`Waiting ${(delay / 1000).toFixed(1)}s...`
);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('unreachable');
}
Step 3: Queue-Based Rate Limiting
import PQueue from 'p-queue';
const bamboohrQueue = new PQueue({
concurrency: 3,
interval: 1000,
intervalCap: 8,
});
async function rateLimitedRequest<T>(operation: () => Promise<T>): Promise<T> {
return bamboohrQueue.add(() => withBambooHRRetry(operation));
}
const employees = await rateLimitedRequest(() => client.getDirectory());
const report = await rateLimitedRequest(() => client.customReport(['firstName', 'lastName']));
const employeeDetails = await Promise.all(
employeeIds.map(id =>
( client.(id, [, , ]))
),
);
Step 4: Reduce Request Volume
Use custom reports instead of individual GETs:
const employees = await client.getDirectory();
for (const emp of employees.employees) {
const detail = await client.getEmployee(emp.id, ['salary', 'department']);
}
const report = await client.customReport([
'firstName', 'lastName', 'department', 'jobTitle', 'hireDate',
]);
Use incremental sync:
const allEmployees = await client.getDirectory();
const changed = await client.request<any>(
'GET', `/employees/changed/?since=${lastSyncTimestamp}`,
);
Use table changed endpoint:
const changedJobs = await client.request<any>(
'GET', `/employees/changed/tables/jobInfo?since=${lastSyncTimestamp}`,
);
Step 5: Monitor Rate Limit Usage
class BambooHRRateLimitMonitor {
private requestLog: { timestamp: number; status: number }[] = [];
private rateLimitHits = 0;
recordRequest(status: number) {
this.requestLog.push({ timestamp: Date.now(), status });
const cutoff = Date.now() - 5 * 60 * 1000;
this.requestLog = this.requestLog.filter(r => r.timestamp > cutoff);
if (status === 503 || status === 429) {
this.rateLimitHits++;
}
}
getStats() {
const recent = this.requestLog;
return {
requestsLast5Min: recent.length,
requestsPerSecond: (recent.length / 300).toFixed(),
: .,
: recent.( r. >= ). / .(recent., ),
};
}
(): {
stats = .();
stats. > || (stats.) > ;
}
}
Output
- Retry logic honoring
Retry-After header
- Queue-based throttling preventing rate limit hits
- Request volume reduction via custom reports and incremental sync
- Rate limit monitoring with stats
Error Handling
| Signal | Detection | Action |
|---|
503 + Retry-After: N | Check response status + header | Wait N seconds, then retry |
503 without Retry-After | Status only | Exponential backoff from 1s |
429 (employee limit) | Status code | Contact BambooHR to increase limit |
| Many consecutive 503s | Monitor hit count | Pause all requests for 60s |
Enterprise Considerations
- Multi-tenant rate limits: Each company domain has independent rate limits
- Batch jobs: Run large syncs during off-peak hours (nights/weekends)
- Contact BambooHR: For enterprise-volume needs, request rate limit increases through support
- Webhook alternatives: Use webhooks for real-time changes instead of polling (see
bamboohr-webhooks-events)
Resources
Next Steps
For security configuration, see bamboohr-security-basics.