ClearCompany HRMDirect Jobs API.
Pull open roles from ClearCompany's HRMDirect career boards using their public RSS requisition feed, then hydrate each posting from server-rendered detail pages for full descriptions, locations, and departments.
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 ClearCompany HRMDirect.
- Full Job Descriptions
- Location (City & State)
- Department Tags
- Requisition IDs
- Posted Dates
- Direct Apply URLs
- 01Job Board Aggregation
- 02ATS Market Mapping
- 03Recruitment Analytics
- 04Careers Page Monitoring
- 05Talent Sourcing Feeds
How to scrape ClearCompany HRMDirect.
Step-by-step guide to extracting jobs from ClearCompany HRMDirect-powered career pages—endpoints, authentication, and working code.
import requests
def board_base_url(tenant: str) -> str:
# e.g. "ecisolutions" -> "https://ecisolutions.hrmdirect.com"
# Always request over https, even if the source link used http://.
return f"https://{tenant}.hrmdirect.com"
tenant = "ecisolutions"
base_url = board_base_url(tenant)import xml.etree.ElementTree as ET
from urllib.parse import urlparse, parse_qs
RSS_PATH = "/employment/rss.php?search=true&dept=-1&city=-1&state=-1"
def list_requisitions(base_url: str) -> list[dict]:
resp = requests.get(base_url + RSS_PATH, timeout=30)
resp.raise_for_status()
root = ET.fromstring(resp.content)
if root.tag.lower() != "rss":
raise ValueError("Response was not an RSS envelope")
listings, seen = [], set()
for item in root.findall("./channel/item"):
link = (item.findtext("link") or "").strip()
req = parse_qs(urlparse(link).query).get("req", [""])[0]
if not req.isdigit() or req in seen:
continue # skip malformed links and duplicate reqs
seen.add(req)
listings.append({
"external_id": req,
"listing_url": f"{base_url}/employment/view.php?req={req}",
"title": (item.findtext("title") or "").strip(),
"posted_at": item.findtext("pubDate"),
})
return listingsimport time
def fetch_detail_html(base_url: str, req: str) -> str | None:
url = f"{base_url}/employment/view.php?req={req}"
resp = requests.get(url, timeout=30)
if resp.status_code == 404:
return None # requisition removed
if resp.status_code in (403, 429):
raise RuntimeError(f"Rate limited / blocked (HTTP {resp.status_code})")
resp.raise_for_status()
time.sleep(0.4) # pace requests to stay under the board's limits
return resp.textfrom bs4 import BeautifulSoup
def parse_detail(base_url: str, req: str, html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
detail_url = f"{base_url}/employment/view.php?req={req}"
h2 = soup.select_one("div.reqResult h2") or soup.select_one("h2")
title = h2.get_text(strip=True) if h2 else None
if not title and soup.title:
title = soup.title.get_text(strip=True).split(" - Careers At")[0].strip()
desc_el = soup.select_one("div.jobDesc")
description = desc_el.get_text(" ", strip=True) if desc_el else ""
fields = {}
for row in soup.select("table.viewFields tr"):
name = row.select_one("td.viewFieldName")
value = row.select_one("td.viewFieldValue")
if name and value:
fields[name.get_text(strip=True).rstrip(":")] = value.get_text(strip=True)
apply_el = soup.select_one("a.applyOnline[href], div.applyDiv a[href]")
apply_url = apply_el["href"] if apply_el else detail_url
return {
"external_id": req,
"listing_url": detail_url,
"apply_url": apply_url,
"title": title,
"description": description,
"location": fields.get("Location"),
"department": fields.get("Department"),
}Use /employment/rss.php only for discovery, then fetch the /employment/view.php?req={id} detail page for the full description, location, and department.
Normalize the board host to https:// and force every detail and apply URL to https before requesting.
Treat an empty <item> list as 'no open roles' rather than a parse failure; only error when the envelope itself is malformed or not RSS.
Keep detail fetches to about 3 concurrent requests spaced ~400ms apart, and back off when a 429 is returned.
Skip any item whose req parameter is missing or non-digit before building the /employment/view.php URL.
- 1Discover with the RSS feed, then hydrate each req from its detail page — never trust RSS descriptions for full text.
- 2Normalize every board host and detail URL to https:// before requesting.
- 3Cap detail fetches at ~3 concurrent with ~400ms spacing to avoid 403/429 responses.
- 4Validate that each detail URL resolves to /employment/view.php with a matching numeric req before fetching.
- 5Treat detail-page 404s as removal signals, not transient failures.
- 6Cache each tenant's req set between runs and only re-fetch details for new or changed requisitions.
One endpoint. All ClearCompany HRMDirect jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=clearcompany hrmdirect" \
-H "X-Api-Key: YOUR_KEY" Access ClearCompany HRMDirect
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.