- criticalThe job ID never reaches the server
- Career-site URLs put the job in the fragment, #/job/1548, which browsers never transmit. Fetching that URL returns the same shell for every requisition. Parse the fragment client-side and call the tenant-scoped API endpoints directly instead of following the link.
- criticalThe search endpoint returns 404 or 405
- JobseekerSearchAPI answers to POST with a JSON body, not to GET with query parameters. Send the Elasticsearch-shaped body with from, size and a match_all filter, set the JSON content type, and include the board as the Referer.
- highPagination stops early or repeats rows
- hits.total is sometimes a plain integer and sometimes an object carrying a value key, so a single-shape reader either stops on page one or loops. Normalise both forms, and advance the offset by the hits you actually received rather than by the requested size.
- mediumDescriptions come back empty
- The narrative lives under userArea, not at the top level: jobSummaryDisplay holds the formatted body and jobSummary the plain version. Reading description from the search hit alone gives a short teaser, so fetch the v2 detail endpoint for the full text.
HealthcareSource Performance Manager Jobs API.
HealthcareSource Performance Manager hosts hospital career sites at pm.healthcaresource.com/CS/{tenant}. Its search API is an Elasticsearch-shaped POST that returns every requisition with schema.org-style job fields.
What's in every response.
Data fields, real-world applications, and the companies already running on HealthcareSource Performance Manager.
Data fields
- Full Job Descriptions
- Requisition Numbers
- Shift Information
- Structured Addresses
- Hiring Organization Names
- Offset Pagination
Use cases
- 01Hospital Job Aggregation
- 02Nursing Recruitment Feeds
- 03Health System Hiring Trackers
- 04ATS Data Pipelines
Trusted by
- Access Health Louisiana
- AnMed
- Archbold
How to scrape HealthcareSource Performance Manager.
Step-by-step guide to extracting jobs from HealthcareSource Performance Manager-powered career pages—endpoints, authentication, and working code.
import re
from urllib.parse import urlparse
HOST = "pm.healthcaresource.com"
TENANT = re.compile("^[a-z0-9][a-z0-9_-]{0,80}$", re.IGNORECASE)
def parse_pm(url: str) -> dict | None:
parsed = urlparse(url)
if parsed.netloc.lower() != HOST:
return None
segments = [s for s in parsed.path.split("/") if s]
if len(segments) != 2 or segments[0].lower() != "cs":
return None
if not TENANT.match(segments[1]):
return None
tenant = segments[1].lower()
job_id = None
fragment = (parsed.fragment or "").strip("#/")
if fragment:
parts = [p for p in fragment.split("/") if p]
if len(parts) != 2 or parts[0].lower() != "job" or not parts[1].isdigit():
return None
job_id = parts[1]
return {"tenant": tenant, "job_id": job_id,
"board_url": f"https://{HOST}/CS/{tenant}/#/"}
print(parse_pm("https://pm.healthcaresource.com/CS/accesshealthla/#/job/1548"))import requests
PAGE_SIZE = 100
def search(session, tenant: str, offset: int = 0) -> dict:
url = (f"https://{HOST}/JobseekerSearchAPI/{tenant}"
f"/api/Search?size={PAGE_SIZE}")
body = {
"from": offset,
"size": PAGE_SIZE,
"query": {"bool": {"filter": {"match_all": {}}}},
}
resp = session.post(
url, json=body,
headers={"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"Referer": f"https://{HOST}/CS/{tenant}/#/"},
timeout=30)
resp.raise_for_status()
return resp.json()
session = requests.Session()
first = search(session, "accesshealthla")def read_total(hits: dict) -> int:
total = hits.get("total")
if isinstance(total, int):
return total
if isinstance(total, dict) and isinstance(total.get("value"), int):
return total["value"]
return len(hits.get("hits") or [])
def join_address(source: dict) -> str | None:
address = ((source.get("jobLocation") or {}).get("address")) or {}
parts = [address.get(key) for key in
("streetAddress", "addressLocality", "addressRegion", "postalCode")]
text = ", ".join(p.strip() for p in parts if isinstance(p, str) and p.strip())
return text or None
def map_hits(payload: dict, tenant: str) -> list[dict]:
hits = (payload.get("hits") or {}).get("hits") or []
rows = []
for hit in hits:
source = hit.get("_source") or {}
user_area = source.get("userArea") or {}
job_id = str(user_area.get("jobPostingID") or "")
title = source.get("title") or source.get("name")
if not job_id.isdigit() or not title:
continue
rows.append({
"id": job_id,
"title": title,
"summary": source.get("description"),
"company": (source.get("hiringOrganization") or {}).get("name"),
"location": join_address(source),
"requisition_number": user_area.get("requisitionNumber"),
"shift": user_area.get("shift"),
"posted_at": source.get("datePosted"),
"url": f"https://{HOST}/CS/{tenant}/#/job/{job_id}",
})
return rowsimport time
def fetch_all(session, tenant: str) -> list[dict]:
offset, jobs, seen = 0, [], set()
while True:
payload = search(session, tenant, offset)
hits = (payload.get("hits") or {})
received = len(hits.get("hits") or [])
total = read_total(hits)
for row in map_hits(payload, tenant):
if row["id"] not in seen:
seen.add(row["id"])
jobs.append(row)
offset += received
if received == 0 or offset >= total:
return jobs
time.sleep(0.075)
listings = fetch_all(session, "accesshealthla")
print(f"{len(listings)} open requisitions")def fetch_detail(session, tenant: str, job_id: str) -> dict | None:
url = (f"https://{HOST}/JobseekerAPI/Site/{tenant}"
f"/api/v2/JobPostingV2?id={job_id}")
resp = session.get(
url,
headers={"Accept": "application/json",
"Referer": f"https://{HOST}/CS/{tenant}/#/"},
timeout=30)
if resp.status_code in (404, 410):
return None # requisition removed
resp.raise_for_status()
payload = resp.json() or {}
user_area = payload.get("userArea") or {}
if str(user_area.get("jobPostingID") or "") != str(job_id):
raise RuntimeError("Performance Manager returned a different requisition")
return {
"id": job_id,
"title": payload.get("title") or payload.get("name"),
"description_html": (user_area.get("jobSummaryDisplay")
or user_area.get("jobSummary")),
"company": (payload.get("hiringOrganization") or {}).get("name"),
"location": join_address(payload),
"employment_type": payload.get("employmentType"),
"requisition_number": user_area.get("requisitionNumber"),
"shift": user_area.get("shift"),
"posted_at": payload.get("datePosted"),
"url": f"https://{HOST}/CS/{tenant}/#/job/{job_id}",
}
for row in listings[:3]:
print(fetch_detail(session, "accesshealthla", row["id"]))
time.sleep(0.075)- 1Parse the URL fragment yourself — the server never receives #/job/{id}
- 2POST an Elasticsearch-shaped body with from, size and a match_all filter
- 3Send the board URL as the Referer on both search and detail calls
- 4Normalise hits.total across its integer and object forms
- 5Advance the offset by hits received, not by the requested page size
- 6Read jobSummaryDisplay from userArea, falling back to jobSummary
One endpoint. All HealthcareSource Performance Manager jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=healthcaresource performance manager" \
-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 Performance Manager
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.