Summary and recommendation
ADP exposes worker data via a REST API at api.adp.com, authenticated with OAuth 2.0 (client_credentials or authorization_code grant) plus mandatory mutual TLS (mTLS) on every request. The API follows an event-driven model: worker creation, termination, and most field-level updates are submitted as POST events to /events/hr/v1/worker.* endpoints rather than direct resource mutations.
All HR events are asynchronous - the API returns an eventID with status 'submitted' and the change is not immediately reflected on re-query. There is no native inbound SCIM endpoint; ADP has no /Users or /Groups SCIM 2.0 surface on api.adp.com.
Integrating ADP into an MCP server with ~100 deep IT/identity integrations requires handling mTLS certificate lifecycle, event-driven write patterns, a polling-based event queue (not push webhooks), and product-tier-dependent endpoint availability across RUN, Workforce Now, and Vantage HCM.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (client_credentials grant for server-to-server; authorization_code grant for user-context flows) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: OAuth 2.0 (client_credentials grant for server-to-server; authorization_code grant for user-context flows)
Setup steps
- Register as an ADP developer at developers.adp.com and create an application.
- Select the appropriate API product (e.g., Workers v2) and request access through the ADP marketplace or your ADP account team.
- Receive client_id, client_secret, and mutual TLS (mTLS) certificate from ADP.
- Configure mTLS: generate a CSR, submit to ADP, receive signed certificate, and configure your HTTP client to present it on every request.
- Exchange client credentials for an access token: POST to https://accounts.adp.com/auth/oauth/v2/token with grant_type=client_credentials.
- Use the returned Bearer token in the Authorization header for all API calls. Tokens expire in 3600 seconds; refresh by repeating the token request.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| workers | Read worker (employee) records | GET /hr/v2/workers |
| workers:write | Create or update worker records (onboarding, personal info changes) | POST/PATCH worker endpoints |
| staffing | Access staffing and position data | Position and assignment endpoints |
| events | Subscribe to and retrieve HR events (hires, terminations, changes) | Event notification endpoints |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| associateOID | string | Unique ADP-assigned identifier for the worker | system-assigned | immutable | Primary key for all worker API operations |
| workerID.idValue | string | Client-assigned employee ID | required | optional | Typically the HR employee number |
| person.legalName.givenName | string | Legal first name | required | optional | |
| person.legalName.familyName1 | string | Legal last name | required | optional | |
| person.birthDate | string (ISO 8601 date) | Date of birth | optional | optional | Format: YYYY-MM-DD |
| person.genderCode.codeValue | string | Gender code | optional | optional | |
| person.communication.emails[].emailUri | string | Work or personal email address | optional | optional | Array; use itemID to target specific entry on update |
| person.communication.mobiles[].formattedNumber | string | Mobile phone number | optional | optional | |
| workerDates.originalHireDate | string (ISO 8601 date) | Original hire date | required | optional | |
| workerDates.terminationDate | string (ISO 8601 date) | Termination date | not applicable | set via termination event | Set through HR event, not direct PATCH |
| workerStatus.statusCode.codeValue | string | Active/Inactive/Terminated status | system-assigned | via events | Read-only via direct field; changed through lifecycle events |
| workAssignments[].positionID | string | Position identifier for the worker's assignment | optional | optional | |
| workAssignments[].jobTitle | string | Job title | optional | optional | |
| workAssignments[].hireDate | string (ISO 8601 date) | Assignment hire date | optional | optional | |
| workAssignments[].workerTypeCode.codeValue | string | Employee type (e.g., REG, TMP) | optional | optional | |
| workAssignments[].homeOrganizationalUnits[].nameCode.codeValue | string | Department or cost center code | optional | optional | |
| workAssignments[].reportsTo[].associateOID | string | Manager's associateOID | optional | optional |
Core endpoints
List Workers
- Method: GET
- URL:
https://api.adp.com/hr/v2/workers - Watch out for: Returns all active workers by default. Use $filter to narrow results. Large orgs may require multiple paginated calls.
Request example
GET /hr/v2/workers?$top=25&$skip=0
Authorization: Bearer {token}
Response example
{
"workers": [
{
"associateOID": "G3349XXXXXXXXXXX",
"workerID": {"idValue": "10001"},
"person": {"legalName": {"givenName": "Jane", "familyName1": "Doe"}},
"workerStatus": {"statusCode": {"codeValue": "Active"}}
}
]
}
Get Single Worker
- Method: GET
- URL:
https://api.adp.com/hr/v2/workers/{associateOID} - Watch out for: Response wraps single worker in a 'workers' array. Always index [0].
Request example
GET /hr/v2/workers/G3349XXXXXXXXXXX
Authorization: Bearer {token}
Response example
{
"workers": [{
"associateOID": "G3349XXXXXXXXXXX",
"person": {
"legalName": {"givenName": "Jane", "familyName1": "Doe"},
"communication": {"emails": [{"emailUri": "jane.doe@company.com"}]}
}
}]
}
Hire New Worker (Onboarding Event)
- Method: POST
- URL:
https://api.adp.com/events/hr/v1/worker.hire - Watch out for: Worker creation is event-driven, not a direct POST to /workers. The event is asynchronous; poll event status or use event notifications to confirm completion.
Request example
POST /events/hr/v1/worker.hire
Authorization: Bearer {token}
Content-Type: application/json
{
"events": [{
"data": {"transform": {
"worker": {
"person": {"legalName": {"givenName": "John", "familyName1": "Smith"}},
"workerDates": {"originalHireDate": "2024-03-01"}
}
}}
}]
}
Response example
{
"events": [{
"eventID": "evt-XXXXXXXX",
"serviceCategoryCode": {"codeValue": "HR"},
"eventStatusCode": {"codeValue": "submitted"}
}]
}
Terminate Worker
- Method: POST
- URL:
https://api.adp.com/events/hr/v1/worker.terminate - Watch out for: Termination is also event-driven and asynchronous. Confirm via event status polling.
Request example
POST /events/hr/v1/worker.terminate
Authorization: Bearer {token}
Content-Type: application/json
{
"events": [{
"data": {"transform": {
"worker": {
"associateOID": "G3349XXXXXXXXXXX",
"workerDates": {"terminationDate": "2024-06-30"}
}
}}
}]
}
Response example
{
"events": [{
"eventID": "evt-YYYYYYYY",
"eventStatusCode": {"codeValue": "submitted"}
}]
}
Update Worker Personal Info
- Method: POST
- URL:
https://api.adp.com/events/hr/v1/worker.personal-communication.email.change - Watch out for: Each type of change (email, name, address, job title) has its own event endpoint. There is no single generic PATCH worker endpoint.
Request example
POST /events/hr/v1/worker.personal-communication.email.change
Authorization: Bearer {token}
Content-Type: application/json
{
"events": [{
"data": {"transform": {
"worker": {
"associateOID": "G3349XXXXXXXXXXX",
"person": {"communication": {"emails": [{"emailUri": "new@company.com"}]}}
}
}}
}]
}
Response example
{
"events": [{
"eventID": "evt-ZZZZZZZZ",
"eventStatusCode": {"codeValue": "submitted"}
}]
}
Get HR Events (Change Feed)
- Method: GET
- URL:
https://api.adp.com/core/v1/event-notification-messages - Watch out for: Messages must be acknowledged (DELETE) after processing or they will be re-delivered. This is a polling-based event queue, not push webhooks.
Request example
GET /core/v1/event-notification-messages
Authorization: Bearer {token}
Response example
{
"eventNotificationMessages": [{
"eventNotificationMessageID": {"idValue": "msg-001"},
"eventNameCode": {"codeValue": "worker.hire"},
"data": {"output": {"worker": {"associateOID": "G3349XXXXXXXXXXX"}}}
}]
}
Acknowledge (Delete) Event Notification
- Method: DELETE
- URL:
https://api.adp.com/core/v1/event-notification-messages/{adpMsgId} - Watch out for: Must be called after processing each event message to remove it from the queue. Failure to acknowledge causes duplicate processing.
Request example
DELETE /core/v1/event-notification-messages/msg-001
Authorization: Bearer {token}
Response example
HTTP 204 No Content
Get Worker Change Events (Audit)
- Method: GET
- URL:
https://api.adp.com/hr/v2/workers/{associateOID}/change-events - Watch out for: Availability of this endpoint depends on the specific ADP product (Workforce Now vs. Vantage HCM) and API subscription level.
Request example
GET /hr/v2/workers/G3349XXXXXXXXXXX/change-events
Authorization: Bearer {token}
Response example
{
"changeEvents": [{
"changeEventID": "ce-001",
"changeDateTime": "2024-05-01T10:00:00Z",
"changeTypeCode": {"codeValue": "worker.hire"}
}]
}
Rate limits, pagination, and events
- Rate limits: ADP does not publicly publish specific numeric rate limits. Limits are enforced per client application and vary by API product and agreement. Exceeding limits returns HTTP 429.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: ADP returns HTTP 429 when rate limits are exceeded. Retry-After header is included. Specific limits are negotiated per client and not published in public docs.
- Pagination method: offset
- Default page size: 25
- Max page size: 100
- Pagination pointer: $top / $skip (OData-style)
| Plan | Limit | Concurrent |
|---|---|---|
| Standard API access | Not publicly documented; enforced per agreement | 0 |
- Webhooks available: No
- Webhook notes: ADP does not offer traditional push webhooks. Instead, ADP provides an event notification message queue (polling model) at /core/v1/event-notification-messages. Consumers poll this endpoint to retrieve HR events (hires, terminations, changes) and must DELETE each message after processing.
- Alternative event strategy: Poll GET /core/v1/event-notification-messages on a scheduled interval and DELETE processed messages.
- Webhook events: worker.hire, worker.terminate, worker.rehire, worker.personal-communication.email.change, worker.legal-name.change, worker.work-assignment.home-organizational-unit.change, worker.work-assignment.job-title.change
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- ADP has no native inbound SCIM endpoint; it functions as an HR source system, not a SCIM target.
- SCIM-based provisioning FROM ADP to other apps requires a third-party bridge such as Aquera.
- Okta, Entra ID, and Google Workspace integrations with ADP use ADP's proprietary API or Aquera, not SCIM.
- No publicly documented SCIM 2.0 /Users or /Groups endpoint exists on api.adp.com.
Common scenarios
Three integration scenarios cover the majority of identity lifecycle use cases. First, new hire provisioning: POST to /events/hr/v1/worker.
hire with legal name, hire date, and work assignment. capture the returned eventID.
poll for 'complete' status before treating the associateOID as stable for downstream provisioning - do not assume the worker is immediately queryable after the POST returns 200. Second, change sync: poll GET /core/v1/event-notification-messages on a scheduled interval, filter for relevant event types (worker.
hire, worker. terminate, worker.
legal-name. change, etc.
), retrieve the full worker record via GET /hr/v2/workers/{associateOID}, apply changes downstream, then DELETE each processed message - unacknowledged messages are re-delivered indefinitely, so idempotent processing logic is required. Third, termination and deprovisioning: receive or trigger a worker.
terminate event, poll to 'complete', then use the associateOID to deactivate the user in connected systems; ADP does not natively push deprovisioning to other apps - an integration layer must translate the termination event into downstream access removal.
Onboard a new employee
- Obtain OAuth 2.0 access token via POST to https://accounts.adp.com/auth/oauth/v2/token using client_credentials grant with mTLS certificate.
- POST to /events/hr/v1/worker.hire with the new worker's legal name, hire date, and work assignment details in the event transform payload.
- Capture the returned eventID from the response.
- Poll GET /events/hr/v1/worker.hire/{eventID} or GET /core/v1/event-notification-messages to confirm the event status changes from 'submitted' to 'complete'.
- Once complete, retrieve the new worker's associateOID from the event output and store it for future API calls.
- Optionally call GET /hr/v2/workers/{associateOID} to verify the worker record was created correctly.
Watch out for: The hire event is asynchronous. Do not assume the worker is immediately queryable after the POST returns 200. Poll for completion before downstream provisioning.
Sync employee changes to downstream systems
- On a scheduled interval, poll GET /core/v1/event-notification-messages with a valid Bearer token and mTLS certificate.
- Iterate through returned eventNotificationMessages, filtering for relevant event types (worker.hire, worker.terminate, worker.legal-name.change, etc.).
- For each event, extract the associateOID from the event data output.
- Call GET /hr/v2/workers/{associateOID} to retrieve the full updated worker record.
- Apply the changes to the downstream system (e.g., update directory, deprovision access).
- DELETE /core/v1/event-notification-messages/{adpMsgId} for each processed message to acknowledge receipt.
Watch out for: Failure to DELETE processed messages causes them to be re-delivered indefinitely. Implement idempotent processing logic in the downstream system.
Terminate an employee and deprovision access
- Receive a termination event from the event notification queue (event type: worker.terminate) or trigger termination via POST to /events/hr/v1/worker.terminate.
- If triggering via API: POST to /events/hr/v1/worker.terminate with the worker's associateOID and terminationDate in the transform payload.
- Poll the event status until 'complete'.
- Use the associateOID to identify the user in downstream systems (IdP, SaaS apps).
- Deprovision or deactivate the user in connected systems. ADP itself does not push deprovisioning to other apps; this must be handled by the integration layer or an IdP like Okta/Entra.
- Acknowledge the event notification message via DELETE.
Watch out for: ADP is the source of truth for employment status but does not natively deprovision access in other systems. An integration layer (Okta, Entra, Aquera, or custom) must translate the ADP termination event into downstream deprovisioning actions.
Why building this yourself is a trap
The ADP API surface carries several non-obvious constraints that create integration debt. mTLS is mandatory on every request - not just token exchange - requiring certificate lifecycle management that standard OAuth client libraries do not handle by default.
Each field-level change type has its own dedicated event endpoint (e.g., worker.personal-communication.email.change, worker.legal-name.change); there is no generic PATCH on /workers, so developers must maintain a mapping of field changes to correct event types.
API access requires formal approval through the ADP Marketplace or direct account team engagement - self-service credential generation is not available, and the specific endpoints accessible depend on the ADP product tier and API subscription purchased.
Rate limits are not publicly documented and are negotiated per client; HTTP 429 with a Retry-After header is the only signal. Pagination uses OData-style $top/$skip with a maximum of 100 records per page, requiring iterative calls for large organizations.
Finally, the event notification queue is a polling model, not push webhooks - teams expecting webhook-based triggers must re-architect around scheduled polling and explicit message acknowledgment.
Automate ADP workflows without one-off scripts
Stitchflow builds and maintains identity workflows for your exact setup. We cover every app, including the ones without APIs, and run deterministic trigger-to-report workflows with human approvals where they matter.