Rival (SilkRoad) Jobs API.
Pull live openings from SilkRoad-hosted career boards on jobs.silkroad.com, with titles, locations, categories, and posting dates parsed from every rendered job page.
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 Rival (SilkRoad).
- Full Job Descriptions
- Structured Job Locations
- Job Categories
- Employment Type
- Posting Dates
- Direct Apply Links
- 01Job Board Aggregation
- 02Career Site Monitoring
- 03Healthcare & Enterprise Hiring Data
- 04New-Posting Alerts
How to scrape Rival (SilkRoad).
Step-by-step guide to extracting jobs from Rival (SilkRoad)-powered career pages—endpoints, authentication, and working code.
import requests
# Board root = /{tenant}/{portal}, e.g. Numotion/Careers or NYULangone/NYULHCareers.
BASE = "https://jobs.silkroad.com"
tenant, portal = "Numotion", "Careers"
board_url = f"{BASE}/{tenant}/{portal}"
resp = requests.get(board_url, timeout=15)
resp.raise_for_status()
print(board_url, resp.status_code)from bs4 import BeautifulSoup
# "embedded" mode trims page chrome and keeps the same sr-panel selectors.
EMBEDDED = "embedded=true&modal="
BASE = "https://jobs.silkroad.com"
def fetch_page(board_url: str, page: int = 1) -> str:
url = f"{board_url}?page={page}&{EMBEDDED}"
r = requests.get(url, timeout=15)
r.raise_for_status()
return r.text
def parse_cards(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
jobs = []
for a in soup.select('a[id^="Jobs_PagedJobList_Job-"]'):
job_id = a["id"].removeprefix("Jobs_PagedJobList_Job-")
title_el = a.select_one(".sr-panel__title")
loc_el = a.select_one(".sr-panel__location")
href = a.get("href", "")
jobs.append({
"id": job_id,
"url": BASE + href if href.startswith("/") else href,
"title": title_el.get_text(strip=True) if title_el else None,
"location": loc_el.get_text(strip=True) if loc_el else None,
})
return jobs
listings = parse_cards(fetch_page(board_url, 1))
print(f"Found {len(listings)} jobs on page 1")def scrape_all(board_url: str) -> list[dict]:
page, all_jobs = 1, []
while True:
html = fetch_page(board_url, page)
all_jobs.extend(parse_cards(html))
# Advance only while the "next" control is present.
if 'id="Jobs_PagedJobList_NextLink"' not in html:
break
page += 1
# Collapse duplicates by numeric job id.
return list({j["id"]: j for j in all_jobs}.values())
jobs = scrape_all(board_url)
print(f"Total unique jobs: {len(jobs)}")def scrape_detail(job_url: str) -> dict:
sep = "&" if "?" in job_url else "?"
r = requests.get(f"{job_url}{sep}{EMBEDDED}", timeout=15)
if r.status_code == 404:
return {"url": job_url, "removed": True}
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
def field(element_id: str):
# <div id="ConfigurablePageDetail__X"><h2>Label</h2><div>Value</div></div>
box = soup.find(id=element_id)
val = box.find("div") if box else None
return val.get_text(strip=True) if val else None
title_el = soup.select_one("#Jobs_JobDetail_TitleText")
desc_el = soup.select_one("section.sr-job-detail__description")
apply_el = soup.select_one("#Jobs_JobDetail_Multiform_ApplyLink")
return {
"url": job_url,
"title": title_el.get_text(strip=True) if title_el else None,
"description": desc_el.get_text(" ", strip=True) if desc_el else None,
"apply_url": apply_el.get("href") if apply_el else job_url,
"location": field("ConfigurablePageDetail__DisplayLocation"),
"category": field("ConfigurablePageDetail__DisplayCategory"),
"employment_type": field("ConfigurablePageDetail__DisplayPosition"),
"posted_date": field("ConfigurablePageDetail__PostingDate"),
}
print(scrape_detail(jobs[0]["url"]))def is_silkroad_board(html: str) -> bool:
return "Jobs_PagedJobList_" in html or "sr-panel" in html
first = fetch_page(board_url, 1)
if not is_silkroad_board(first):
print("Tenant no longer served by SilkRoad — skip")
else:
print("Live SilkRoad board")The page loads yet contains no Jobs_PagedJobList_ or sr-panel markers, so parsing yields zero jobs. Check for those markers on page 1; if they are absent and there is no next-link, treat the tenant as migrated and skip it.
The listings root is /{tenant}/{portal} — a single segment is invalid, and the portal is not always 'Careers' (e.g. NYULangone/NYULHCareers, or two-letter tenants like RI/CBS). Always resolve the full two-segment path before requesting.
SilkRoad blocks or throttles fast crawlers. Pace requests around 750ms, keep concurrent detail fetches at 2 or fewer, and back off on 403/429 before retrying.
ConfigurablePageDetail__* fields are template-driven and optional — some tenants omit PostingDate, DisplayCategory, or DisplayPosition. Default every configurable field to None instead of assuming it exists.
When section.sr-job-detail__description is absent the body cannot be parsed. Fall back to the listing title, and flag or skip jobs whose detail body comes back empty.
- 1Always resolve the full two-segment board root (/{tenant}/{portal}) — the portal is not always 'Careers'.
- 2Append embedded=true&modal= to listing and detail requests for smaller, chrome-free HTML.
- 3Paginate with ?page=N and stop when the Jobs_PagedJobList_NextLink control disappears.
- 4Pace requests around 750ms and keep detail concurrency at 2 or fewer to avoid 403/429 blocks.
- 5Treat every ConfigurablePageDetail__* field (location, category, employment type, posting date) as optional.
- 6De-duplicate listings by numeric job id before fetching detail pages.
One endpoint. All Rival (SilkRoad) jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=rival (silkroad)" \
-H "X-Api-Key: YOUR_KEY" Access Rival (SilkRoad)
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.