How do I identify and match employees from the HRIS API?
When integrating with an HRIS connector, you'll often need to match an Apideck employee record back to the underlying system - for example, to look the same person up against the vendor's native API, or to reconcile against records you've already stored on your side. This article explains which identifier field to use.
Start with the id field
For every supported HRIS connector, the id returned on /hris/employees is the native employee identifier from the underlying vendor's API. There's no separate identifier hidden behind the scenes, and you don't need raw=true or a custom field mapping to access it.
For example, in a BambooHR response:
{ "id": "99", "firstName": "Michael", ... }
The "99" is the same ID BambooHR uses internally - the value you'd get from GET /v1/employees/99 directly against BambooHR.
Lucca HR works the same way: their numeric user ID (e.g. 416) is returned as the unified id (as a string, "416").
You can safely store this value and use it as the link between Apideck and the underlying system.
When to use employee_number
Some vendors expose two distinct identifiers per employee:
A database ID (the vendor's internal primary key), and
A separate HR-facing employee number (the value shown in the vendor's UI, on payslips, or in CSV exports).
For these connectors, the database ID is returned as id, and the HR-facing number is returned separately as employee_number.
Use employee_number when your system stores the HR-facing number rather than the database ID. Connectors where this distinction matters include BambooHR, Lucca HR, AlexisHR, BreatheHR, Cascade HR, CIPHR, Factorial, Folks HR, Gusto, HiBob, HR Works, Humaans, Keka, Loket, Namely, NetSuite, Nmbrs, Okta, Paychex, People HR, Sage HR, Workday, and Zoho People, among others.
For connectors where the vendor exposes only one identifier, employee_number may be empty - in that case, id is the only value you need.
When to use downstream_id
downstream_id is reserved for connectors where the vendor exposes a second native identifier beyond what fits in id and employee_number. Today, this applies to a small number of HRIS connectors:
Fourth -
downstream_idreturns thePayrollNumber.Acerta -
downstream_idreturns theemployeeId, whileidcarries theagreementId.Remote -
downstream_idmirrors the value ofid, so you can match on either.
For all other HRIS connectors, downstream_id is empty. An empty downstream_id does not mean the native vendor ID is hidden - it means the vendor has only one stable identifier per employee, and that identifier is already in id.
Which field should I match on?
A quick decision guide:
If your system stores… | Match on |
|---|---|
The identifier the vendor uses internally (e.g. BambooHR's |
|
The HR-facing employee number (e.g. BambooHR's |
|
A secondary native identifier (Fourth |
|
An email or display name |
|
Frequently asked questions
Why does the id value look different from what I see in my HRIS dashboard? Most HRIS dashboards display the human-readable employee number, not the database primary key. The database primary key is what we surface as id. The number you see in the dashboard is usually surfaced separately as employee_number - try matching on that instead.
Do I need raw=true to retrieve the native ID? No. raw=true returns the unmodified vendor payload alongside the unified response, but it isn't required to access the native ID. For HRIS employees, id already carries the native vendor identifier.
Why is downstream_id empty for my connector? Because the vendor only exposes one stable identifier per employee, and we already return it as id. downstream_id is only populated when the vendor has a second distinct identifier worth surfacing.
If anything still doesn't line up with what you're seeing in a response, please reach out - we're happy to take a closer look at the specific connector and resource you're working with.
