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.
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.
What's in every response.
Data fields, real-world applications, and the companies already running on Kronos Workforce Ready.
- Structured Pay Ranges
- Job Categories & Industries
- Employment Type & Degree
- Full Job Descriptions
- Map Coordinates
- Remote & Quick-Apply Flags
- 01Job Board Aggregation
- 02Salary Benchmarking
- 03Local & Regional Hiring Trends
- 04Multi-Tenant Board Monitoring
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 jobOnly 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.
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.
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.
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.
Pace requests to roughly one every 100ms and cap concurrent detail fetches at about 3 to avoid rate limiting across a tenant's pages.
- 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" Access Kronos Workforce Ready
job data today.
One API call. Structured data. No scraping infrastructure to build or maintain — start with the free tier and scale as you grow.