All platforms

UKG Pro Workforce Ready (SaaShr) Jobs API.

Pull structured job listings — pay ranges, categories, and full descriptions — from any employer board on the SaaShr pod pool through one anonymous JSON endpoint, normalized by Jobo's unified jobs API.

Get API access
UKG Pro Workforce Ready (SaaShr)
Live
<3haverage discovery time
1hrefresh interval
Developer tools

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 UKG Pro Workforce Ready (SaaShr).

Data fields
  • Structured Pay Ranges & Period
  • Job Categories & Industries
  • Employment Type & Required Degree
  • Full Descriptions & Requirements
  • Remote Status & Geocoordinates
  • Configured Custom Fields
Use cases
  1. 01Multi-Tenant Board Monitoring
  2. 02Compensation Benchmarking
  3. 03Regional Hiring Analysis
  4. 04Job Aggregation Feeds
DIY GUIDE

How to scrape UKG Pro Workforce Ready (SaaShr).

Step-by-step guide to extracting jobs from UKG Pro Workforce Ready (SaaShr)-powered career pages—endpoints, authentication, and working code.

RESTintermediateNo published limit; pace ~100ms between requests and cap concurrent detail fetchesNo auth

Identify the pod host and tenant ID

SaaShr serves each employer from a numbered pod (e.g. secure4.saashr.com) or the entertimeonline.com white-label host, with the tenant as a numeric ID in the /ta/{id}.careers path. Extract both from the board URL before calling the API — marketing hosts and the sibling Kronos product are not valid.

Step 1: Identify the pod host and tenant ID
import re

board_url = "https://secure4.saashr.com/ta/6154221.careers"
m = re.match(
    r"^(https://secure\d*\.(?:saashr|entertimeonline)\.com)/ta/(\d{5,12})\.careers$",
    board_url,
)
if not m:
    raise ValueError("Not a valid SaaShr board URL")

origin, tenant = m.group(1), m.group(2)
print(origin, tenant)  # https://secure4.saashr.com 6154221

Fetch a page of job requisitions

The public board loads jobs from an anonymous JSON endpoint — no bearer token, cookie, or CSRF value is required. Reproduce the browser XHR headers and pass the tenant as %7C{tenant} (a URL-encoded |{tenant}).

Step 2: Fetch a page of job requisitions
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:
    url = f"{origin}/ta/rest/ui/recruitment/companies/%7C{tenant}/job-requisitions"
    params = {"offset": page, "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)
print(first["_paging"]["total"], "open jobs")

Page through every requisition

The "offset" parameter is a one-based page number (not a row offset), and "size" is fixed at 100. Use _paging.total as the authority: compute the page count and stop when the total is exhausted rather than trusting an empty page.

Step 3: Page through every requisition
import math

def fetch_all() -> list[dict]:
    first = fetch_page(1)
    total = first["_paging"]["total"]
    jobs = list(first["job_requisitions"])
    pages = math.ceil(total / PAGE_SIZE)
    for page in range(2, pages + 1):
        jobs.extend(fetch_page(page)["job_requisitions"])
    assert len(jobs) == total, f"expected {total}, got {len(jobs)}"
    return jobs

all_jobs = fetch_all()

Read the listing fields

Each requisition carries the native numeric id, title, location, pay range, categories, and employment type. Build the public apply URL from the id using the ShowJob query parameter.

Step 4: Read the listing fields
for job in all_jobs:
    job_id = job["id"]
    location = job.get("location") or {}
    employee_type = job.get("employee_type") or {}
    print({
        "id": job_id,
        "title": job.get("job_title"),
        "city": location.get("city"),
        "state": location.get("state"),
        "pay_from": job.get("base_pay_from"),
        "pay_to": job.get("base_pay_to"),
        "pay_period": job.get("base_pay_frequency"),
        "employment_type": employee_type.get("name"),
        "remote": job.get("is_remote_job"),
        "apply_url": f"{origin}/ta/{tenant}.careers?ShowJob={job_id}&lang=en-US",
    })

Fetch a full job description

The listing endpoint returns summaries; call the per-requisition detail endpoint (showMap=1) for the full HTML description, requirements, required degree, minimum experience, map coordinates, and any configured custom fields.

Step 5: Fetch a full job description
def fetch_detail(job_id) -> dict:
    url = f"{origin}/ta/rest/ui/recruitment/companies/%7C{tenant}/job-requisitions/{job_id}"
    resp = requests.get(url, params={"showMap": 1, "lang": "en-US"}, headers=headers, timeout=15)
    resp.raise_for_status()
    return resp.json()

detail = fetch_detail(all_jobs[0]["id"])
print(detail.get("job_description"))
print(detail.get("job_requirement"))
Common issues
highTreating "offset" as a row offset (0, 100, 200…) refetches page 1 or skips pages.

The parameter is a one-based page index despite its name — pass 1, 2, 3…, keep size fixed at 100, and derive the page count from _paging.total.

mediumStopping pagination on the first empty-looking page can truncate the snapshot.

Use _paging.total as the source of truth: fetch ceil(total / 100) pages and assert the collected row count equals total before trusting the result.

mediumURLs on www.saashr.com or a *.mykronos.com host return nothing useful.

Only the numbered pods secure{N}.saashr.com and secure{N}.entertimeonline.com expose the anonymous job-requisitions API. The sibling Kronos product uses a separate contract and host.

mediumSome tenants answer anonymous API calls with 401 or 403.

Send the exact XHR headers (Accept, a pod-root Referer, and X-Requested-With: XMLHttpRequest). If a board still rejects anonymous access, that tenant has gated its careers API.

lowA ShowJob id returns 404/410 after the requisition closes.

Treat 404/410 on the detail endpoint as a removal signal and drop the job rather than retrying it.

Best practices
  1. 1Pass offset as a 1-based page number with size=100 — never as a row offset.
  2. 2Trust _paging.total for completeness: collect ceil(total / 100) pages and verify the count matches.
  3. 3Send the anonymous XHR headers (Accept, pod-root Referer, X-Requested-With) on every request.
  4. 4Preserve the exact pod host from the board URL; don't normalize secure4/secure7 or swap to www.
  5. 5Pace requests (~100ms) and keep concurrent detail fetches low to avoid 429s.
  6. 6Enrich from the detail endpoint for pay, custom fields, and the full description, not just the listing.
Or skip the complexity

One endpoint. All UKG Pro Workforce Ready (SaaShr) jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://connect.jobo.world/api/jobs?sources=ukg pro workforce ready (saashr)" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

Access UKG Pro Workforce Ready (SaaShr)
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.

99.9%API uptime
<200msAvg response
50M+Jobs processed