Connect AI agents to Workday (HCM, Financials, Recruiting) using REST API, Workday Query Language (WQL), and Report-as-a-Service (RaaS).
Scanned 6/6/2026
Install via CLI
openskills install frank-luongt/faos-skills-marketplace<!-- AUTO-GENERATED by export-skills.py — DO NOT EDIT -->
---
name: workday-hcm
description: Workday HCM, Financials, and Recruiting integration patterns. Use when building AI agents that query or manage employee data, job requisitions, financial transactions, or workforce analytics via Workday REST API, WQL, and RaaS.
tags: [workday, hcm, hr, recruiting, financials]
---
# Workday HCM Integration
Connect AI agents to Workday (HCM, Financials, Recruiting) using REST API, Workday Query Language (WQL), and Report-as-a-Service (RaaS).
## When to Use
- Building AI agents that query employee data, org charts, or workforce analytics
- Integrating with Workday Recruiting (requisitions, candidates, applications)
- Accessing Workday Financials (ledger accounts, journal entries, supplier invoices)
- Using WQL for complex cross-domain queries
- Consuming Workday custom reports via RaaS endpoints
- Triggering Workday Orchestrations from external AI agents
## API Surface
| Method | URL Pattern | Use Case |
|---|---|---|
| **REST API** | `/ccx/api/v1/{tenant}/{resource}` | CRUD on workers, orgs, requisitions, financials |
| **WQL** | `/ccx/api/wql/v1/{tenant}` | SQL-like queries across domains |
| **RaaS** | `/ccx/service/customreport2/{tenant}/{owner}/{report}` | Custom reports as REST endpoints |
| **Orchestrations** | `/ccx/api/v1/{tenant}/orchestrations/{id}/run` | Trigger workflows |
| **OAuth Token** | `/ccx/oauth2/{tenant}/token` | Authentication |
## Key Resources
| Domain | Resources |
|---|---|
| **HCM** | Workers, Organizations, Job Profiles, Positions, Supervisory Orgs |
| **Recruiting** | Job Requisitions, Job Applications, Candidates |
| **Financials** | Ledger Accounts, Journal Entries, Suppliers, Customer Invoices |
| **Time & Absence** | Time Entries, Time Off Requests, Absence Types |
| **Compensation** | Compensation Plans, Compensation Reviews |
| **Talent** | Goals, Performance Reviews, Development Plans, Skills |
## Patterns
### 1. OAuth 2.0 Authentication
```python
import requests
from datetime import datetime, timedelta
class WorkdayClient:
"""Workday REST API client with OAuth 2.0 refresh token flow."""
def __init__(self, tenant: str, host: str, client_id: str,
client_secret: str, refresh_token: str):
self.tenant = tenant
self.base_url = f"https://{host}/ccx"
self.token_url = f"{self.base_url}/oauth2/{tenant}/token"
self.api_base = f"{self.base_url}/api/v1/{tenant}"
self.wql_url = f"{self.base_url}/api/wql/v1/{tenant}"
self.raas_url = f"{self.base_url}/service/customreport2/{tenant}"
self.client_id = client_id
self.client_secret = client_secret
self.refresh_token = refresh_token
self._token = None
self._token_expiry = None
def _get_token(self) -> str:
if self._token and self._token_expiry and datetime.utcnow() < self._token_expiry:
return self._token
resp = requests.post(self.token_url, data={
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
"client_id": self.client_id,
"client_secret": self.client_secret,
})
resp.raise_for_status()
data = resp.json()
self._token = data["access_token"]
self._token_expiry = datetime.utcnow() + timedelta(seconds=data.get("expires_in", 3600) - 120)
return self._token
@property
def _headers(self) -> dict:
return {
"Authorization": f"Bearer {self._get_token()}",
"Content-Type": "application/json",
"Accept": "application/json",
}
def get(self, path: str, params: dict = None) -> dict:
resp = requests.get(f"{self.api_base}/{path}", headers=self._headers, params=params)
if resp.status_code == 429:
import time
time.sleep(10)
resp = requests.get(f"{self.api_base}/{path}", headers=self._headers, params=params)
resp.raise_for_status()
return resp.json()
def post(self, path: str, data: dict) -> dict:
resp = requests.post(f"{self.api_base}/{path}", headers=self._headers, json=data)
resp.raise_for_status()
return resp.json()
```
### 2. HCM -- Workers and Organizations
```python
# Search workers by name
def search_workers(client: WorkdayClient, name: str, limit: int = 20) -> list[dict]:
return client.get("workers", params={"search": name, "limit": limit}).get("data", [])
# Get a specific worker
def get_worker(client: WorkdayClient, worker_id: str) -> dict:
return client.get(f"workers/{worker_id}")
# Get direct reports
def get_direct_reports(client: WorkdayClient, worker_id: str) -> list[dict]:
return client.get(f"workers/{worker_id}/directReports").get("data", [])
# Get organizations
def get_organizations(client: WorkdayClient, org_type: str = "Supervisory") -> list[dict]:
return client.get("organizations", params={"type": org_type, "limit": 50}).get("data", [])
# Paginate through all workers
def get_all_workers(client: WorkdayClient) -> list[dict]:
all_workers = []
offset = 0
while True:
resp = client.get("workers", params={"limit": 100, "offset": offset})
data = resp.get("data", [])
all_workers.extend(data)
if offset + 100 >= resp.get("total", 0):
break
offset += 100
return all_workers
```
### 3. Recruiting
```python
# List open requisitions
def get_open_requisitions(client: WorkdayClient, limit: int = 50) -> list[dict]:
return client.get("jobRequisitions", params={"status": "Open", "limit": limit}).get("data", [])
# Get applications for a requisition
def get_applications(client: WorkdayClient, requisition_id: str) -> list[dict]:
return client.get(f"jobRequisitions/{requisition_id}/jobApplications").get("data", [])
# Get candidate details
def get_candidate(client: WorkdayClient, candidate_id: str) -> dict:
return client.get(f"candidates/{candidate_id}")
```
### 4. Financials
```python
# List ledger accounts
def get_ledger_accounts(client: WorkdayClient, limit: int = 100) -> list[dict]:
return client.get("ledgerAccounts", params={"limit": limit}).get("data", [])
# Get journal entries for a date range
def get_journal_entries(client: WorkdayClient, from_date: str, to_date: str) -> list[dict]:
return client.get("journalEntries", params={
"fromDate": from_date, "toDate": to_date, "limit": 100,
}).get("data", [])
# Get supplier invoices
def get_supplier_invoices(client: WorkdayClient, status: str = "Approved") -> list[dict]:
return client.get("supplierInvoices", params={"status": status, "limit": 50}).get("data", [])
```
### 5. WQL (Workday Query Language)
```python
def run_wql(client: WorkdayClient, query: str) -> list[dict]:
"""Execute a WQL query for complex cross-domain data retrieval."""
resp = requests.get(client.wql_url, headers=client._headers, params={"query": query})
resp.raise_for_status()
return resp.json().get("data", [])
# Active workers with their managers
workers_with_mgrs = run_wql(client, """
SELECT worker, worker.name, worker.primaryWorkEmail,
worker.supervisoryOrganization.manager.name AS managerName
FROM allActiveWorkers
WHERE worker.location = 'Ho Chi Minh City'
LIMIT 100
""")
# Open requisitions with applicant counts
reqs = run_wql(client, """
SELECT jobRequisition, jobRequisition.jobTitle,
jobRequisition.numberOfOpenings,
COUNT(jobApplication) AS applicationCount
FROM jobRequisitions
WHERE jobRequisition.status = 'Open'
GROUP BY jobRequisition
""")
# Skills gap analysis
skills = run_wql(client, """
SELECT worker.name, skill.name, proficiency.level
FROM workerSkills
WHERE worker.supervisoryOrganization = 'Engineering'
""")
```
### 6. RaaS (Report-as-a-Service)
```python
def get_raas_report(client: WorkdayClient, owner: str, report_name: str,
params: dict = None) -> dict:
"""Fetch a Workday custom report exposed as a REST endpoint.
RaaS is often the most practical integration method because:
- Reports can aggregate data that would need multiple API calls
- Business users control report definitions without code changes
- Reports include calculated fields and custom logic
"""
url = f"{client.raas_url}/{owner}/{report_name}"
query = {"format": "json"}
if params:
query.update(params)
resp = requests.get(url, headers=client._headers, params=query)
resp.raise_for_status()
return resp.json()
# Headcount report filtered by org
headcount = get_raas_report(client, "ISU_Integration_User", "Active_Headcount_Report", {
"Organization!WID": "abc123",
"As_Of_Date": "2026-02-28",
})
# Open positions report
positions = get_raas_report(client, "ISU_Integration_User", "Open_Positions_Report", {
"Job_Family": "Engineering",
})
```
### 7. Orchestrations (Trigger Workflows)
```python
def trigger_orchestration(client: WorkdayClient, orchestration_id: str, payload: dict) -> dict:
"""Trigger a Workday Orchestration from an AI agent.
Orchestrations can:
- Read/write business objects
- Trigger business processes (approvals, notifications)
- Call external services (webhook back to AI agent)
"""
return client.post(f"orchestrations/{orchestration_id}/run", payload)
# Example: Trigger onboarding workflow for a new hire
result = trigger_orchestration(client, "onboarding_workflow_001", {
"worker_id": "abc123",
"start_date": "2026-03-15",
"department": "Engineering",
})
```
### 8. Agent Tool Functions
```python
def lookup_employee(employee_name: str) -> str:
"""Look up a Workday employee by name.
Args:
employee_name: Full or partial employee name
"""
client = WorkdayClient(tenant=WD_TENANT, host=WD_HOST,
client_id=WD_CLIENT_ID, client_secret=WD_SECRET,
refresh_token=WD_REFRESH_TOKEN)
workers = search_workers(client, employee_name, limit=5)
if not workers:
return f"No employee found matching '{employee_name}'."
results = []
for w in workers:
results.append(f"- {w.get('descriptor', 'N/A')} (ID: {w.get('id', 'N/A')})")
return f"Found {len(workers)} employee(s):\n" + "\n".join(results)
def get_open_positions(department: str = "") -> str:
"""Get open job requisitions, optionally filtered by department.
Args:
department: Department name to filter (optional)
"""
client = WorkdayClient(tenant=WD_TENANT, host=WD_HOST,
client_id=WD_CLIENT_ID, client_secret=WD_SECRET,
refresh_token=WD_REFRESH_TOKEN)
if department:
reqs = run_wql(client, f"""
SELECT jobRequisition.jobTitle, jobRequisition.numberOfOpenings
FROM jobRequisitions
WHERE jobRequisition.status = 'Open'
AND jobRequisition.supervisoryOrganization LIKE '%{department}%'
""")
else:
reqs = get_open_requisitions(client)
return f"Found {len(reqs)} open requisition(s)."
```
## Workday Illuminate (AI Features)
| Feature | Description |
|---|---|
| **Skills Cloud** | 50,000+ skills taxonomy, auto-extraction from resumes/JDs, skills gap analysis |
| **AI Gateway** | Governed proxy for external LLMs -- enforces PII masking, audit logging |
| **Anomaly Detection** | Flags unusual financial transactions and expense patterns |
| **Workforce Planning AI** | Attrition risk scoring, predictive headcount planning |
| **Recruiting AI** | Candidate-job matching, bias detection in job postings |
| **Illuminate Agents** | Workday's native AI agents for multi-step tasks (expense reports, requisitions) |
## Integration Method Guide
| Need | Method | Complexity |
|---|---|---|
| Read employee data | REST API `/workers` | Low |
| Complex cross-domain queries | WQL | Low-Medium |
| Aggregated/formatted data | RaaS (Custom Reports) | Low |
| Write/update records | REST API POST/PUT/PATCH | Medium |
| Trigger workflows | Orchestrations | Medium |
| Bulk data import | EIB (file-based via SFTP/S3) | Medium |
| Register as AI provider | AI Gateway | High |
## Anti-Patterns
- Fetching all workers without pagination -- Workday rate-limits at ~30 req/min for heavy endpoints
- Skipping WQL when multiple REST calls are needed -- WQL is far more efficient for cross-domain queries
- Not using RaaS for reporting -- custom reports are the most practical read-heavy integration method
- Hardcoding Workday tenant URLs -- use environment config (prod, sandbox, impl are different hosts)
- Ignoring ISU security groups -- most "API not working" issues are security/permission misconfigurations
- Sending PII to external LLMs without Workday AI Gateway -- use the gateway for governed AI access
## References
- [Workday REST API Documentation](https://community.workday.com/sites/default/files/file-hosting/restapi/)
- [Workday WQL Reference](https://community.workday.com/sites/default/files/file-hosting/wql/)
- [Workday Extend / Orchestrations](https://www.workday.com/en-us/products/platform/extend.html)
- [Workday Illuminate](https://www.workday.com/en-us/artificial-intelligence.html)
<!-- Source: .faos/custom/skills/integrations/workday-hcm/SKILL.md -->
No comments yet. Be the first to comment!