Cornerstone Jobs API.
Cornerstone OnDemand learning and talent management suite with recruiting capabilities for enterprise organizations.
Try the API.
Test Jobs, Feed, and Auto-Apply 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 Cornerstone.
- Enterprise focus
- Learning integration
- Talent management
- Global coverage
- Skills development
- 01Enterprise talent acquisition
- 02Global workforce planning
- 03Learning and development tracking
- 04Internal mobility programs
How to scrape Cornerstone.
Step-by-step guide to extracting jobs from Cornerstone-powered career pages—endpoints, authentication, and working code.
# Modern CSOD URL format:
# https://{company}.csod.com/ux/ats/careersite/{siteId}/home?c={company}
# Examples:
# https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel
# https://thekids.csod.com/ux/ats/careersite/4/home?c=thekids
company = "henkel"
site_id = 1
company_url = f"https://{company}.csod.com/ux/ats/careersite/{site_id}/home?c={company}"
print(f"Target URL: {company_url}")import re
import requests
response = requests.get(company_url)
html = response.text
# Extract token from embedded JavaScript
token_match = re.search(r'csod\.context\.token\s*=\s*["']([^"']+)["']', html)
token = token_match.group(1) if token_match else None
# Extract API region endpoint
cloud_match = re.search(r'csod\.context\.endpoints\.cloud\s*=\s*["']([^"']+)["']', html)
cloud_endpoint = cloud_match.group(1) if cloud_match else None
if not token:
raise Exception("Failed to extract JWT token")
print(f"Token length: {len(token)}")
print(f"Cloud endpoint: {cloud_endpoint}")import requests
api_url = f"{cloud_endpoint}/rec-job-search/external/jobs"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Origin": f"https://{company}.csod.com",
"Referer": f"https://{company}.csod.com/",
"Csod-Accept-Language": "en-US",
}
payload = {
"careerSiteId": site_id,
"careerSitePageId": 1,
"pageNumber": 1,
"pageSize": 25,
"cultureId": 1,
"searchText": "",
"cultureName": "en-US",
"states": [],
"countryCodes": [],
"cities": [],
"placeID": "",
"radius": None,
"postingsWithinDays": None,
"customFieldCheckboxKeys": [],
"customFieldDropdowns": [],
"customFieldRadios": [],
}
response = requests.post(api_url, headers=headers, json=payload)
data = response.json()
print(f"Found {data.get('data', {}).get('totalCount', 0)} total jobs")jobs = []
for req in data.get("data", {}).get("requisitions", []):
job = {
"id": req.get("requisitionId"),
"title": req.get("displayJobTitle"),
"description": req.get("externalDescription"), # Full description included
"locations": [
{
"city": loc.get("city"),
"state": loc.get("state"),
"country": loc.get("country"),
}
for loc in req.get("locations", [])
],
"posted_date": req.get("postingEffectiveDate"),
"expiration_date": req.get("postingExpirationDate"),
"detail_url": f"https://{company}.csod.com/ux/ats/careersite/{site_id}/home/requisition/{req.get('requisitionId')}?c={company}",
}
jobs.append(job)
print(f"Parsed {len(jobs)} jobs")
print(f"First job title: {jobs[0]['title'] if jobs else 'No jobs found'}")import time
all_jobs = []
page_number = 1
page_size = 25
while True:
payload["pageNumber"] = page_number
payload["pageSize"] = page_size
response = requests.post(api_url, headers=headers, json=payload)
data = response.json()
requisitions = data.get("data", {}).get("requisitions", [])
if not requisitions:
break
all_jobs.extend(requisitions)
total_count = data.get("data", {}).get("totalCount", 0)
print(f"Page {page_number}: {len(requisitions)} jobs (total: {len(all_jobs)}/{total_count})")
if len(all_jobs) >= total_count:
break
page_number += 1
time.sleep(1) # Rate limiting delay
print(f"Collected {len(all_jobs)} total jobs")import re
import requests
def get_fresh_token(company_url: str) -> tuple[str, str]:
"""Fetch a fresh JWT token and cloud endpoint from the career site."""
response = requests.get(company_url)
html = response.text
token_match = re.search(r'csod\.context\.token\s*=\s*["']([^"']+)["']', html)
cloud_match = re.search(r'csod\.context\.endpoints\.cloud\s*=\s*["']([^"']+)["']', html)
if not token_match:
raise Exception("Failed to extract JWT token")
return token_match.group(1), cloud_match.group(1) if cloud_match else None
def fetch_jobs_with_retry(company: str, site_id: int, max_retries: int = 2):
"""Fetch jobs with automatic token refresh on 401 errors."""
company_url = f"https://{company}.csod.com/ux/ats/careersite/{site_id}/home?c={company}"
for attempt in range(max_retries + 1):
token, cloud_endpoint = get_fresh_token(company_url)
api_url = f"{cloud_endpoint}/rec-job-search/external/jobs"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
response = requests.post(api_url, headers=headers, json=payload)
if response.status_code == 401:
print(f"Token expired, refreshing... (attempt {attempt + 1})")
continue
response.raise_for_status()
return response.json()
raise Exception("Failed to fetch jobs after token refresh")CSOD tokens expire after approximately 1 hour. Implement token refresh logic that re-fetches the career site page and extracts a new token when API returns 401 errors.
The cloud endpoint varies by company region. Always extract it from csod.context.endpoints.cloud in the page JavaScript. Common regions include uk.api.csod.com, eu-fra.api.csod.com, and na.api.csod.com.
API requests require correct Origin and Referer headers matching the career site domain. Server-side requests avoid CORS issues entirely.
Site ID and Culture ID are company-specific. Extract these from the URL or page context. Default to siteId=1 and cultureId=1 (en-US) if unknown.
Some companies use legacy CSOD URLs without /ux/ats/careersite/. These may require different authentication. Modern UX URLs are publicly accessible and recommended.
Add delays between pagination requests (1 second minimum). For large job boards, consider spreading requests over time to avoid rate limits.
- 1Always extract the cloud endpoint dynamically from the page JavaScript
- 2Implement token refresh logic to handle 1-hour expiration
- 3Use server-side requests to avoid CORS issues
- 4Add 1-second delays between pagination requests
- 5Cache job results daily to minimize API calls
- 6Extract site ID and culture ID from the URL for accurate results
One endpoint. All Cornerstone jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://enterprise.jobo.world/api/jobs?sources=cornerstone" \
-H "X-Api-Key: YOUR_KEY" Access Cornerstone
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.