All platforms

Cornerstone Jobs API.

Cornerstone OnDemand learning and talent management suite with recruiting capabilities for enterprise organizations.

Cornerstone
Live
80K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Cornerstone
HenkelNHSBarclaysVolvoStarbucks
Developer tools

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.

Data fields
  • Enterprise focus
  • Learning integration
  • Talent management
  • Global coverage
  • Skills development
Use cases
  1. 01Enterprise talent acquisition
  2. 02Global workforce planning
  3. 03Learning and development tracking
  4. 04Internal mobility programs
Trusted by
HenkelNHSBarclaysVolvoStarbucks
DIY GUIDE

How to scrape Cornerstone.

Step-by-step guide to extracting jobs from Cornerstone-powered career pages—endpoints, authentication, and working code.

RESTadvanced~60 requests/minute (token-based)Auth required

Identify the Cornerstone URL pattern

Cornerstone OnDemand (CSOD) uses modern UX career sites with specific URL patterns. Identify the company subdomain and site ID from the careers page URL.

Step 1: Identify the Cornerstone URL pattern
# 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}")

Extract JWT token from the page

CSOD uses JWT Bearer tokens embedded in the page JavaScript. Load the career site page and extract the token along with the regional API endpoint using regex.

Step 2: Extract JWT token from the page
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}")

Call the job search API

Use the extracted token to call the regional job search API. The API returns full job descriptions in a single request. Include proper headers for authentication.

Step 3: Call the job search API
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")

Parse job details from the response

CSOD returns complete job information including full descriptions in the API response. Extract the relevant fields from each requisition object.

Step 4: Parse job details from the response
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'}")

Handle pagination

CSOD uses page-based pagination with totalCount for reference. Loop through pages until all jobs are collected, adding delays between requests.

Step 5: Handle pagination
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")

Handle token expiration

CSOD tokens expire after approximately 1 hour. Implement a refresh function to re-fetch the token when API returns 401 errors.

Step 6: Handle token expiration
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")
Common issues
highJWT token expired

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.

mediumRegional API endpoint unknown

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.

mediumCORS errors in browser

API requests require correct Origin and Referer headers matching the career site domain. Server-side requests avoid CORS issues entirely.

lowSite ID and Culture ID mismatch

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.

mediumLegacy URL format not supported

Some companies use legacy CSOD URLs without /ux/ats/careersite/. These may require different authentication. Modern UX URLs are publicly accessible and recommended.

lowRate limiting on large requests

Add delays between pagination requests (1 second minimum). For large job boards, consider spreading requests over time to avoid rate limits.

Best practices
  1. 1Always extract the cloud endpoint dynamically from the page JavaScript
  2. 2Implement token refresh logic to handle 1-hour expiration
  3. 3Use server-side requests to avoid CORS issues
  4. 4Add 1-second delays between pagination requests
  5. 5Cache job results daily to minimize API calls
  6. 6Extract site ID and culture ID from the URL for accurate results
Or skip the complexity

One endpoint. All Cornerstone jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://enterprise.jobo.world/api/jobs?sources=cornerstone" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

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.

99.9%API uptime
<200msAvg response
50M+Jobs processed