ApplicantPro Jobs API.
Tap a public JSON API to pull salary ranges, departments, and full descriptions from any ApplicantPro careers board — no auth required, with a global sitemap index for discovering every isolved Talent Acquisition company.
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 ApplicantPro.
- Structured Pay Ranges
- Department & Classification
- Employment & Workplace Type
- HTML & Plain-Text Descriptions
- Benefits & Salary Details
- Posting & Closing Dates
- 01SMB Job Aggregation
- 02Multi-Company Board Discovery
- 03Salary Benchmarking
- 04Recruitment Market Research
How to scrape ApplicantPro.
Step-by-step guide to extracting jobs from ApplicantPro-powered career pages—endpoints, authentication, and working code.
import requests
import re
from urllib.parse import urlparse
def get_site_id(subdomain: str) -> str | None:
url = f"https://{subdomain}.applicantpro.com/jobs/"
response = requests.get(url, timeout=10)
response.raise_for_status()
# Extract domain_id from embedded JavaScript
match = re.search(r'"domain_id"\s*:\s*"(\d+)"', response.text)
if match:
return match.group(1)
return None
site_id = get_site_id("harvardbioscience")
print(f"Site ID: {site_id}") # Output: Site ID: 11099import requests
import json
from urllib.parse import quote
subdomain = "harvardbioscience"
site_id = "11099"
get_params = {
"isInternal": 0,
"showLocation": 1,
"showEmploymentType": 1,
"chatToApplyButton": "0"
}
# URL-encode the JSON params
encoded_params = quote(json.dumps(get_params))
listings_url = f"https://{subdomain}.applicantpro.com/core/jobs/{site_id}?getParams={encoded_params}"
headers = {
"Accept": "application/json",
"Referer": f"https://{subdomain}.applicantpro.com/jobs/"
}
response = requests.get(listings_url, headers=headers, timeout=10)
data = response.json()
jobs = data.get("data", {}).get("jobs", [])
job_count = data.get("data", {}).get("jobCount", 0)
print(f"Found {len(jobs)} jobs (API reports {job_count} total)")for job in jobs:
print({
"id": job.get("id"),
"title": job.get("title"),
"location": job.get("jobLocation"),
"city": job.get("city"),
"state": job.get("stateName"),
"country": job.get("iso3"),
"department": job.get("orgTitle"),
"classification": job.get("classification"),
"employment_type": job.get("employmentType"),
"workplace_type": job.get("workplaceType"),
"pay_type": job.get("payType"),
"pay_details": job.get("payDetails"),
"min_salary": job.get("minSalary"),
"max_salary": job.get("maxSalary"),
"job_url": job.get("jobUrl"),
"posted_date": job.get("startDateRef"),
"expiry_date": job.get("endDateRef"),
})import time
def get_job_details(subdomain: str, site_id: str, job_id: int) -> dict:
url = f"https://{subdomain}.applicantpro.com/core/jobs/{site_id}/{job_id}/job-details"
headers = {
"Accept": "application/json",
"Referer": f"https://{subdomain}.applicantpro.com/jobs/{job_id}"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
return response.json().get("data", {})
# Fetch details for first job with rate limiting
if jobs:
details = get_job_details(subdomain, site_id, jobs[0]["id"])
print({
"id": details.get("id"),
"title": details.get("title"),
"city": details.get("city"),
"description_html": details.get("advertisingDescriptionHtml", "")[:200],
"description_plain": details.get("advertisingDescription", "")[:200],
"benefits": details.get("benefits"),
"zip_code": details.get("jobBoardZip"),
"pay_details": details.get("payDetails"),
})
time.sleep(1.0) # Be respectful with rate limitingdef safe_extract(subdomain: str) -> list[dict]:
try:
site_id = get_site_id(subdomain)
if not site_id:
print(f"Could not find site ID for {subdomain}")
return []
get_params = {"isInternal": 0, "showLocation": 1}
encoded_params = quote(json.dumps(get_params))
url = f"https://{subdomain}.applicantpro.com/core/jobs/{site_id}?getParams={encoded_params}"
response = requests.get(url, headers={"Accept": "application/json"}, timeout=10)
response.raise_for_status()
data = response.json()
if not data.get("success"):
print(f"API returned error for {subdomain}")
return []
return data.get("data", {}).get("jobs", [])
except requests.RequestException as e:
print(f"Request failed for {subdomain}: {e}")
return []
jobs = safe_extract("harvardbioscience")import requests
import xml.etree.ElementTree as ET
# Global sitemap index lists all ApplicantPro companies
sitemap_index_url = "https://feeds.applicantpro.com/site_map_index.xml"
response = requests.get(sitemap_index_url, timeout=10)
root = ET.fromstring(response.content)
# Extract company sitemap URLs
namespaces = {"ns": "http://www.sitemaps.org/schemas/sitemap/0.9"}
company_sitemaps = []
for sitemap in root.findall("ns:sitemap", namespaces):
loc = sitemap.find("ns:loc", namespaces)
if loc is not None:
company_sitemaps.append(loc.text)
print(f"Found {len(company_sitemaps)} company sitemaps")
# Parse individual company sitemap for job URLs
def parse_company_sitemap(sitemap_url: str) -> list[str]:
response = requests.get(sitemap_url, timeout=10)
root = ET.fromstring(response.content)
job_urls = []
for url in root.findall("ns:url", namespaces):
loc = url.find("ns:loc", namespaces)
if loc is not None and "/jobs/" in loc.text:
job_urls.append(loc.text)
return job_urls
# Example: Get jobs from first company sitemap
if company_sitemaps:
jobs = parse_company_sitemap(company_sitemaps[0])
print(f"Found {len(jobs)} job URLs in first sitemap")The page structure may have changed. Try alternative regex patterns or look for the domain_id in script tags within the courierCurrentRouteData object. Some companies use custom domains that redirect to ApplicantPro.
The listings API does not include descriptions. You must make a separate call to the job-details endpoint for each job to get the full description and benefits.
Some companies use custom domains that redirect to ApplicantPro. Follow redirects and extract the actual subdomain from the final URL (host ending in .applicantpro.com) before pulling the site ID.
Some companies may have no active postings. Check the jobCount field in the response and handle empty arrays gracefully in your code.
Dates appear in different formats across endpoints (e.g. 'Jan 23, 2026' in listings vs '23-Jan-2026' in details). Use a flexible parser like dateutil to handle both.
Unthrottled requests can return HTTP 403 or 429. Keep a single concurrent connection and pause 1-2 seconds between requests, especially for per-job detail fetches.
Salary fields (minSalary, maxSalary) are strings and may be empty. Always check for truthy values before parsing; freeform pay info is often in payDetails as text rather than structured numbers.
- 1Cache the site ID per subdomain — the careers-page HTML only needs fetching once
- 2Prefer advertisingDescriptionHtml, falling back to advertisingDescription then description
- 3Throttle to roughly one request every 1-2 seconds and cap concurrent detail fetches
- 4Use the jobUrl field from the response, falling back to a constructed /jobs/{id} URL
- 5Pull benefits and ZIP code from the details endpoint — they are absent from listings
- 6Discover new company boards through the global sitemap index at feeds.applicantpro.com
One endpoint. All ApplicantPro jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=applicantpro" \
-H "X-Api-Key: YOUR_KEY" Access ApplicantPro
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.