Summary and recommendation
The Zenefits Core API is a REST API authenticated via OAuth 2.0 Authorization Code flow, with tokens scoped per company tenant - a separate OAuth flow is required for each Zenefits organization.
The base URL is https://api.zenefits.com/core, and all requests require a Bearer token in the Authorization header.
Pagination uses limit/offset with a default page size of 20 and a maximum of 100;
the response includes a next_url field for pre-built next-page navigation.
No official SDK is published;
all integrations require raw HTTP calls.
Rate-limit values and headers are not publicly documented - contact Zenefits developer support for current thresholds before building high-frequency polling logic.
For identity graph construction, the People endpoint returns nested department and manager objects (referenced by Zenefits ID), enabling traversal of the full reporting hierarchy.
Employment records are stored separately from person records and must be joined on person.id to correlate hire date, employment type, and title into a unified identity record.
Sensitive fields such as date_of_birth may require additional OAuth scopes or admin-level app credentials.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (Authorization Code flow) |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Growth (formerly referred to as Pro in some sources) |
Authentication
Auth method: OAuth 2.0 (Authorization Code flow)
Setup steps
- Register an application in the Zenefits Developer Portal to obtain a client_id and client_secret.
- Redirect the user to https://secure.zenefits.com/oauth2/platform-authorize/ with response_type=code, client_id, redirect_uri, and scope parameters.
- Exchange the returned authorization code for an access_token and refresh_token via POST to https://secure.zenefits.com/oauth2/token/.
- Include the access token in all API requests as a Bearer token in the Authorization header.
- Refresh the access token using the refresh_token grant before expiry.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| people:read | Read employee profile data including personal info, employment status, and contact details. | GET /people, GET /people/{id} |
| people:write | Create or update employee records. | POST /people, PATCH /people/{id} |
| employments:read | Read employment details such as hire date, department, and title. | GET /employments |
| departments:read | Read department structure. | GET /departments |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique Zenefits person identifier. | system-generated | immutable | Used as path param in all person-specific calls. |
| first_name | string | Employee's legal first name. | required | optional | |
| last_name | string | Employee's legal last name. | required | optional | |
| preferred_name | string | Preferred display name. | optional | optional | |
| work_email | string | Primary work email address. | required | optional | Used as login identifier. |
| personal_email | string | Personal email address. | optional | optional | |
| status | string | Employment status (active, inactive, etc.). | system-set | optional | Reflects hire/termination state. |
| department | object | Nested object with department id and name. | optional | optional | References /departments resource. |
| manager | object | Nested object referencing manager's person id. | optional | optional | |
| title | string | Job title. | optional | optional | |
| start_date | date | Employment start (hire) date (YYYY-MM-DD). | required | optional | |
| termination_date | date | Date of termination if applicable. | optional | optional | Set when offboarding. |
| location | object | Work location object. | optional | optional | |
| phone | string | Work phone number. | optional | optional | |
| date_of_birth | date | Employee date of birth. | optional | optional | Sensitive field; scope-gated. |
| gender | string | Gender identity. | optional | optional | |
| type | string | Employment type (employee, contractor). | optional | optional | |
| created_at | datetime | Record creation timestamp (ISO 8601). | system-generated | immutable | |
| updated_at | datetime | Last modification timestamp (ISO 8601). | system-generated | system-updated |
Core endpoints
List all people
- Method: GET
- URL:
https://api.zenefits.com/core/people - Watch out for: Inactive/terminated employees are included by default; filter by status=active if needed.
Request example
GET /core/people?limit=20&offset=0
Authorization: Bearer {access_token}
Response example
{
"data": {
"url": "/core/people",
"data": [{"id":"abc123","first_name":"Jane","last_name":"Doe","work_email":"jane@co.com","status":"active"}],
"next_url": "/core/people?limit=20&offset=20"
}
}
Get a single person
- Method: GET
- URL:
https://api.zenefits.com/core/people/{id} - Watch out for: Returns 404 if the person ID does not exist or the token lacks people:read scope.
Request example
GET /core/people/abc123
Authorization: Bearer {access_token}
Response example
{
"data": {
"id": "abc123",
"first_name": "Jane",
"last_name": "Doe",
"work_email": "jane@co.com",
"status": "active",
"title": "Engineer"
}
}
Update a person
- Method: PATCH
- URL:
https://api.zenefits.com/core/people/{id} - Watch out for: Not all fields are writable via API; some HR fields (e.g., compensation) require Zenefits UI or separate payroll endpoints.
Request example
PATCH /core/people/abc123
Authorization: Bearer {access_token}
Content-Type: application/json
{"title": "Senior Engineer"}
Response example
{
"data": {
"id": "abc123",
"title": "Senior Engineer",
"updated_at": "2024-06-01T12:00:00Z"
}
}
List employments
- Method: GET
- URL:
https://api.zenefits.com/core/employments - Watch out for: Employment records are separate from person records; join on person.id to correlate.
Request example
GET /core/employments?limit=20
Authorization: Bearer {access_token}
Response example
{
"data": {
"data": [{"id":"emp1","person":{"id":"abc123"},"hire_date":"2022-01-10","employment_type":"employee"}]
}
}
List departments
- Method: GET
- URL:
https://api.zenefits.com/core/departments - Watch out for: Department IDs are required when assigning employees to departments via PATCH /people.
Request example
GET /core/departments
Authorization: Bearer {access_token}
Response example
{
"data": {
"data": [{"id":"dept1","name":"Engineering"}]
}
}
List company locations
- Method: GET
- URL:
https://api.zenefits.com/core/locations - Watch out for: Location IDs are needed when setting work location on a person record.
Request example
GET /core/locations
Authorization: Bearer {access_token}
Response example
{
"data": {
"data": [{"id":"loc1","name":"HQ","city":"San Francisco"}]
}
}
Get company info
- Method: GET
- URL:
https://api.zenefits.com/core/companies/{id} - Watch out for: Company ID is returned during OAuth token exchange; store it for subsequent calls.
Request example
GET /core/companies/co123
Authorization: Bearer {access_token}
Response example
{
"data": {
"id": "co123",
"name": "Acme Corp",
"ein": "12-3456789"
}
}
Rate limits, pagination, and events
Rate limits: Zenefits developer docs do not publicly specify exact rate-limit numbers or tiers.
Rate-limit headers: Unknown
Retry-After header: Unknown
Rate-limit notes: No explicit rate-limit values, headers, or Retry-After behavior documented in official sources. Contact Zenefits developer support for current limits.
Pagination method: offset
Default page size: 20
Max page size: 100
Pagination pointer: limit / offset
Webhooks available: No
Webhook notes: Zenefits developer documentation does not describe a native outbound webhook system for real-time event notifications.
Alternative event strategy: Poll the People API periodically using the updated_at field to detect changes, or use the Okta SCIM integration for provisioning-event-driven flows.
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Growth (formerly referred to as Pro in some sources)
Endpoint: Provisioned by Okta (or OneLogin) connector; Zenefits does not publish a standalone SCIM base URL. The endpoint is generated within the IdP integration setup.
Supported operations: Create Users, Update User Attributes, Deactivate Users, Group Push (via Okta), Schema Discovery
Limitations:
- SCIM endpoint is IdP-connector-specific (Okta, OneLogin); no generic SCIM URL is published by Zenefits.
- SSO must be configured before SCIM provisioning can be enabled.
- Available on Growth plan and above; not available on Essentials.
- Google Workspace SCIM provisioning is not supported per available documentation.
- Password sync is not supported via SCIM; authentication is handled by the IdP SSO.
Common scenarios
Three integration patterns are well-supported by the available API surface.
First, syncing active employees to an external directory: authenticate, paginate GET /core/people with status=active, then enrich each record via GET /core/employments filtered by person.id, and upsert using work_email as the unique key
omitting the status filter will silently include terminated employees.
Second, automated onboarding via Okta SCIM: requires the Growth plan, a fully configured SAML SSO connection, and SCIM enabled in the Okta app catalog;
Okta generates the SCIM endpoint and bearer token, and provisioning errors will occur if SSO is not validated first.
Third, updating job title and department after an internal transfer: resolve the department ID via GET /core/departments, resolve the person ID via work_email lookup, then PATCH /core/people/{id} with the new title and department object
department must be referenced by its Zenefits ID, not by name string, or the request returns a 400.
Sync active employees to an external directory
- Authenticate via OAuth 2.0 Authorization Code flow to obtain access_token.
- GET /core/people?limit=100&offset=0 and filter results where status=active.
- Paginate using next_url until no further pages are returned.
- For each person, GET /core/employments filtered by person.id to retrieve hire_date and employment_type.
- Upsert records into the external directory using work_email as the unique key.
Watch out for: Inactive/terminated employees are returned by default; omitting the status filter will include them in the sync.
Automate employee onboarding via Okta SCIM
- Ensure Zenefits account is on the Growth plan or above.
- Configure SAML SSO between Okta and Zenefits via the Okta application catalog.
- Enable SCIM provisioning in the Okta Zenefits app; Okta generates the SCIM endpoint and bearer token.
- Assign the Okta application to a user or group; Okta pushes a SCIM Create User request to Zenefits.
- Verify the new employee record appears in Zenefits with correct attributes.
Watch out for: SSO must be fully configured and tested before enabling SCIM; enabling SCIM without SSO will result in provisioning errors.
Update job title and department after an internal transfer
- GET /core/departments to retrieve the target department's id.
- GET /core/people?work_email=jane@co.com to resolve the person's Zenefits id.
- PATCH /core/people/{id} with body {"title": "Staff Engineer", "department": {"id": "dept_new"}}.
- Confirm the 200 response contains the updated title and department fields.
Watch out for: Department must be referenced by its Zenefits id, not by name string; an invalid id returns a 400 error.
Why building this yourself is a trap
The Zenefits API has several non-obvious failure modes worth flagging before building production integrations. The developer portal (developers.zenefits.com) has had periods of limited maintenance; verify endpoint availability and documentation accuracy against live behavior before committing to an integration design.
Webhooks are not available - there is no native outbound event system, so change detection requires periodic polling against the People API using the updated_at field, or delegation to the Okta SCIM integration for provisioning-event-driven flows. SCIM provisioning is IdP-connector-specific (Okta or OneLogin only);
no generic SCIM base URL is published by Zenefits, and Google Workspace SCIM is not supported. Compensation and some other sensitive HR fields are not writable via the Core API and require the Zenefits UI or separate payroll endpoints, which limits the scope of fully automated lifecycle management.
Automate Zenefits workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.