How to Read Employee Payroll History with Unified's HR API
January 11, 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
connectionIdcreated via Unified's auth flow - A Unified API key
Step 1: Fetch employees
Employees are the anchor object for all HR data. All payroll records reference employees by ID.
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 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:
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.
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 periodpaid_at: payment dategross_amountnet_amountcurrencydetails[]: earnings, taxes, deductions
Joining payslips to employees
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.
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_athourscompensation[]entries:amountcurrencyfrequency(HOUR, DAY, ONE_TIME, etc.)
Joining timeshifts to employees
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
- Each record includes its own
- 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)
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:
- Fetching employees
- Joining payslips for pay-period payroll snapshots
- Optionally joining timeshifts for time-based compensation
- 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.