---
title: "How to Read Employee Payroll History with Unified's"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_read_employee_payroll_history_with_unified-icon.png
date: 2026-01-10T00:00:00.000Z
tag: Product
description: "Payroll analysis breaks when products rely on inferred compensation instead of recorded payroll events."
url: "https://unified.to/blog/how_to_read_employee_payroll_history_with_unified"
---

# How to Read Employee Payroll History with Unified's
------
_January 10, 2026_

Payroll analysis breaks when products rely on inferred compensation instead of recorded payroll events.


Across HR systems, 'pay history' is often implied rather than explicit. Employee records may show a current salary or hourly rate, but they rarely encode _when_ that compensation applied, _which period_ it covered, or _what was actually paid_ after deductions. Vendors expose this differently, and many don't expose it cleanly at all.


For a PM, this creates real constraints:

- Can payroll analytics be trusted across HR systems with different data models?
- Can historical pay be reconstructed without guessing past compensation?
- Can you explain exactly where a number came from during audits or disputes?

Many products solve this by storing snapshots, reconstructing history, or quietly falling back to current-state compensation fields. That approach works—until it doesn't, and correctness becomes hard to defend.


Unified's HR API takes a stricter stance. It does not invent payroll history or mutate employee records. Instead, it exposes _documented payroll artifacts_—payslips and timeshifts—when the underlying HR system supports them. These represent point-in-time payroll snapshots and time-window compensation records, respectively.


This guide shows how to assemble employee payroll history using those primitives only—reading payslips and timeshifts, joining them to employees, and aggregating results client-side—without vendor-specific integrations, undocumented fields, or assumed compensation history.


## Prerequisites

- Node.js 18+
- A Unified workspace
- An HR integration enabled for your customer
- A `connectionId` created via Unified's auth flow
- A [Unified API](/technology) key

## Step 1: Fetch employees


Employees are the anchor object for all HR data. All payroll records reference employees by ID.


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

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

const connectionId = 'YOUR_CONNECTION_ID';

const employees = await sdk.hris.listHrisEmployees({
  connectionId,
  limit: 100,
  offset: 0,
  sort: 'updated_at',
  order: 'asc',
});
```


### What this returns

- A **plain array** of `HrisEmployee`
- No wrapper object
- No cursor field

[Pagination](/docs/reference/pagination) is handled with `limit` and `offset`. You know you've reached the end when the number of returned records is less than your requested limit.


### Employee identifiers


Each employee has a canonical identifier:


```typescript
employee.id
```


All payroll objects reference this ID.


## Step 2: Fetch payroll history via payslips


Payslips are the primary source of **historical payroll data** in Unified.


```typescript
const payslips = await sdk.hris.listHrisPayslips({
  connectionId,
  limit: 100,
  offset: 0,
  user_id: employeeId, // optional filter
  sort: 'updated_at',
  order: 'asc',
});
```


### Payslip semantics


Each `HrisPayslip` represents a **point-in-time payroll snapshot**:

- `start_at` / `end_at`: pay period
- `paid_at`: payment date
- `gross_amount`
- `net_amount`
- `currency`
- `details[]`: earnings, taxes, deductions

### Joining payslips to employees


```typescript
payslip.user_id === employee.id
```


### Date filtering


Payslips **cannot** be filtered by pay-period dates server-side. To work with a date range:

- Fetch all payslips
- Filter client-side using `start_at` / `end_at`

## Step 3: Fetch time-based compensation via timeshifts (optional)


Some HR systems calculate payroll from worked hours rather than fixed salaries. For these, Unified exposes **timeshifts**.


```typescript
const timeshifts = await sdk.hris.listHrisTimeshifts({
  connectionId,
  limit: 100,
  offset: 0,
  start_gte: '2026-01-01T00:00:00Z',
  end_lt: '2026-02-01T00:00:00Z',
});
```


### Timeshift semantics


Each `HrisTimeshift` represents compensation for a time window:

- `start_at` / `end_at`
- `hours`
- `compensation[]` entries:
    - `amount`
    - `currency`
    - `frequency` (HOUR, DAY, ONE_TIME, etc.)

### Joining timeshifts to employees


```typescript
timeshift.employee_user_id === employee.id
```


Timeshifts support **true server-side date filtering** using `start_gte` and `end_lt`, but availability depends on the integration.


## Step 4: Understand what is _not_ historical


Unified also exposes `employee.compensation[]`, but:

- It has **no effective dates**
- No history semantics are documented
- It should be treated as **current-state compensation only**

For historical payroll analysis, always use **payslips** or **timeshifts**.


## Step 5: Aggregate payroll data client-side


Unified returns **raw numeric values**. All aggregation is your responsibility.


### Key constraints

- **No currency normalization**
    - Each record includes its own `currency`
    - Aggregate only within the same currency
- **No frequency normalization**
    - Timeshift compensation may be hourly, yearly, or one-time
    - Convert units explicitly if needed
- **No server-side aggregation**
    - No totals, averages, or group-by endpoints

### Example aggregation logic (conceptual)


```typescript
const totalsByPeriod = {};

for (const payslip of payslips) {
  const periodKey = `${payslip.start_at}-${payslip.end_at}`;
  const currency = payslip.currency;

  if (!totalsByPeriod[periodKey]) {
    totalsByPeriod[periodKey] = { currency, gross: 0, net: 0 };
  }

  totalsByPeriod[periodKey].gross += payslip.gross_amount ?? 0;
  totalsByPeriod[periodKey].net += payslip.net_amount ?? 0;
}
```


## Error handling and unsupported features


Unified uses standard HTTP status codes.


### Important behavior

- **501 Not Implemented**
    - The integration does not support that feature (e.g. payslips or timeshifts)
    - The connection itself is still valid
- **401 Unauthorized**
    - Connection is broken and must be recreated
- **403 Forbidden**
    - Missing scopes or permissions

Always check the **Feature Support** tab for an integration in `app.unified.to` to confirm object availability.


## Summary


With Unified's HR API, you can reliably read employee payroll history by:

1. Fetching employees
2. Joining **payslips** for pay-period payroll snapshots
3. Optionally joining **timeshifts** for time-based compensation
4. Aggregating results client-side with explicit currency and frequency handling

This approach avoids vendor-specific APIs, preserves correctness, and scales cleanly across HR systems—without Unified storing or mutating payroll data.


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


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