Skillshub bamboohr-rate-limits
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/bamboohr-rate-limits" ~/.claude/skills/comeonoliver-skillshub-bamboohr-rate-limits && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/bamboohr-rate-limits/SKILL.mdsource content
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 | | Primary rate limit signal |
header | seconds (e.g., ) | How long to wait before retrying |
| varies | May contain rate limit detail |
HTTP Status | 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; // Only retry 429, 503, 500, 502 // Honor BambooHR's Retry-After header let delay: number; if (err.meta.retryAfter) { delay = parseInt(err.meta.retryAfter, 10) * 1000; } else { // Exponential backoff with jitter 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'; // BambooHR unofficial guidance: stay under ~10 requests/second const bamboohrQueue = new PQueue({ concurrency: 3, // Max 3 concurrent requests interval: 1000, // Per 1-second window intervalCap: 8, // Max 8 requests per second }); async function rateLimitedRequest<T>(operation: () => Promise<T>): Promise<T> { return bamboohrQueue.add(() => withBambooHRRetry(operation)); } // Usage — all requests go through the queue const employees = await rateLimitedRequest(() => client.getDirectory()); const report = await rateLimitedRequest(() => client.customReport(['firstName', 'lastName'])); // Bulk operations automatically throttled const employeeDetails = await Promise.all( employeeIds.map(id => rateLimitedRequest(() => client.getEmployee(id, ['firstName', 'lastName', 'jobTitle'])) ), );
Step 4: Reduce Request Volume
Use custom reports instead of individual GETs:
// BAD: N+1 requests (one per employee) const employees = await client.getDirectory(); for (const emp of employees.employees) { const detail = await client.getEmployee(emp.id, ['salary', 'department']); // 500 employees = 501 requests } // GOOD: 1 request using custom report const report = await client.customReport([ 'firstName', 'lastName', 'department', 'jobTitle', 'hireDate', ]); // 1 request, all employee data
Use incremental sync:
// BAD: Full directory pull every time const allEmployees = await client.getDirectory(); // GOOD: Only changed employees since last sync const changed = await client.request<any>( 'GET', `/employees/changed/?since=${lastSyncTimestamp}`, ); // Only fetch details for employees that actually changed
Use table changed endpoint:
// GET /employees/changed/tables/{tableName}?since=... 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 }); // Only keep last 5 minutes 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(2), rateLimitHits: this.rateLimitHits, errorRate: recent.filter(r => r.status >= 400).length / Math.max(recent.length, 1), }; } shouldBackOff(): boolean { const stats = this.getStats(); return stats.errorRate > 0.1 || parseFloat(stats.requestsPerSecond) > 8; } }
Output
- Retry logic honoring
headerRetry-After - 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 |
|---|---|---|
+ | Check response status + header | Wait N seconds, then retry |
without | Status only | Exponential backoff from 1s |
(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.