---
title: "How to Analyze Compensation with Unified's HR & Directory API"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_analyze_compensation_with_unified_hr_and_directory_api-icon.png
date: 2026-01-10T00:00:00.000Z
tag: Product
description: "Compensation analysis looks straightforward until you support more than one HR system."
url: "https://unified.to/blog/how_to_analyze_compensation_with_unified_hr_and_directory_api"
---

# How to Analyze Compensation with Unified's HR & Directory API
------
_January 10, 2026_

Compensation analysis looks straightforward until you support more than one HR system.


Across HRIS platforms, 'employee pay' is modeled very differently. Some systems emphasize salary. Others break compensation into many components. Employment status, contractor treatment, and demographic fields vary in both structure and availability. Even when the data exists, it's often exposed inconsistently or buried behind vendor-specific assumptions.


For a PM, this creates immediate ambiguity:

- Does 'total compensation' include bonuses, equity, or only base pay?
- Are we analyzing active employees only, or also recent terminations?
- Can averages and equity analyses be trusted across HRIS providers?

Many products handle this by hardcoding assumptions per vendor or narrowing scope to a single HR system. That works until customers ask for broader support—or expect the numbers to line up across tools.


Unified's [HR & Directory API](/hris) is designed to remove that fragmentation. It exposes employees and their **current compensation packages** through a normalized Employee model, regardless of which HRIS your customer uses. Compensation components, employment status, and core demographics are structured consistently, while leaving normalization policy decisions (like annualization or inclusion rules) explicitly in your control.


This guide shows how to build a compensation analysis module on top of that model—fetching employees, filtering active staff, normalizing compensation frequencies, and computing totals and averages—without maintaining vendor-specific connectors or embedding undocumented assumptions in your product logic.


## Prerequisites

- Node.js v18+
- A Unified account with an **HRIS** integration enabled
- Your [Unified API](/technology) key (JWT)
- A customer HRIS `connectionId`

## Step 1: Set up your project


```bash
mkdir compensation-analysis-demo
cd compensation-analysis-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv
```


Create a `.env` file:


```plain text
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_HRIS=your_customer_hris_connection_id
```


## Step 2: Initialize the SDK


```typescript
import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';

const { UNIFIED_API_KEY, CONNECTION_HRIS } = process.env;

const sdk = new UnifiedTo({
  security: { jwt: UNIFIED_API_KEY! },
});
```


## Step 3: Understand how compensation is represented in Unified


Unified exposes compensation data through the **Employee** object. There is no separate 'Employment' or 'Compensation' endpoint—everything you need for this use case is embedded in the employee record.


### Employee (`HrisEmployee`)


Each employee includes:

- **Identity**

    `id`, `first_name`, `last_name`, `emails`, `title`

- **Employment status**

    `employment_status` (`ACTIVE` / `INACTIVE`)


    `hired_at`, `terminated_at`


    `employment_type` (`FULL_TIME`, `PART_TIME`, `CONTRACTOR`, etc.)

- **Compensation array** (`compensation[]`)

    Each entry represents one component of the employee's compensation package.

- **Optional demographics**

    `gender`, `date_of_birth`, `marital_status`, `pronouns`


    (Availability varies by HRIS and customer configuration.)


### Compensation entries


Each item in `compensation[]` includes:

- `type` — `SALARY`, `BONUS`, `STOCK_OPTIONS`, `EQUITY`, `OTHER`
- `amount` — numeric value
- `currency` — ISO currency code (e.g. `USD`)
- `frequency` — `ONE_TIME`, `DAY`, `QUARTER`, `YEAR`, `HOUR`, `MONTH`, `WEEK`
- `group_id` — optional reference to an HRIS group (e.g. department)

Important constraints:

- Compensation entries represent the **current state**, not an effective-dated history.
- There are no start/end dates on compensation records.
- Multiple entries can exist per employee to represent different pay components (salary + bonus + equity).

## Step 4: Fetch all employees (with pagination)


```typescript
type HrisEmployee = {
  id?: string;
  title?: string;
  employment_status?: 'ACTIVE' | 'INACTIVE';
  terminated_at?: string | null;
  employment_type?: string;
  gender?: string;
  compensation?: {
    type?: 'SALARY' | 'BONUS' | 'STOCK_OPTIONS' | 'EQUITY' | 'OTHER';
    amount?: number;
    currency?: string;
    frequency?: 'ONE_TIME' | 'DAY' | 'QUARTER' | 'YEAR' | 'HOUR' | 'MONTH' | 'WEEK';
  }[];
};

export async function fetchAllEmployees(
  connectionId: string,
  pageSize = 100
): Promise<HrisEmployee[]> {
  let offset = 0;
  const employees: HrisEmployee[] = [];

  while (true) {
    const page = await sdk.hris.listHrisEmployees({
      connectionId,
      limit: pageSize,
      offset,
      fields: 'id,title,employment_status,terminated_at,employment_type,gender,compensation'
    });

    if (!page || page.length === 0) break;

    employees.push(...page);
    offset += pageSize;
  }

  return employees;
}
```


## Step 5: Filter active employees


```typescript
export function filterActiveEmployees(
  employees: HrisEmployee[]
): HrisEmployee[] {
  return employees.filter(
    (e) => e.employment_status === 'ACTIVE' && !e.terminated_at
  );
}
```


## Step 6: Normalize compensation amounts (policy-dependent)


Compensation entries use different frequencies. To aggregate meaningfully, normalize amounts to a common time basis (for example, annualized).

> Note: The multipliers below are defaults. You should adjust them to match your organization's working-hour and working-day policies.

```typescript
const frequencyMultiplier: Record<string, number> = {
  YEAR: 1,
  QUARTER: 4,
  MONTH: 12,
  WEEK: 52,
  DAY: 260,   // policy assumption
  HOUR: 2080, // policy assumption
  ONE_TIME: 1,
};

function normalizeAmount(amount: number, frequency: string): number {
  return amount * (frequencyMultiplier[frequency] ?? 1);
}
```


## Step 7: Aggregate compensation per employee (currency-safe)


Instead of aggregating per compensation record, first compute **total compensation per employee**, grouped by currency.


```typescript
type EmployeeCompTotal = {
  employee_id: string;
  title?: string;
  employment_type?: string;
  gender?: string;
  totals_by_currency: Record<string, number>;
};

export function aggregatePerEmployee(
  employees: HrisEmployee[]
): EmployeeCompTotal[] {
  const results: EmployeeCompTotal[] = [];

  for (const e of employees) {
    if (!e.id) continue;

    const totals: Record<string, number> = {};

    for (const c of e.compensation ?? []) {
      if (c.amount == null || !c.currency || !c.frequency) continue;

      const normalized = normalizeAmount(c.amount, c.frequency);
      totals[c.currency] = (totals[c.currency] ?? 0) + normalized;
    }

    results.push({
      employee_id: e.id,
      title: e.title,
      employment_type: e.employment_type,
      gender: e.gender,
      totals_by_currency: totals,
    });
  }

  return results;
}
```


## Step 8: Compute totals and averages


### Total compensation (by currency)


```typescript
export function totalCompensationByCurrency(
  employees: EmployeeCompTotal[]
): Record<string, number> {
  const totals: Record<string, number> = {};

  for (const e of employees) {
    for (const [currency, amount] of Object.entries(e.totals_by_currency)) {
      totals[currency] = (totals[currency] ?? 0) + amount;
    }
  }

  return totals;
}
```


### Average pay by role (per employee)


```typescript
export function averagePayByRole(
  employees: EmployeeCompTotal[]
): Record<string, Record<string, number>> {
  const totals: Record<string, Record<string, number>> = {};
  const counts: Record<string, number> = {};

  for (const e of employees) {
    const role = e.title ?? 'Unknown';
    counts[role] = (counts[role] ?? 0) + 1;

    for (const [currency, amount] of Object.entries(e.totals_by_currency)) {
      totals[role] ??= {};
      totals[role][currency] = (totals[role][currency] ?? 0) + amount;
    }
  }

  const averages: Record<string, Record<string, number>> = {};
  for (const role of Object.keys(totals)) {
    averages[role] = {};
    for (const [currency, total] of Object.entries(totals[role])) {
      averages[role][currency] = total / counts[role];
    }
  }

  return averages;
}
```


### Optional: breakdown by demographic field


```typescript
export function breakdownByField(
  employees: EmployeeCompTotal[],
  field: 'gender' | 'employment_type'
): Record<string, Record<string, number>> {
  const totals: Record<string, Record<string, number>> = {};

  for (const e of employees) {
    const key = (e as any)[field] ?? 'Unknown';

    totals[key] ??= {};
    for (const [currency, amount] of Object.entries(e.totals_by_currency)) {
      totals[key][currency] = (totals[key][currency] ?? 0) + amount;
    }
  }

  return totals;
}
```


## Step 9: Putting it all together


```typescript
async function main() {
  const employees = await fetchAllEmployees(CONNECTION_HRIS!);
  const active = filterActiveEmployees(employees);
  const perEmployee = aggregatePerEmployee(active);

  console.log('Total compensation by currency:');
  console.log(totalCompensationByCurrency(perEmployee));

  console.log('Average pay by role:');
  console.log(averagePayByRole(perEmployee));

  console.log('Compensation by gender:');
  console.log(breakdownByField(perEmployee, 'gender'));
}

main().catch(console.error);
```


With Unified's HR & Directory API, compensation analysis becomes a straightforward data problem instead of a vendor-specific integration challenge. By relying on the normalized Employee model and its embedded compensation records, you can compute totals, averages, and demographic breakdowns across HRIS platforms using the same code path.


This approach provides:

- Real-time access to current compensation
- Currency-safe aggregation
- Explicit, policy-controlled normalization
- A clean foundation for FP&A, HR analytics, and pay-equity tooling

From here, common extensions include grouping by department or location, incorporating benefits and deductions for total-rewards analysis, or layering in historical tracking using HRIS-specific metadata when available.


→ [Start your 30-day free trial](https://app.unified.to/login)


→ [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)