Claude
Skills
Sign in
Back

bamboohr-reference-architecture

Included with Lifetime
$97 forever

Implement BambooHR reference architecture for production HR data pipelines. Use when designing new BambooHR integrations, building employee sync systems, or establishing architecture standards for BambooHR-powered applications. Trigger with phrases like "bamboohr architecture", "bamboohr design", "bamboohr project structure", "bamboohr system design", "bamboohr pipeline".

Designsaashrbamboohrarchitecture

What this skill does

# BambooHR Reference Architecture

## Overview

Production-ready architecture for BambooHR integrations covering the three most common patterns: real-time employee sync, HR data pipeline, and employee lifecycle automation.

## Prerequisites

- Understanding of layered architecture and event-driven design
- BambooHR API knowledge from earlier skills in this pack
- TypeScript project setup with Node.js 18+

## Instructions

### Architecture Overview

```
┌──────────────────────────────────────────────────────────┐
│                    Your Application                       │
├──────────────┬───────────────┬────────────────────────────┤
│  API Layer   │  Sync Engine  │  Webhook Handler           │
│  /api/*      │  (Cron/Queue) │  /webhooks/bamboohr        │
├──────────────┴───────────────┴────────────────────────────┤
│                   Service Layer                            │
│  EmployeeService  │  TimeOffService  │  ReportService     │
├───────────────────┴──────────────────┴────────────────────┤
│                BambooHR Client Layer                       │
│  BambooHRClient  │  Cache  │  RetryHandler  │  Metrics    │
├───────────────────┴─────────┴────────────────┴────────────┤
│                   Data Layer                               │
│  PostgreSQL (employees)  │  Redis (cache)  │  S3 (files)  │
└───────────────────────────────────────────────────────────┘
                           │
                           ▼
              ┌────────────────────────┐
              │   BambooHR REST API    │
              │ api.bamboohr.com/api/  │
              │   gateway.php/{co}/v1  │
              └────────────────────────┘
```

### Project Structure

```
bamboohr-integration/
├── src/
│   ├── bamboohr/
│   │   ├── client.ts              # HTTP client (from sdk-patterns)
│   │   ├── types.ts               # BambooHR API response types
│   │   ├── retry.ts               # Retry with Retry-After support
│   │   ├── cache.ts               # LRU + Redis cache layer
│   │   └── metrics.ts             # Request counting and latency
│   ├── services/
│   │   ├── employee-sync.ts       # Incremental directory sync
│   │   ├── time-off.ts            # PTO balance and request management
│   │   ├── reports.ts             # Custom report generation
│   │   └── lifecycle.ts           # Onboarding/offboarding automation
│   ├── handlers/
│   │   ├── webhook.ts             # Webhook signature verification + routing
│   │   └── events.ts              # Employee change event processors
│   ├── api/
│   │   ├── health.ts              # Health check endpoint
│   │   ├── employees.ts           # REST API for local employee data
│   │   └── reports.ts             # Report generation endpoints
│   ├── jobs/
│   │   ├── full-sync.ts           # Scheduled full directory sync
│   │   ├── incremental-sync.ts    # Frequent delta sync
│   │   └── report-export.ts       # Scheduled report export
│   └── db/
│       ├── schema.sql             # PostgreSQL schema
│       └── queries.ts             # Database queries
├── tests/
│   ├── unit/
│   │   ├── client.test.ts
│   │   ├── employee-sync.test.ts
│   │   └── webhook.test.ts
│   ├── integration/
│   │   └── bamboohr-live.test.ts
│   └── mocks/
│       └── bamboohr-handlers.ts   # MSW handlers
├── config/
│   ├── default.json
│   ├── production.json
│   └── test.json
└── docker-compose.yml             # PostgreSQL + Redis for local dev
```

### Step 1: Data Model

```sql
-- db/schema.sql
CREATE TABLE bamboohr_employees (
  id               INTEGER PRIMARY KEY,  -- BambooHR employee ID
  first_name       TEXT NOT NULL,
  last_name        TEXT NOT NULL,
  display_name     TEXT,
  work_email       TEXT,
  job_title        TEXT,
  department       TEXT,
  division         TEXT,
  location         TEXT,
  supervisor_id    INTEGER REFERENCES bamboohr_employees(id),
  status           TEXT DEFAULT 'Active',
  hire_date        DATE,
  termination_date DATE,
  employee_number  TEXT,
  raw_data         JSONB,                -- Full BambooHR response
  synced_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  created_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at       TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_employees_status ON bamboohr_employees(status);
CREATE INDEX idx_employees_department ON bamboohr_employees(department);
CREATE INDEX idx_employees_synced ON bamboohr_employees(synced_at);

CREATE TABLE bamboohr_sync_log (
  id            SERIAL PRIMARY KEY,
  sync_type     TEXT NOT NULL,  -- 'full', 'incremental', 'webhook'
  started_at    TIMESTAMPTZ NOT NULL,
  completed_at  TIMESTAMPTZ,
  employees_created  INTEGER DEFAULT 0,
  employees_updated  INTEGER DEFAULT 0,
  employees_deleted  INTEGER DEFAULT 0,
  errors        JSONB DEFAULT '[]',
  status        TEXT DEFAULT 'running'  -- 'running', 'completed', 'failed'
);
```

### Step 2: Employee Sync Service

```typescript
// src/services/employee-sync.ts
import { BambooHRClient } from '../bamboohr/client';
import { db } from '../db/queries';

const SYNC_FIELDS = [
  'firstName', 'lastName', 'displayName', 'workEmail',
  'jobTitle', 'department', 'division', 'location',
  'supervisor', 'status', 'hireDate', 'terminationDate',
  'employeeNumber',
];

export class EmployeeSyncService {
  constructor(private client: BambooHRClient) {}

  async fullSync(): Promise<SyncResult> {
    const log = await db.createSyncLog('full');

    try {
      // One API call for all employee data
      const report = await this.client.customReport(SYNC_FIELDS);
      const result = { created: 0, updated: 0, deleted: 0, errors: [] as string[] };

      for (const emp of report.employees) {
        try {
          const existing = await db.getEmployee(parseInt(emp.id));
          if (existing) {
            await db.updateEmployee(parseInt(emp.id), emp);
            result.updated++;
          } else {
            await db.createEmployee(parseInt(emp.id), emp);
            result.created++;
          }
        } catch (err) {
          result.errors.push(`Employee ${emp.id}: ${(err as Error).message}`);
        }
      }

      // Mark employees not in report as inactive
      const activeIds = new Set(report.employees.map(e => parseInt(e.id)));
      const localEmployees = await db.getActiveEmployeeIds();
      for (const localId of localEmployees) {
        if (!activeIds.has(localId)) {
          await db.deactivateEmployee(localId);
          result.deleted++;
        }
      }

      await db.completeSyncLog(log.id, result);
      return result;
    } catch (err) {
      await db.failSyncLog(log.id, (err as Error).message);
      throw err;
    }
  }

  async incrementalSync(): Promise<SyncResult> {
    const lastSync = await db.getLastSyncTimestamp();
    const changed = await this.client.request<any>(
      'GET', `/employees/changed/?since=${lastSync}`,
    );

    const changedIds = Object.keys(changed.employees || {});
    if (changedIds.length === 0) return { created: 0, updated: 0, deleted: 0, errors: [] };

    // Fetch details for changed employees only
    const result = { created: 0, updated: 0, deleted: 0, errors: [] as string[] };
    for (const id of changedIds) {
      const emp = await this.client.getEmployee(id, SYNC_FIELDS);
      // Upsert logic...
    }

    return result;
  }

  async handleWebhookEvent(employeeId: string, action: string, fields: Record<string, string>) {
    switch (action) {
      case 'Created':
        await db.createEmployee(parseInt(employeeId), fields);
        break;
      case 'Updated':
        await db.updateEmployee(parseInt(employeeId), fields);
        break;
      case 'Deleted':
        await db.deactivateEmployee(parseInt(employeeId));
        break;
    }
  }
}
```

### Step 3: Employee Lifecycle Automation

```typescript
// src/services/lifecycle.ts
export class EmployeeLifecycleService {
  constructor(
    private bamboohr: BambooHRClient,
    private slackClient: any,
    private googleAdmin: any,
  ) {}

  async onNew

Related in Design