iCIMS Jobs API.
Pull structured job data — titles, full descriptions, salary bands, employment type, and locations — from the enterprise career sites hosted on this platform, no public API required.
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 iCIMS.
- Full Job Descriptions
- Structured Salary Bands
- Employment Type
- City, State & Country Locations
- Posted & Closing Dates
- Apply URLs
- 01Enterprise Job Aggregation
- 02Salary Benchmarking
- 03Large-Employer Job Tracking
- 04Recruitment Market Research
How to scrape iCIMS.
Step-by-step guide to extracting jobs from iCIMS-powered career pages—endpoints, authentication, and working code.
import requests
import xml.etree.ElementTree as ET
company = "careers-bcore"
sitemap_url = f"https://{company}.icims.com/sitemap.xml"
response = requests.get(sitemap_url, timeout=30)
response.raise_for_status()
root = ET.fromstring(response.content)
namespaces = {'ns': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
job_urls = []
for url in root.findall('ns:url', namespaces):
loc = url.find('ns:loc', namespaces)
lastmod = url.find('ns:lastmod', namespaces)
if loc is not None and '/jobs/' in loc.text:
job_urls.append({
'url': loc.text,
'lastmod': lastmod.text if lastmod is not None else None
})
print(f"Found {len(job_urls)} job URLs in sitemap")import requests
from bs4 import BeautifulSoup
import re
company = "careers-bcore"
listings_url = f"https://{company}.icims.com/jobs/search?in_iframe=1"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'text/html',
}
response = requests.get(listings_url, headers=headers, timeout=30)
soup = BeautifulSoup(response.content, 'html.parser')
jobs = []
for link in soup.select('a.iCIMS_Anchor[href*="/jobs/"]'):
href = link.get('href', '')
title = link.get('title') or link.get_text(strip=True)
# Extract job ID from URL pattern /jobs/{id}/
job_id_match = re.search(r'/jobs/(\d+)/', href)
job_id = job_id_match.group(1) if job_id_match else None
# Skip login / apply / referral links
if job_id and title and not any(x in href.lower() for x in ('/login', 'mode=apply', '/referral')):
jobs.append({
'id': job_id,
'title': title,
'url': href
})
print(f"Found {len(jobs)} jobs on listing page")import requests
import json
from bs4 import BeautifulSoup
job_url = "https://careers-bcore.icims.com/jobs/2931/applications-developer/job"
detail_url = f"{job_url}?in_iframe=1"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
}
response = requests.get(detail_url, headers=headers, timeout=30)
soup = BeautifulSoup(response.content, 'html.parser')
job = {}
# Preferred path: parse the embedded JobPosting JSON-LD
for script in soup.find_all('script', type='application/ld+json'):
try:
data = json.loads(script.string or '')
except (json.JSONDecodeError, TypeError):
continue
if isinstance(data, dict) and data.get('@type') == 'JobPosting':
job['title'] = data.get('title')
job['description'] = data.get('description')
job['employment_type'] = data.get('employmentType')
job['posted_at'] = data.get('datePosted')
job['closes_at'] = data.get('validThrough')
job['apply_url'] = data.get('url')
job['company'] = (data.get('hiringOrganization') or {}).get('name')
salary_value = (data.get('baseSalary') or {}).get('value') or {}
job['salary_min'] = salary_value.get('minValue')
job['salary_max'] = salary_value.get('maxValue')
break
# Fallback: parse server-rendered HTML when JSON-LD is absent or blank
if not job.get('title'):
heading = soup.select_one('h1.iCIMS_Header') or soup.select_one('h1')
job['title'] = heading.get_text(strip=True) if heading else None
if not job.get('description'):
sections = [s.get_text('\n', strip=True)
for s in soup.select('.iCIMS_InfoMsg_Job .iCIMS_Expandable_Text')]
job['description'] = '\n'.join(filter(None, sections)) or None
if not job.get('apply_url'):
apply_link = (soup.select_one('a[href*="mode=apply"]')
or soup.select_one('a.iCIMS_ApplyOnlineButton'))
job['apply_url'] = apply_link.get('href') if apply_link else None
print(job)import requests
from bs4 import BeautifulSoup
import re
import time
company = "careers-bcore"
all_jobs = []
seen = set()
page = 0
while True:
url = f"https://{company}.icims.com/jobs/search?pr={page}&in_iframe=1"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
}
response = requests.get(url, headers=headers, timeout=30)
soup = BeautifulSoup(response.content, 'html.parser')
job_links = soup.select('a.iCIMS_Anchor[href*="/jobs/"]')
new_count = 0
for link in job_links:
href = link.get('href', '')
title = link.get('title') or link.get_text(strip=True)
job_id_match = re.search(r'/jobs/(\d+)/', href)
if job_id_match and job_id_match.group(1) not in seen:
seen.add(job_id_match.group(1))
new_count += 1
all_jobs.append({
'id': job_id_match.group(1),
'title': title,
'url': href
})
# Stop when a page adds no new records (defensive termination)
if new_count == 0:
break
print(f"Page {page}: +{new_count} jobs (total: {len(all_jobs)})")
page += 1
# Be respectful - add delay between requests
time.sleep(1)
# Safety limit
if page > 100:
break
print(f"Total jobs found: {len(all_jobs)}")import requests
company = "careers-bcore"
robots_url = f"https://{company}.icims.com/robots.txt"
response = requests.get(robots_url, timeout=30)
robots_content = response.text
# Parse sitemap URL from robots.txt
sitemap_url = None
for line in robots_content.split('\n'):
if line.lower().startswith('sitemap:'):
sitemap_url = line.split(':', 1)[1].strip()
print(f"Sitemap found: {sitemap_url}")
break
# Note disallowed paths (e.g. /jobs/*login, /jobs/*referral, /jobs/*candidate)
disallowed = []
for line in robots_content.split('\n'):
if line.lower().startswith('disallow:'):
path = line.split(':', 1)[1].strip()
if path:
disallowed.append(path)
print(f"Disallowed paths: {disallowed}")iCIMS exposes no JSON endpoint for listings; the Jibe /api/jobs path returns server-rendered HTML, not JSON. Fetch the search page (/jobs/search on v1 tenants, /jobs/search?json=true on v2 internal-* tenants) and extract every /jobs/{id}/ anchor from the HTML.
The same employer can appear as careers-{co}.icims.com, internal-{co}.icims.com (v2, client-rendered), jobs-/jobs2-/staff-{co}.icims.com, or {co}.jibeapply.com. Detect the internal-* prefix to switch listings to ?json=true, strip the role prefix for a canonical tenant slug, and expect v2 detail pages to yield listings only (no server-side description body).
Some tenants populate the JobPosting JSON-LD description; others leave it empty and render the body only in HTML. Prefer the JSON-LD description, and when it is missing or blank, concatenate the .iCIMS_InfoMsg_Job .iCIMS_Expandable_Text sections (falling back to .iCIMS_Expandable_Text).
The public career page renders job content inside an iframe, so the bare URL returns the wrapper chrome instead of the job HTML. Append ?in_iframe=1 (or &in_iframe=1) to every listings and detail URL.
HTML labels arrive as US-CA-San Francisco or GB-ENG-London, and JSON-LD address fields sometimes contain the literal 'UNAVAILABLE'. Split the encoded prefix into city/state/country and drop 'UNAVAILABLE' values; JSON-LD jobLocation.address gives cleaner addressLocality/Region/Country when present.
Some tenants sit behind Cloudflare and block unheadered or aggressive clients. Send a desktop User-Agent, keep concurrency low (~3 detail fetches with ~500ms spacing), and back off on 403/429.
- 1Prefer JobPosting JSON-LD on detail pages for title, description, salary, employment type, and dates; fall back to iCIMS_ HTML sections when it is empty
- 2Use /sitemap.xml to enumerate every /jobs/ URL in a single request instead of paging the search HTML
- 3Always append ?in_iframe=1 so responses contain just the job content, not the wrapper page
- 4Detect internal-* (v2) tenants and request /jobs/search?json=true; use /jobs/search for v1 hosts
- 5Strip careers-/internal-/jobs-/staff- prefixes to a canonical tenant slug so candidate and internal boards deduplicate together
- 6Keep concurrency low (~3) with a desktop User-Agent to avoid Cloudflare blocks and mobile redirects
One endpoint. All iCIMS jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=icims" \
-H "X-Api-Key: YOUR_KEY" Access iCIMS
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.