- criticalEvery API call returns 404
- The API is namespaced under the deployed build version, so /api/jobs alone does not exist. Load the board page first and read the version marker out of its bootstrap script, then place that value between the host and /api in every request path.
- highA crawl fails halfway through with 404s
- A deploy rotates the version segment while your crawl is running, invalidating a cached value. Treat a 404 on an API path as a signal to re-read the board once and retry with the fresh version rather than aborting the whole tenant.
- highJob IDs are mangled or collide
- Identifiers are opaque URL-safe strings such as BlYGs4AHMkmUlmkarS36BA, not numbers. Parsing them as integers, lowercasing them or trimming them truncates real IDs and merges distinct jobs. Store them verbatim and URL-encode when building paths.
- lowLocations come through as fragments
- The address is split across street, city, state and zip fields, all of them optional. Join whichever are present into one string instead of reading city alone, otherwise multi-site operators end up with jobs that all look identically located.
HealthcareSource Hiring Jobs API.
HealthcareSource Hiring serves senior-care and hospital boards at {tenant}.hcshiring.com. A public JSON API returns paginated jobs and full descriptions once you read the build version off the board page.
What's in every response.
Data fields, real-world applications, and the companies already running on HealthcareSource Hiring.
Data fields
- Full Job Descriptions
- Authoritative Job Totals
- Street, City, State & ZIP
- Organization Names
- Opening & Expiry Dates
- Labor Request IDs
Use cases
- 01Senior Care Job Aggregation
- 02Healthcare Recruitment Feeds
- 03Clinical Hiring Trackers
- 04ATS Data Pipelines
Trusted by
- Accura
- American Lutheran
- Americare
- Aberdeen Health and Rehab
How to scrape HealthcareSource Hiring.
Step-by-step guide to extracting jobs from HealthcareSource Hiring-powered career pages—endpoints, authentication, and working code.
import re
from urllib.parse import urlparse, unquote
SUFFIX = ".hcshiring.com"
TENANT = re.compile("^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$", re.IGNORECASE)
JOB_ID = re.compile("^[A-Za-z0-9_-]{16,80}$")
def parse_hcs(url: str) -> dict | None:
parsed = urlparse(url)
host = parsed.netloc.lower().rstrip(".")
if not host.endswith(SUFFIX):
return None
tenant = host[: -len(SUFFIX)]
if tenant in ("www", "cdn", "") or "." in tenant or not TENANT.match(tenant):
return None
segments = [s for s in parsed.path.split("/") if s]
job_id = None
if segments:
if segments[0] != "jobs" or len(segments) > 2:
return None
if len(segments) == 2:
job_id = unquote(segments[1])
if not JOB_ID.match(job_id):
return None
return {"tenant": tenant, "job_id": job_id,
"board_url": f"https://{tenant}.hcshiring.com/jobs"}
print(parse_hcs("https://aberdeenhealthandrehab.hcshiring.com/jobs/BlYGs4AHMkmUlmkarS36BA"))import requests
VERSION = re.compile("var[ ]+V[ ]*=[ ]*[{][ ]*version:[ ]*['\"]([a-zA-Z0-9]+)['\"]")
def resolve_version(session, tenant: str) -> str:
board_url = f"https://{tenant}.hcshiring.com/jobs"
resp = session.get(board_url, headers={"Accept": "text/html"}, timeout=30)
resp.raise_for_status()
match = VERSION.search(resp.text)
if not match:
raise RuntimeError("board omitted its API version marker")
return match.group(1)
session = requests.Session()
version = resolve_version(session, "accura")
print("api version:", version)import time
def fetch_page(session, tenant: str, version: str, page: int) -> dict:
url = f"https://{tenant}.hcshiring.com/{version}/api/jobs?page={page}"
resp = session.get(url, headers={"Accept": "application/json"}, timeout=30)
resp.raise_for_status()
return resp.json()
def join_address(row: dict) -> str | None:
parts = [row.get(key) for key in ("street", "city", "state", "zip")]
text = ", ".join(p.strip() for p in parts if isinstance(p, str) and p.strip())
return text or None
def fetch_all(session, tenant: str, version: str) -> list[dict]:
page, jobs = 1, []
while True:
payload = fetch_page(session, tenant, version, page)
rows = payload.get("jobs") or []
meta = payload.get("meta") or {}
total_pages = meta.get("totalPages") or 1
for row in rows:
job_id, title = row.get("id"), row.get("title")
if not job_id or not title:
continue
jobs.append({
"id": job_id,
"title": title.strip(),
"summary": row.get("summary"),
"company": row.get("organization"),
"location": join_address(row),
"posted_at": row.get("lastOpening"),
"closes_at": row.get("validThrough"),
"url": f"https://{tenant}.hcshiring.com/jobs/{job_id}",
})
if page >= total_pages or not rows:
return jobs
page += 1
time.sleep(0.075)
listings = fetch_all(session, "accura", version)
print(f"{len(listings)} open jobs")def fetch_detail(session, tenant: str, version: str, job_id: str) -> dict | None:
url = (f"https://{tenant}.hcshiring.com/{version}"
f"/api/jobDescriptions?id={job_id}")
resp = session.get(url, headers={"Accept": "application/json"}, timeout=30)
if resp.status_code in (404, 410):
return None # job removed
resp.raise_for_status()
payload = resp.json() or {}
detail = payload.get("jobDescriptions") or {}
job = detail.get("job") or {}
if detail.get("id") != job_id or not job:
raise RuntimeError("HealthcareSource returned a different job than requested")
return {
"id": job_id,
"title": job.get("title"),
"description_html": detail.get("description"),
"company": job.get("organization"),
"location": join_address(job),
"posted_at": job.get("lastOpening"),
"closes_at": job.get("validThrough"),
"labor_request_id": detail.get("laborRequest"),
"url": f"https://{tenant}.hcshiring.com/jobs/{job_id}",
}
for row in listings[:3]:
print(fetch_detail(session, "accura", version, row["id"]))
time.sleep(0.075)class VersionCache:
def __init__(self, session):
self.session = session
self.versions = {}
def get(self, tenant: str) -> str:
if tenant not in self.versions:
self.versions[tenant] = resolve_version(self.session, tenant)
return self.versions[tenant]
def refresh(self, tenant: str) -> str:
self.versions.pop(tenant, None)
return self.get(tenant)
def call_with_retry(cache: VersionCache, tenant: str, build_url) -> dict:
version = cache.get(tenant)
resp = cache.session.get(build_url(version),
headers={"Accept": "application/json"}, timeout=30)
if resp.status_code == 404:
# The build rolled over mid-crawl — re-read the board and retry once.
version = cache.refresh(tenant)
resp = cache.session.get(build_url(version),
headers={"Accept": "application/json"}, timeout=30)
resp.raise_for_status()
return resp.json()
cache = VersionCache(session)
payload = call_with_retry(
cache, "accura",
lambda v: f"https://accura.hcshiring.com/{v}/api/jobs?page=1")- 1Read the build version off the board page and cache it per tenant
- 2Re-resolve the version and retry once when an API path returns 404
- 3Drive pagination off meta.totalPages and stop on an empty page
- 4Store the opaque job ID verbatim and URL-encode it in request paths
- 5Verify the detail response's id echoes the job you requested
- 6Join street, city, state and ZIP into one location string
One endpoint. All HealthcareSource Hiring jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=healthcaresource hiring" \
-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 HealthcareSource Hiring
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.