- highRequests to www.mykronos.com or a saashr.com host return no board
- Only multi-label pod hosts (e.g. prd01-hcm01.prd.mykronos.com) serve the anonymous REST board. The www host is marketing, and the sibling SaaShr product lives on secure*.saashr.com and must be scraped as a separate provider.
- highPagination skips or refetches rows when offset is treated as a row index
- The offset parameter is a one-based page number, not a row offset. Start at offset=1 and iterate pages 1..ceil(total/100); never send offset=0 or offset=100.
- mediumDropping or double-encoding the %7C pipe breaks the path
- The tenant segment is prefixed with a URL-encoded pipe (companies/%7C{tenant}). Keep it verbatim and prevent your HTTP client from re-encoding the % into %25.
- mediumAnonymous access rejected with 401 or 403
- Some tenants gate the board. Always send the Accept, Referer, and X-Requested-With: XMLHttpRequest headers, and treat a persistent 401/403 as an auth-gated tenant rather than retrying.
- medium429 responses under heavy crawling
- Pace requests to roughly one every 100ms and cap concurrent detail fetches at about 3 to avoid rate limiting across a tenant's pages.
Kronos Workforce Ready Jobs API.
Pull structured job listings from legacy UKG Workforce Ready boards on mykronos.com, where an anonymous REST endpoint returns pay ranges, categories, and full descriptions with no login or browser automation.
What's in every response.
Data fields, real-world applications, and the companies already running on Kronos Workforce Ready.
Data fields
- Structured Pay Ranges
- Job Categories & Industries
- Employment Type & Degree
- Full Job Descriptions
- Map Coordinates
- Remote & Quick-Apply Flags
Use cases
- 01Job Board Aggregation
- 02Salary Benchmarking
- 03Local & Regional Hiring Trends
- 04Multi-Tenant Board Monitoring
Trusted by
- Arnot Health
- Harris Center
- Arc Baltimore
How to scrape Kronos Workforce Ready.
Step-by-step guide to extracting jobs from Kronos Workforce Ready-powered career pages—endpoints, authentication, and working code.
import re
board_url = "https://prd01-hcm01.prd.mykronos.com/ta/6012355.careers"
# Host must be a multi-label pod under mykronos.com (>= 2 labels before it),
# and the tenant is a 5-12 digit numeric ID.
m = re.match(
r"^https://((?:[a-z0-9-]+\.){2,}mykronos\.com)/ta/(\d{5,12})\.careers$",
board_url,
)
if not m:
raise ValueError("Not a valid Kronos Workforce Ready board URL")
origin = "https://" + m.group(1)
tenant = m.group(2)import requests
PAGE_SIZE = 100
HEADERS = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Referer": f"{origin}/",
"X-Requested-With": "XMLHttpRequest",
}
def fetch_page(page: int) -> dict:
# %7C is the encoded pipe before the tenant; keep it verbatim.
url = f"{origin}/ta/rest/ui/recruitment/companies/%7C{tenant}/job-requisitions"
params = {
"offset": page, # one-based PAGE number, not a row offset
"size": PAGE_SIZE,
"sort": "desc",
"ein_id": "",
"lang": "en-US",
}
resp = requests.get(url, params=params, headers=HEADERS, timeout=15)
resp.raise_for_status()
return resp.json()
first = fetch_page(1)
total = first["_paging"]["total"]
print(f"Tenant {tenant} has {total} open jobs")import math
import time
def fetch_all_jobs() -> list[dict]:
first = fetch_page(1)
total = first["_paging"]["total"]
jobs = list(first["job_requisitions"])
total_pages = math.ceil(total / PAGE_SIZE)
for page in range(2, total_pages + 1):
payload = fetch_page(page)
jobs.extend(payload["job_requisitions"])
time.sleep(0.1) # ~100ms between requests
return jobsdef parse_job(job: dict) -> dict:
job_id = str(job["id"])
loc = job.get("location") or {}
return {
"external_id": job_id,
"title": (job.get("job_title") or "").strip(),
"apply_url": f"{origin}/ta/{tenant}.careers?ShowJob={job_id}&lang=en-US",
"city": loc.get("city"),
"state": loc.get("state"),
"country": loc.get("country"),
"salary_min": job.get("base_pay_from"),
"salary_max": job.get("base_pay_to"),
"salary_period": job.get("base_pay_frequency"),
"categories": job.get("job_categories"),
"is_remote": job.get("is_remote_job"),
}
rows = [parse_job(j) for j in fetch_all_jobs()]def fetch_detail(job_id: str) -> dict | None:
url = (
f"{origin}/ta/rest/ui/recruitment/companies/%7C{tenant}"
f"/job-requisitions/{job_id}"
)
resp = requests.get(
url,
params={"showMap": 1, "lang": "en-US"},
headers=HEADERS,
timeout=15,
)
if resp.status_code in (404, 410):
return None # job was removed from the board
resp.raise_for_status()
job = resp.json()
parts = [job.get("job_preview"), job.get("job_description"), job.get("job_requirement")]
job["full_description"] = "\n\n".join(p for p in parts if p)
return job- 1Restrict scraping to multi-label mykronos.com pod hosts; skip www and saashr.com siblings
- 2Treat the API's offset as a one-based page number, not a row offset
- 3Page until _paging.total is fully consumed (ceil(total / 100) pages)
- 4Send Accept, Referer, and X-Requested-With: XMLHttpRequest on every request
- 5Pace requests to ~100ms and cap concurrent detail fetches at 3
- 6Treat detail 404/410 as job removal and 401/403 as an auth-gated tenant
One endpoint. All Kronos Workforce Ready jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=kronos workforce ready" \
-H "X-Api-Key: YOUR_KEY"Try the API.
Test Jobs and Feed endpoints against https://connect.jobo.world with live request/response examples, then copy ready-to-use curl commands.
Access Kronos Workforce Ready
job data today.
One API call. Structured data. No scraping infrastructure to build or maintain — start with the $5 free starting balance and scale as you grow.