# Cornerstone Career Site Jobs Scraper (`gefese/cornerstone-jobs-scraper`) Actor

Scrapes public job postings from Cornerstone OnDemand (CSOD) career sites: title, locations, posting and expiry dates, full description and job URL. Unofficial tool, not affiliated with Cornerstone OnDemand or any employer.

- **URL**: https://apify.com/gefese/cornerstone-jobs-scraper.md
- **Developed by:** [Gonzalo Fajardo](https://apify.com/gefese) (community)
- **Categories:** Jobs
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.05 / 1,000 result items

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Cornerstone Career Site Jobs Scraper

Extract public job postings from career sites that run on Cornerstone OnDemand (CSOD), for example `https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel`. Paste one or more career-site URLs and get one clean dataset row per job, with the full description. This is an unofficial tool. It is not affiliated with, endorsed by or sponsored by Cornerstone OnDemand or by any employer whose jobs it reads.

The actor uses plain HTTP requests, with no browser, no login and no proxy. It loads the career-site page once, the same way a visitor's browser does, to get the short-lived anonymous access token that the page itself uses, and then reads the same public job-search endpoint that the career site's front end calls. It reads the tenant's robots.txt at the start of every run, waits the Crawl-delay the tenant publishes, and fetches one request at a time.

### What data does it return?

For each job:

- `tenant`, `careerSiteId`, `requisitionId`: which career site the job is on and its requisition id
- `title`
- `locations`: an array of `{city, state, country}` objects (country is a two-letter code, missing parts are `null`) and `location`, the same locations joined into one string
- `postedDate` and `expirationDate`: ISO 8601 dates (`YYYY-MM-DD`); `expirationDate` is `null` when the job has no end date
- `descriptionText`: the job description as plain text. E-mail addresses inside descriptions are replaced with `[email removed]`.
- `descriptionHtml`: only filled when the employer's ad contains HTML markup, otherwise `null`
- `jobUrl`: the public page of the job; `careerSiteUrl`; `scrapedAt` (UTC timestamp)

The job-search endpoint of these career sites publishes only these job fields. It does not provide department, employment type or salary, so the actor does not invent them. Only a fixed list of fields is copied from the response; recruiter, hiring-manager and other contact-person data are never copied.

### How to run it

1. Open the actor and keep the prefilled input, or replace the URL with the career site of another employer. The URL looks like `https://<tenant>.csod.com/ux/ats/careersite/<siteId>/home?c=<tenant>`; the tenant and the career site id are read from it. If you paste only `https://<tenant>.csod.com/`, career site 1 is used.
2. Optionally enter search keywords, the same as typing them into the search box of the career site.
3. Set "Maximum jobs". It is a total for the whole run, across all URLs. The prefill is 20, with Henkel first, then IATA and Instem as further URLs that are only read if the earlier career sites return fewer than 20 jobs in total.
4. Click Start. When the run ends, open the Output tab to view, filter or export the dataset as JSON, CSV or Excel.

A URL whose tenant does not exist, whose career site id is wrong or that is not a `csod.com` address is skipped with an error line in the log, and the remaining URLs are still processed. If every URL is skipped, the run ends as failed with a message that says so.

### How much does it cost to scrape Cornerstone career sites?

Pricing is pay per event: **0.0015 USD per job in the dataset** (one `result` event per row). Example: 1,000 jobs cost 1.50 USD, 100 jobs cost 0.15 USD. Platform usage is not billed separately to you under pay-per-event pricing. Set a maximum total charge in the run options to cap spending; the actor stops cleanly when the limit is reached and reports how many jobs it stored.

### Input example

```json
{
  "startUrls": [
    { "url": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel" },
    { "url": "https://iata.csod.com/ux/ats/careersite/1/home?c=iata" },
    { "url": "https://instem.csod.com/ux/ats/careersite/1/home?c=instem" }
  ],
  "searchText": "",
  "maxItems": 20
}
```

### Output example

Three real rows from local runs on 2026-10-03 (the description is shortened here to 200 characters):

```json
[
  {
    "tenant": "henkel",
    "careerSiteId": 1,
    "requisitionId": 89183,
    "title": "Internship as Global Commercial Excellence Assistant",
    "locations": [
      {
        "city": "Milano",
        "state": "LOMBARDY",
        "country": "IT"
      }
    ],
    "location": "Milano, LOMBARDY, IT",
    "postedDate": "2026-10-02",
    "expirationDate": null,
    "descriptionText": "About this Position At Henkel, you can build on a strong legacy and leading positions in both industrial and consumer businesses to reimagine and improve life every day. If you love challenging the st…",
    "descriptionHtml": null,
    "jobUrl": "https://henkel.csod.com/ux/ats/careersite/1/home/requisition/89183?c=henkel",
    "careerSiteUrl": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel",
    "scrapedAt": "2026-10-03T12:18:17Z"
  },
  {
    "tenant": "henkel",
    "careerSiteId": 1,
    "requisitionId": 88548,
    "title": "Intern Project Engineer",
    "locations": [
      {
        "city": "Scheemda",
        "state": "GRONINGEN",
        "country": "NL"
      }
    ],
    "location": "Scheemda, GRONINGEN, NL",
    "postedDate": "2026-10-02",
    "expirationDate": null,
    "descriptionText": "About this Position Please only apply if you are currently studying in the Netherlands or if you graduated no more than 3 months before the internship start date. Applications that do not meet these c…",
    "descriptionHtml": null,
    "jobUrl": "https://henkel.csod.com/ux/ats/careersite/1/home/requisition/88548?c=henkel",
    "careerSiteUrl": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel",
    "scrapedAt": "2026-10-03T12:18:17Z"
  },
  {
    "tenant": "iata",
    "careerSiteId": 1,
    "requisitionId": 3816,
    "title": "Intern, Market and Consumer Insights",
    "locations": [
      {
        "city": "Geneva",
        "state": null,
        "country": "CH"
      }
    ],
    "location": "Geneva, CH",
    "postedDate": "2026-10-02",
    "expirationDate": "2026-10-14",
    "descriptionText": "About the team you are joining Are you passionate about market research? Interested in being an integral team member of the Market Consumer Insights team where you would work and learn about how to co…",
    "descriptionHtml": null,
    "jobUrl": "https://iata.csod.com/ux/ats/careersite/1/home/requisition/3816?c=iata",
    "careerSiteUrl": "https://iata.csod.com/ux/ats/careersite/1/home?c=iata",
    "scrapedAt": "2026-10-03T12:18:38Z"
  }
]
```

### Limitations and the free plan

- Only public career sites on `<tenant>.csod.com` are supported. Employers that put a custom domain in front of their Cornerstone site are not supported unless the `csod.com` address still works.
- The job-search endpoint is an internal interface of the career-site front end, not a documented API. If Cornerstone changes it, the actor will need an update.
- The tenant host publishes a Crawl-delay of 10 seconds, so every run spends about 10 seconds before the first job, and a run that stored jobs waits 10 more seconds before it exits (so the dataset counters settle); the prefilled run takes about 26 seconds. In local tests, 20 jobs took 14 seconds, 250 jobs from one tenant took 17 seconds and a three-tenant run with 38 jobs took 37 seconds.
- Job counts and content change from day to day, and some tenants have no open jobs at a given moment (one of the tenants tested had zero). A run on such a tenant finishes with 0 jobs and no charge; the log and the run's status message then carry a warning. If an unfiltered search returns no jobs, the actor loads the career site again with a new anonymous session up to two times before it reports the warning.
- Descriptions are returned as the employer wrote them; a few test postings contain only placeholder text.
- On the free plan you can run the prefilled input and similar small runs within the monthly free credit. Larger scrapes are billed at the per-job price above.

### FAQ

**Is this the official Cornerstone API?** No. It reads public job listings the same way the career site's own page does, without any login.

**Can I get candidate or recruiter data?** No. The actor only returns published job ad fields and never reads application or candidate data.

**A URL was skipped. Why?** Check the run log: the message names the reason, for example an unknown tenant, a career site id that does not exist or a robots.txt that disallows the path.

**Where do I report a problem?** Use the Issues tab of this actor on Apify and include the career-site URL and the run ID.

# Actor input Schema

## `startUrls` (type: `array`):

Public Cornerstone OnDemand career sites, for example https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel. The tenant and the career site id are read from the URL; one URL per career site.

## `searchText` (type: `string`):

Optional keywords, the same as typing into the search box on the career site. Leave empty for all jobs.

## `maxItems` (type: `integer`):

Stops after this many jobs in total across all career site URLs, in the order given. Each job is one billed result.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel"
    },
    {
      "url": "https://iata.csod.com/ux/ats/careersite/1/home?c=iata"
    },
    {
      "url": "https://instem.csod.com/ux/ats/careersite/1/home?c=instem"
    }
  ],
  "searchText": "",
  "maxItems": 20
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel"
        },
        {
            "url": "https://iata.csod.com/ux/ats/careersite/1/home?c=iata"
        },
        {
            "url": "https://instem.csod.com/ux/ats/careersite/1/home?c=instem"
        }
    ],
    "searchText": "",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("gefese/cornerstone-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [
        { "url": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel" },
        { "url": "https://iata.csod.com/ux/ats/careersite/1/home?c=iata" },
        { "url": "https://instem.csod.com/ux/ats/careersite/1/home?c=instem" },
    ],
    "searchText": "",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("gefese/cornerstone-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://henkel.csod.com/ux/ats/careersite/1/home?c=henkel"
    },
    {
      "url": "https://iata.csod.com/ux/ats/careersite/1/home?c=iata"
    },
    {
      "url": "https://instem.csod.com/ux/ats/careersite/1/home?c=instem"
    }
  ],
  "searchText": "",
  "maxItems": 20
}' |
apify call gefese/cornerstone-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gefese/cornerstone-jobs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jdaHnW0sjCEfRs9Ue/builds/aKkbjvdD9nTJcBy1W/openapi.json
