---
# === IDENTITY ===
id: software/startup-dashboard/people-operations-dashboard/2026
canonical_question: "How do I build a people ops dashboard — ATS integration, org chart, compensation tracking, onboarding workflow?"
aliases:
  - "people operations dashboard setup"
  - "HR analytics dashboard build"
  - "startup people dashboard with BambooHR or Greenhouse"
  - "headcount and compensation tracking dashboard"
entity_type: execution_recipe
domain: software > startup-dashboard > people-operations-dashboard
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === VERIFICATION ===
last_verified: 2026-03-13
confidence: 0.88
version: 1.0
first_published: 2026-03-13

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "BambooHR added OAuth 2.0 support and new /v1/employees endpoint (2025); Greenhouse Harvest API v1/v2 deprecated, must use v3 by Aug 2026"
  next_review: 2026-09-09
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "HRIS API rate limits: BambooHR = no published hard limit but throttles at ~100 requests/minute; Rippling = 60 requests/minute. Cache employee data daily."
  - "Compensation data is the most sensitive HR data — encrypt at rest, restrict dashboard access to People team + CEO only. Never expose individual salaries in shared views."
  - "ATS candidate data subject to GDPR (EU) and CCPA (CA) — anonymize rejected candidate records after 6 months or per retention policy."
  - "Turnover calculations require at least 12 months of data for annualized rates. Quarterly snapshots are directional only for startups under 50 employees."
  - "Org chart visualization breaks at ~200 nodes in browser-rendered trees. For larger orgs, use collapsible hierarchy or department-level drill-down."

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs a recruiting pipeline tracker only, not a full people dashboard"
    use_instead: "Search knowledgelib.io for recruiting pipeline tracking — no dedicated unit yet"
  - condition: "User wants payroll processing, not analytics"
    use_instead: "Search knowledgelib.io for startup payroll setup — no dedicated unit yet"
  - condition: "User is building enterprise HRIS, not a startup people dashboard"
    use_instead: "Search knowledgelib.io for enterprise HRIS architecture — no dedicated unit yet"

# === AGENT HINTS ===
inputs_needed:
  - key: hris_platform
    question: "Which HRIS does the company use?"
    type: choice
    options: ["BambooHR", "Rippling", "Gusto", "Deel", "no HRIS yet"]
  - key: ats_platform
    question: "Which ATS does the company use for recruiting?"
    type: choice
    options: ["Greenhouse", "Lever", "Ashby", "Workable", "no ATS / spreadsheet"]
  - key: technical_skill
    question: "What is the user's technical skill level?"
    type: choice
    options: ["non-technical (no-code only)", "semi-technical (can edit code)", "developer (can write code)"]
  - key: headcount
    question: "Current headcount?"
    type: choice
    options: ["1-20", "21-50", "51-150", "150+"]

# === EXECUTION METADATA ===
execution:
  required_inputs:
    - name: "HRIS system with API access"
      source: "user's HR platform"
      format: "API key or OAuth2 credentials"
    - name: "ATS system with API access"
      source: "user's applicant tracking system"
      format: "API key or OAuth2 credentials"
    - name: "Compensation bands or budget"
      source: "user's comp plan"
      format: "Spreadsheet with role, level, salary range, equity"

  outputs:
    - name: "People operations dashboard"
      format: "deployed web application"
      description: "Live dashboard showing headcount by department, org chart, compensation analysis, turnover metrics, recruiting pipeline, and onboarding status"
    - name: "Automated HRIS/ATS sync"
      format: "scheduled ETL job"
      description: "Daily extraction from HRIS and ATS to dashboard data store"
    - name: "Onboarding tracker"
      format: "Kanban board or checklist view"
      description: "Per-employee onboarding progress with task completion status"

  tools_required:
    - name: "Retool or Metabase"
      purpose: "Dashboard UI builder"
      tier: "free"
      cost: "$0 (free tier) to $10/user/month"
      alternatives: ["Google Looker Studio", "Grafana", "Preset"]
    - name: "PostgreSQL"
      purpose: "People data warehouse"
      tier: "free"
      cost: "$0 (Supabase free tier) to $25/month"
      alternatives: ["BigQuery", "DuckDB"]
    - name: "Unified HRIS API (Merge or Finch)"
      purpose: "Normalize BambooHR/Rippling/Gusto data into single schema"
      tier: "free"
      cost: "$0 (limited) to $300/month"
      alternatives: ["Direct API integration", "n8n custom workflow"]

  credentials_needed:
    - service: "BambooHR"
      type: "API key or OAuth2 client ID + secret"
      where_to_get: "https://documentation.bamboohr.com/docs/getting-started"
      free_tier_limits: "No published rate cap; throttles ~100 req/min"
    - service: "Greenhouse"
      type: "API key (HTTP Basic Auth)"
      where_to_get: "https://developers.greenhouse.io/"
      free_tier_limits: "50 requests/10 seconds"

  estimated_duration: "6-10 hours for initial setup; 2-3 hours for compensation model calibration"
  estimated_cost: "$0 (all free tiers) to $100/month (paid tiers)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/startup-dashboard/people-operations-dashboard/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-13)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/startup-dashboard/financial-operations-dashboard/2026"
      label: "Finance dashboard provides payroll cost data"
  feeds_into:
    - id: "software/startup-dashboard/executive-command-center-design/2026"
      label: "Executive command center design — cross-functional 8-12 KPI selection, RAG thresholds, alert triggers and action rules for a single aggregated screen"
  related_to:
    - id: "business/people-ops/employee-engagement-diagnostic/2026"
      label: "Employee engagement diagnostic — survey methodology, maturity scoring, action planning and retention-risk identification"
  alternative_to:
    - id: "software/startup-dashboard/legal-compliance-dashboard/2026"
      label: "Compliance-focused people tracking"

# === SOURCES ===
sources:
  - id: src1
    title: "BambooHR API Documentation"
    author: BambooHR
    url: https://documentation.bamboohr.com/docs/getting-started
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src2
    title: "Greenhouse Developer Resources"
    author: Greenhouse
    url: https://developers.greenhouse.io/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src3
    title: "HR Dashboard: 5 Examples, Metrics and a How-To"
    author: AIHR
    url: https://www.aihr.com/blog/hr-dashboard/
    type: industry_guide
    published: 2025-08-01
    reliability: high
  - id: src4
    title: "14 Key HR Metrics to Track: Definitions and Formulas"
    author: ExtensisHR
    url: https://extensishr.com/resource/blogs/14-hr-metrics-and-how-to-calculate-them/
    type: industry_guide
    published: 2025-06-01
    reliability: high
  - id: src5
    title: "25 HRIS APIs to Integrate With in 2026"
    author: Unified.to
    url: https://unified.to/blog/25_hris_apis_to_integrate_with_in_2026_workday_bamboohr_adp_and_unified_hr_integration_apis
    type: industry_guide
    published: 2025-12-01
    reliability: high
  - id: src6
    title: "6 Essential HR Metrics Dashboards You Need in 2025"
    author: SplashBI
    url: https://splashbi.com/blog/6-essential-hr-metrics-dashboards-you-need-in-2025/
    type: industry_guide
    published: 2025-01-01
    reliability: high
---

# People Operations Dashboard

## Purpose

This recipe produces a startup people operations dashboard that integrates with the HRIS (BambooHR, Rippling, or Gusto) and ATS (Greenhouse, Lever, or Ashby), displays an interactive org chart, tracks compensation against bands, monitors turnover and retention metrics, visualizes the recruiting pipeline, and shows onboarding progress for new hires. The output is a deployed internal tool giving founders and People leads a single view of workforce health, replacing scattered spreadsheets and manual headcount tracking. [src3]

## Prerequisites
<!-- Agents: verify ALL prerequisites before executing. Missing prerequisites = failed execution. -->

- [ ] **HRIS system** with API access — [BambooHR](https://www.bamboohr.com) or [Rippling](https://www.rippling.com) with API key or OAuth2 configured
- [ ] **ATS system** with API access — [Greenhouse](https://www.greenhouse.com) or [Lever](https://www.lever.co) with API key configured
- [ ] **At least 3 months of employee data** in the HRIS — required for turnover trend analysis; 12+ months for annualized rates
- [ ] **Compensation bands** or salary budget — by role, level, and department
- [ ] **PostgreSQL database** — free tier at [Supabase](https://supabase.com) or [Neon](https://neon.tech)
- [ ] **Dashboard tool account** — [Retool](https://retool.com) or [Metabase](https://www.metabase.com)
- [ ] **Onboarding checklist template** (optional) — task list per role for 30/60/90-day onboarding

## Constraints
<!-- Hard rules. Agents: enforce throughout execution. -->

- HRIS API rate limits: BambooHR throttles at ~100 requests/minute; Greenhouse Harvest API allows 50 requests/10 seconds. Cache employee roster daily. [src1]
- Compensation data must be restricted to People team + CEO only. Never show individual salaries in department-wide views — use band position percentiles instead. [src3]
- ATS candidate data subject to GDPR (EU) and CCPA (CA). Anonymize rejected candidate records after 6 months or per your retention policy. [src2]
- Turnover rate calculation requires 12+ months for annualized accuracy. For startups under 50 employees, show rolling 6-month rate with confidence warning. [src4]
- Org chart visualization breaks at ~200 nodes in browser-rendered SVG/canvas trees. Use collapsible hierarchy with department-level drill-down for larger orgs.

## Tool Selection Decision

```
Which path?
├── User is non-technical AND budget = free
│   └── PATH A: No-Code Free — Google Sheets + manual HRIS/ATS export
├── User is non-technical AND budget > $0
│   └── PATH B: No-Code Paid — Retool + Supabase + Merge HRIS ($300/mo)
├── User is semi-technical or developer AND budget = free
│   └── PATH C: Code + Free — Metabase + PostgreSQL + cron sync scripts
└── User is developer AND budget > $0
    └── PATH D: Code + Paid — Retool + PostgreSQL + n8n + direct APIs
```

| Path | Tools | Cost | Speed | Output Quality |
|------|-------|------|-------|---------------|
| A: No-Code Free | Google Sheets + CSV exports | $0 | 3-4 hours | Basic — manual weekly updates |
| B: No-Code Paid | Retool + Supabase + Merge | $300/mo | 5-7 hours | Good — auto-sync, interactive |
| C: Code + Free | Metabase + PostgreSQL + cron | $0 | 8-10 hours | Good — full SQL control |
| D: Code + Paid | Retool + PostgreSQL + n8n | $25-50/mo | 6-8 hours | Excellent — real-time, custom |

## Execution Flow

### Step 1: Design the People Data Model

**Duration**: 30-60 minutes
**Tool**: SQL client

```sql
-- People operations data model
CREATE TABLE employees (
  id TEXT PRIMARY KEY,
  hris_id TEXT UNIQUE, -- ID from BambooHR/Rippling
  name TEXT NOT NULL,
  email TEXT,
  department TEXT NOT NULL,
  title TEXT NOT NULL,
  level TEXT, -- 'IC1', 'IC2', 'IC3', 'M1', 'M2', 'Director', 'VP', 'C-Suite'
  manager_id TEXT REFERENCES employees(id),
  hire_date DATE NOT NULL,
  termination_date DATE,
  employment_type TEXT DEFAULT 'full-time', -- 'full-time', 'part-time', 'contractor'
  location TEXT,
  status TEXT DEFAULT 'active' -- 'active', 'on_leave', 'terminated'
);

CREATE TABLE compensation (
  id SERIAL PRIMARY KEY,
  employee_id TEXT REFERENCES employees(id),
  effective_date DATE NOT NULL,
  base_salary NUMERIC(10,2),
  currency TEXT DEFAULT 'USD',
  equity_shares INTEGER DEFAULT 0,
  equity_vesting_start DATE,
  bonus_target_pct NUMERIC(5,2) DEFAULT 0,
  comp_band_min NUMERIC(10,2),
  comp_band_max NUMERIC(10,2)
);

CREATE TABLE recruiting_pipeline (
  id SERIAL PRIMARY KEY,
  ats_id TEXT UNIQUE, -- ID from Greenhouse/Lever
  job_title TEXT NOT NULL,
  department TEXT NOT NULL,
  stage TEXT NOT NULL, -- 'sourced', 'applied', 'phone_screen', 'interview', 'offer', 'hired', 'rejected'
  candidate_name TEXT,
  source TEXT, -- 'referral', 'inbound', 'sourced', 'agency'
  applied_date DATE,
  stage_entered_date DATE,
  days_in_stage INTEGER DEFAULT 0,
  recruiter TEXT
);

CREATE TABLE onboarding_tasks (
  id SERIAL PRIMARY KEY,
  employee_id TEXT REFERENCES employees(id),
  task_name TEXT NOT NULL,
  category TEXT NOT NULL, -- 'day_1', 'week_1', 'month_1', 'month_2', 'month_3'
  assigned_to TEXT,
  due_date DATE,
  completed_date DATE,
  status TEXT DEFAULT 'pending' -- 'pending', 'in_progress', 'completed', 'overdue'
);

CREATE TABLE headcount_snapshots (
  id SERIAL PRIMARY KEY,
  snapshot_date DATE NOT NULL,
  department TEXT NOT NULL,
  active_count INTEGER NOT NULL,
  new_hires INTEGER DEFAULT 0,
  terminations INTEGER DEFAULT 0,
  open_roles INTEGER DEFAULT 0
);

-- Indexes
CREATE INDEX idx_emp_department ON employees(department);
CREATE INDEX idx_emp_status ON employees(status);
CREATE INDEX idx_emp_manager ON employees(manager_id);
CREATE INDEX idx_comp_employee ON compensation(employee_id);
CREATE INDEX idx_pipeline_stage ON recruiting_pipeline(stage);
CREATE INDEX idx_pipeline_dept ON recruiting_pipeline(department);
CREATE INDEX idx_onboard_employee ON onboarding_tasks(employee_id);
CREATE INDEX idx_headcount_date ON headcount_snapshots(snapshot_date);
```

**Verify**: All 5 tables created successfully.
**If failed**: Check PostgreSQL connection and permissions.

### Step 2: Build HRIS Data Sync

**Duration**: 1-2 hours
**Tool**: n8n, custom script, or Merge API

BambooHR sync:

```javascript
// BambooHR Employee Directory
// GET https://{companyDomain}.bamboohr.com/api/gateway.php/{companyDomain}/v1/employees/directory
// Headers: Authorization: Basic {base64(apiKey + ':')}
// Returns: { employees: [{ id, displayName, department, jobTitle, ... }] }

const response = await fetch(
  `https://${domain}.bamboohr.com/api/gateway.php/${domain}/v1/employees/directory`,
  { headers: { Authorization: `Basic ${btoa(apiKey + ':')}`, Accept: 'application/json' } }
);
const data = await response.json();

for (const emp of data.employees) {
  // Upsert to employees table
  // Map: emp.id → hris_id, emp.displayName → name, emp.department → department
  // emp.jobTitle → title, emp.hireDate → hire_date
}

// For detailed fields (compensation, etc.), use:
// GET /v1/employees/{id}/?fields=salary,payRate,payType,hireDate
```

Greenhouse ATS sync:

```javascript
// Greenhouse Harvest API — list all applications
// GET https://harvest.greenhouse.io/v1/applications?per_page=100&status=active
// Headers: Authorization: Basic {base64(apiKey + ':')}

const apps = await fetch(
  'https://harvest.greenhouse.io/v1/applications?per_page=100',
  { headers: { Authorization: `Basic ${btoa(apiKey + ':')}` } }
);
const candidates = await apps.json();

for (const app of candidates) {
  // Map to recruiting_pipeline table
  // app.current_stage.name → stage, app.jobs[0].name → job_title
  // app.candidate.first_name + last_name → candidate_name
  // app.source.public_name → source
}
```

Schedule: daily at 6:00 AM for employee roster; hourly for recruiting pipeline.

**Expected output**: employees table populated with current roster. recruiting_pipeline has active candidates.
**Verify**: `SELECT department, COUNT(*) FROM employees WHERE status='active' GROUP BY department;` returns department counts matching HRIS.
**If failed**: Check API key permissions. BambooHR requires "API Access" permission level. Greenhouse requires "Harvest" API key type.

### Step 3: Build People Analytics Queries

**Duration**: 1-2 hours
**Tool**: SQL

```sql
-- 1. Headcount by department with growth trend
SELECT
  department,
  COUNT(*) FILTER (WHERE status = 'active') as active_headcount,
  COUNT(*) FILTER (WHERE hire_date >= DATE_TRUNC('month', CURRENT_DATE)) as new_this_month,
  COUNT(*) FILTER (WHERE termination_date >= DATE_TRUNC('month', CURRENT_DATE)) as termed_this_month,
  ROUND(COUNT(*) FILTER (WHERE termination_date IS NOT NULL
    AND termination_date >= CURRENT_DATE - INTERVAL '12 months')::NUMERIC
    / NULLIF(COUNT(*), 0) * 100, 1) as annual_turnover_pct
FROM employees
GROUP BY department
ORDER BY active_headcount DESC;

-- 2. Compensation band analysis (anonymized — no individual salaries)
SELECT
  e.department,
  e.level,
  COUNT(*) as headcount,
  ROUND(AVG(c.base_salary), 0) as avg_salary,
  ROUND(MIN(c.base_salary), 0) as min_salary,
  ROUND(MAX(c.base_salary), 0) as max_salary,
  ROUND(AVG(c.comp_band_min), 0) as band_min,
  ROUND(AVG(c.comp_band_max), 0) as band_max,
  ROUND(AVG((c.base_salary - c.comp_band_min) /
    NULLIF(c.comp_band_max - c.comp_band_min, 0) * 100), 1) as avg_band_position_pct
FROM employees e
JOIN compensation c ON e.id = c.employee_id
WHERE e.status = 'active'
  AND c.effective_date = (
    SELECT MAX(effective_date) FROM compensation c2
    WHERE c2.employee_id = e.id
  )
GROUP BY e.department, e.level
ORDER BY e.department, e.level;

-- 3. Recruiting pipeline funnel
SELECT
  stage,
  COUNT(*) as candidate_count,
  ROUND(AVG(days_in_stage), 1) as avg_days_in_stage,
  COUNT(*) FILTER (WHERE source = 'referral') as from_referrals,
  COUNT(*) FILTER (WHERE source = 'inbound') as from_inbound,
  COUNT(*) FILTER (WHERE source = 'sourced') as from_sourced
FROM recruiting_pipeline
WHERE stage != 'rejected'
GROUP BY stage
ORDER BY CASE stage
  WHEN 'sourced' THEN 1 WHEN 'applied' THEN 2
  WHEN 'phone_screen' THEN 3 WHEN 'interview' THEN 4
  WHEN 'offer' THEN 5 WHEN 'hired' THEN 6
END;

-- 4. Time to hire by department
SELECT
  department,
  COUNT(*) as hires,
  ROUND(AVG(stage_entered_date - applied_date), 1) as avg_days_to_hire,
  ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY stage_entered_date - applied_date), 1) as median_days_to_hire
FROM recruiting_pipeline
WHERE stage = 'hired'
  AND applied_date >= CURRENT_DATE - INTERVAL '6 months'
GROUP BY department;

-- 5. Onboarding completion rate
SELECT
  e.name,
  e.department,
  e.hire_date,
  COUNT(*) as total_tasks,
  COUNT(*) FILTER (WHERE ot.status = 'completed') as completed_tasks,
  ROUND(COUNT(*) FILTER (WHERE ot.status = 'completed')::NUMERIC / COUNT(*) * 100, 0) as completion_pct,
  COUNT(*) FILTER (WHERE ot.status = 'overdue') as overdue_tasks
FROM employees e
JOIN onboarding_tasks ot ON e.id = ot.employee_id
WHERE e.hire_date >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY e.id, e.name, e.department, e.hire_date
ORDER BY e.hire_date DESC;
```

**Verify**: Headcount query returns departments with non-zero active counts. Pipeline funnel shows descending candidate counts by stage.
**If failed**: Check employee status field mapping. Ensure terminated employees have termination_date set.

### Step 4: Assemble the Dashboard UI

**Duration**: 1-2 hours
**Tool**: Retool or Metabase

```
Dashboard Layout:
┌─────────────────────────────────────────────────────┐
│  KPI Row: Total Headcount | Open Roles | Turnover % │
│           Avg Time-to-Hire | Onboarding Completion    │
├─────────────────────────┬───────────────────────────┤
│  Org Chart / Department  │  Recruiting Pipeline      │
│  Tree (collapsible)      │  Funnel (horizontal bar)  │
├─────────────────────────┼───────────────────────────┤
│  Headcount Trend by Dept │  Comp Band Analysis       │
│  (stacked area chart)    │  (box plot by level)      │
├─────────────────────────┴───────────────────────────┤
│  Onboarding Tracker: New Hires (Kanban by phase)     │
├─────────────────────────────────────────────────────┤
│  Turnover Analysis (line chart + department table)    │
└─────────────────────────────────────────────────────┘
```

Key visual: Recruiting funnel shows conversion rate between each stage. Red highlight on stages where avg days exceeds department benchmark. Comp band chart uses box plots showing where employees sit relative to band min/max — flags individuals below band min or above band max.

**Verify**: All 6 sections render. Headcount matches HRIS.
**If failed**: Check query bindings and data source connections.

### Step 5: Configure People Alerts

**Duration**: 30-60 minutes
**Tool**: n8n or Pipedream + Slack/Email

```javascript
// Weekly people health check (run Monday 8:00 AM)
const alerts = [];

// Turnover spike — above 15% annualized
if (annualTurnoverPct > 15) {
  alerts.push(`WARNING: Annualized turnover is ${annualTurnoverPct}% (healthy < 10%).`);
}

// Stale candidates — anyone in pipeline > 14 days without stage change
for (const candidate of staleCandidates) {
  alerts.push(`${candidate.name} stuck in ${candidate.stage} for ${candidate.days_in_stage} days.`);
}

// Onboarding overdue tasks
for (const hire of newHires) {
  if (hire.overdue_tasks > 0) {
    alerts.push(`${hire.name}: ${hire.overdue_tasks} overdue onboarding tasks.`);
  }
}

// Comp band violations — employees outside bands
if (compViolations.length > 0) {
  alerts.push(`${compViolations.length} employees outside compensation bands — review needed.`);
}
```

**Verify**: Test alert fires in Slack/email with sample data.
**If failed**: Check webhook URL and workflow execution.

### Step 6: Deploy and Share Access

**Duration**: 30 minutes
**Tool**: Dashboard platform settings

- Share full dashboard with CEO and Head of People
- Create restricted view (no compensation) for department managers
- Set recruiter-only view showing pipeline for their roles only
- Enable weekly email digest for hiring managers with open roles

**Verify**: CEO sees all sections. Department managers cannot see compensation data.

## Output Schema

```json
{
  "output_type": "people_operations_dashboard",
  "format": "deployed web application",
  "components": [
    {"name": "headcount_by_department", "type": "chart", "description": "Stacked area chart of headcount by department over time", "required": true},
    {"name": "org_chart", "type": "visualization", "description": "Interactive collapsible org tree by manager hierarchy", "required": true},
    {"name": "recruiting_funnel", "type": "chart", "description": "Horizontal funnel showing candidates by pipeline stage with conversion rates", "required": true},
    {"name": "compensation_analysis", "type": "chart", "description": "Box plots showing salary distribution vs band by role level", "required": true},
    {"name": "onboarding_tracker", "type": "kanban", "description": "New hire onboarding progress by 30/60/90-day phase", "required": true},
    {"name": "kpi_cards", "type": "metrics", "description": "Headcount, open roles, turnover %, avg time-to-hire, onboarding completion", "required": true}
  ],
  "refresh_interval": "daily (HRIS sync) + hourly (ATS pipeline)",
  "data_source": "PostgreSQL synced from HRIS and ATS"
}
```

## Quality Benchmarks

| Quality Metric | Minimum Acceptable | Good | Excellent |
|---------------|-------------------|------|-----------|
| Data freshness | < 48 hours lag | < 24 hours | Same-day sync |
| Headcount accuracy | Within 2 of HRIS | Exact match | Real-time match |
| Pipeline coverage | Active candidates only | All open roles | + historical hires |
| Comp data completeness | Base salary only | + equity + bonus | Full comp package + bands |
| Onboarding tracking | Manual checklist | Automated task tracking | + manager feedback + NPS |

**If below minimum**: Check HRIS/ATS API sync logs. Headcount discrepancies usually mean contractor/intern exclusion or delayed termination processing.

## Error Handling

| Error | Likely Cause | Recovery Action |
|-------|-------------|----------------|
| BambooHR API 401 | API key revoked or insufficient permissions | Regenerate API key with "API Access" permission level |
| Greenhouse API 403 | Harvest API key not created or wrong type | Create new Harvest API key (not Job Board or Partner key) in Greenhouse admin |
| Headcount doesn't match HRIS | Contractors or interns excluded from API response | Check employment_type filter — include all types and filter in dashboard |
| Compensation data missing | HRIS doesn't expose salary via API (common on lower tiers) | Use manual CSV upload as fallback; schedule monthly re-import |
| Org chart has orphan nodes | Manager_id references terminated employee or null | Add fallback: orphan employees roll up to department head |
| Onboarding tasks not progressing | Assignees not updating status | Add Slack reminders for overdue tasks; auto-mark overdue after due_date |

## Cost Breakdown

| Component | Free Tier | Paid Tier | At Scale (Series A+) |
|-----------|-----------|-----------|----------|
| Dashboard (Retool) | $0 (5 users) | $10/user/mo | $50/mo (5 users) |
| Database (Supabase) | $0 (500MB) | $25/mo | $25/mo |
| ETL (n8n) | $0 (self-hosted) | $20/mo | $50/mo |
| Unified HRIS API (Merge) | N/A | $300/mo | $300/mo |
| **Total** | **$0** | **$355/mo** | **$425/mo** |

## Anti-Patterns

### Wrong: Showing individual salaries on shared dashboards
Even if "only managers can see it," one screenshot or screen share leaks compensation data across the company. This destroys trust and creates legal liability. [src3]

### Correct: Show percentile position within band, not dollar amounts
Display each person as a dot on a 0-100% band position scale. Managers see "Employee X is at 45th percentile of band" — never the actual salary.

### Wrong: Calculating turnover from terminations in the current month only
Monthly turnover of 2/50 = 4% looks alarming but annualizes to 48% — or it could be normal seasonal variation. Single-month snapshots create false urgency or false comfort. [src4]

### Correct: Use 12-month rolling turnover with 3-month trend
Annualized turnover rate = (terminations in trailing 12 months / average headcount) * 100. Show 3-month trend to distinguish spikes from patterns.

## When This Matters

Use when a startup has 20+ employees and needs automated visibility into headcount, hiring pipeline, and compensation beyond manual spreadsheets. Critical for companies scaling headcount rapidly (hiring 5+ people/quarter) or preparing for board reporting on team metrics. This recipe builds the dashboard — for recruiting strategy or compensation philosophy, use a playbook card instead.

## Related Units

- [Financial Operations Dashboard](/software/startup-dashboard/financial-operations-dashboard/2026) — provides payroll cost data
- [Sales Operations Dashboard](/software/startup-dashboard/sales-operations-dashboard/2026) — provides sales team headcount context
- [Startup Metrics Overview](/software/startup-dashboard/startup-metrics-overview/2026) — aggregated metrics dashboard
- [Legal & Compliance Dashboard](/software/startup-dashboard/legal-compliance-dashboard/2026) — compliance-focused people tracking
