---
title: "How to Analyze Employee Compensation Across HRIS Platforms with"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_analyze_employee_compensation_across_hris_platforms_with-icon.png
date: 2026-01-10T00:00:00.000Z
updated: 2026-10-21T01:08:04.544Z
readingTime: 4
tag: Product
description: "Unified.to's HR & Directory API returns each employee's current pay as a list of compensation entries (type, amount, currency, frequency) on supported..."
url: "https://unified.to/blog/how_to_analyze_employee_compensation_across_hris_platforms_with"
---

# How to Analyze Employee Compensation Across HRIS Platforms with

[Unified.to](http://unified.to/)'s [HR & Directory API](/hris) returns each employee's current pay as a list of compensation entries (type, amount, currency, frequency) on supported integrations, including Workday, BambooHR, ADP Workforce Now, SAP SuccessFactors and UKG Pro HCM (as of 19 October 2026). This guide is for product and engineering teams building compensation analytics, pay-equity or FP&A features: it shows how to read those entries, annualize them under your own rules, and total them per employee and per role without one integration per HRIS.


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, each vendor shapes it differently.


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.to](http://unified.to/)'s HR & Directory API returns employees and their **current compensation** in one Employee model. Compensation entries come back on supported integrations only, such as Workday, BambooHR, ADP Workforce Now, Ceridian Dayforce, SAP SuccessFactors and UKG Pro HCM; HiBob, Personio and Deel return employees without them (as of 19 October 2026). Check each integration's Feature Support tab before you promise the feature. Compensation components, employment status, and core demographics are structured consistently, while leaving normalization policy decisions (like annualization or inclusion rules) explicitly in your control.


## 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: Where does compensation live in [Unified.to](http://unified.to/)'s Employee model?


On the **Employee** object. There's no separate compensation endpoint: current pay sits in each employee's `compensation[]` array. Pay actually paid lives elsewhere, in payslips (see [How to Read Employee Payroll History](https://unified.to/blog/how_to_read_employee_payroll_history_with_unified)), and benefits and deductions have their own objects.


### 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`


    (Returned on some integrations only, e.g. gender and date of birth on Workday, BambooHR, HiBob and SAP SuccessFactors; pronouns on very few.)


### Compensation entries


Each item in `compensation[]` includes:

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

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.to](http://unified.to/)'s HR & Directory API, the same code computes totals, averages and demographic breakdowns on every integration that returns compensation entries; on the rest, your module should show that pay data isn't available rather than a zero.


This approach provides:

- Current compensation, read from the HRIS at request time (no customer records at rest on [Unified.to](http://unified.to/))
- Currency-safe aggregation
- Explicit, policy-controlled normalization
- A clean foundation for FP&A, HR analytics, and pay-equity tooling

From here, common extensions are grouping by department or location (`groups`, `locations`), adding benefits and deductions for total-rewards views on integrations that return them, and reading pay history from payslips.


Related: [How to Read Employee Payroll History](https://unified.to/blog/how_to_read_employee_payroll_history_with_unified) · [HR API Integration](https://unified.to/blog/hr_api_integration_real_time_employee_data_directories_and_workforce_automation) · [Employee data model](https://docs.unified.to/hris/employee/model)


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


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