# Workday, SmartRecruiters & Personio Jobs Scraper (`scrapefy/workday-jobs-scraper`) Actor

Scrape open jobs from Workday, SmartRecruiters and Personio career sites in one run, same job schema as our ATS Jobs Scraper. Filter by keyword, location, remote and posted date. No personal data.

- **URL**: https://apify.com/scrapefy/workday-jobs-scraper.md
- **Developed by:** [Scrapify](https://apify.com/scrapefy) (community)
- **Categories:** Jobs, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Workday, SmartRecruiters & Personio Jobs Scraper: open jobs from three career-site systems in one run

Scrape **open jobs from company career sites that run on Workday, SmartRecruiters or Personio** and get one clean, normalised dataset: **title, company, department, location(s), remote flag, employment type, posted date, job URL, requisition ID and the job description as plain text**. Paste a list of career-site addresses, press Start and get all of them in one run; the ATS is detected from each address. Filter by **keyword, location, remote and posted-since** before you pay. Export to JSON, CSV or Excel, or call it from the API.

Use it for **job-market and hiring-trend research, lead lists for recruiters and staffing firms, competitor hiring signals, job aggregators and alerts, salary and skills datasets, and LLM/RAG pipelines**. Works with AI agents through the Apify MCP server. It reads the public JSON/XML endpoints the career pages themselves use; no login, no cookies, no proxy.

**Same schema as our [ATS Jobs Scraper](https://apify.com/santis_hermes/ats-jobs-scraper)**, which covers Greenhouse, Lever and Ashby. Use both and merge the datasets: the field names are identical. For Greenhouse, Lever or Ashby boards, use that actor.

### Why this one

- **Three systems, one run.** Workday (large employers), SmartRecruiters and Personio (European SMEs) together, one schema, one price.
- **Filters that work before charging.** Keywords (all words of a line must match), locations, remote only and posted in the last N days. Jobs that do not match are not returned and not charged.
- **Auto-detect from a careers URL.** Paste `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, `https://jobs.smartrecruiters.com/ServiceNow` or `https://mobimeo.jobs.personio.de`; tenant, server and site are parsed for you.
- **Optional full description** as plain text (HTML removed, length-capped).
- **Keeps running when a site does not cooperate.** A wrong address, unknown company, blocked or empty site gets one error row with the reason; every other site continues. A site that keeps answering 429 is stopped politely.
- **No personal data.** Job fields only. No recruiter or hiring-manager names, emails or phone numbers.
- **Cheap and predictable.** $0.0015 per job; `Max jobs per company` and `Max jobs in total` cap every run.
- **Run summary.** A `RUN_SUMMARY` record lists jobs and requests per career site, errors, HTTP statuses and charged events.

### Input

| Field | Meaning |
|---|---|
| Career site URLs | Career-site addresses, one per line (Workday, SmartRecruiters, Personio). Custom company domains cannot be auto-detected: copy the myworkdayjobs / smartrecruiters / personio address from the careers page |
| Workday sites / SmartRecruiters IDs / Personio names | Optional short forms (`adobe/wd5/external_experienced`, `Sodexo`, `mobimeo`) |
| Keywords | Optional. All words of at least one line must appear in title, department or (with descriptions on) description |
| Locations | Optional location texts (`india`, `london`) |
| Remote jobs only | Default off |
| Posted in the last N days | Optional |
| Include job description / Max description length | Default off / 4,000 characters |
| Max jobs per company / Max jobs in total | Default 200 / 500 |
| Remove duplicates | Default on |

### Output

Job row (same names as ATS Jobs Scraper): `company, platform (workday|smartrecruiters|personio), companyName, jobId, title, department, team, location, locations[], workplaceType, employmentType, postedAt, updatedAt, jobUrl, applyUrl, requisitionId, isRemote, salaryMin, salaryMax, salaryCurrency, salaryInterval, salarySummary, descriptionText (when requested), scrapedAt`, plus `careerSite` for Workday.

**Error row:** `type: "error", input, platform, company, error, httpStatus`. See `sample-output.json` for real rows.

Notes on the data:

- **Workday** shows only a relative age in its list ("Posted 3 Days Ago", "30+" for older), so `postedAt` is the day (midnight UTC) derived from it, or `null` for 30+ days; with descriptions on, the exact start date is used. Multi-location jobs show "3 Locations" in `location` unless the detail page is read (descriptions, location filter or remote filter on).
- **SmartRecruiters** gives release date, department, function and a remote/hybrid flag.
- **Personio** feeds contain the description text, office list, department, category, schedule and created date.
- Salary fields are part of the shared schema; these three systems do not publish structured pay, so they are `null`.
- Workday `jobId` is the requisition ID; SmartRecruiters `jobId` is the posting ID.

### Pricing

Pay per event: **$0.0015 per job** ($1.50 per 1,000 jobs) plus a $0.00005 start event per run. Error rows, filtered-out jobs and duplicates are not charged. A run returning 200 jobs costs about 30 cents. See PRICING.md for the benchmark.

### Public career-site data only, no personal data

The actor reads only public career-site endpoints (Workday `/wday/cxs/.../jobs`, SmartRecruiters `/v1/companies/<id>/postings`, Personio `/xml`) that are served to every visitor of a careers page. It returns **job posting fields only**: no recruiter or candidate data, no names, emails or phone numbers, no login, and it does not bypass anything. Job postings belong to the employers: you are responsible for using them in line with applicable law and each site's terms.

### Notes and limits

- Not an official Workday, SmartRecruiters or Personio product. Endpoints can change without notice.
- Workday returns 20 jobs per request, paced about one request per second; a 1,000-job company takes about a minute without descriptions, and descriptions need one more request per job.
- SmartRecruiters answers 200 with an empty list for an unknown company ID; the actor turns that into an error row. IDs are case-sensitive.
- A company that moved to another system, or uses a custom domain, cannot be found from the name alone.

### FAQ

**How much does it cost?** $0.0015 per job plus $0.00005 per run. 1,000 jobs = $1.50. Error rows are free.

**Do I need an API key, account or proxy?** No.

**Can I scrape LinkedIn or Indeed with it?** No. Only Workday, SmartRecruiters and Personio career sites. For Greenhouse, Lever and Ashby use our ATS Jobs Scraper.

**Can I get recruiter emails?** No. Job data only.

**Which export formats are available?** JSON, CSV, Excel, XML, HTML or RSS from the Console or API, or integrations such as Make, Zapier, n8n and Google Sheets.

**Something broke or a field is missing?** Open an issue on the Actor's Issues tab with the run link.

### Use with AI agents / MCP

Agents can call this Actor through the [Apify MCP server](https://mcp.apify.com). Let the agent find it with the `search-actors` tool ("workday jobs scraper") and call it by name:

- Actor: `santis_hermes/workday-jobs-scraper`
- Example prompt: "List the data engineer jobs posted in the last 7 days at NVIDIA and ServiceNow."
- Minimal input:

```json
{
  "careerSiteUrls": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite", "https://jobs.smartrecruiters.com/ServiceNow"],
  "keywords": ["data engineer"],
  "postedSinceDays": 7,
  "maxJobsPerCompany": 50
}
```

### Feedback

If this saved you time, a rating on the Store page helps others find it. If it did not work for you, please open an issue on the Issues tab with the run link and I will look at it.

# Actor input Schema

## `careerSiteUrls` (type: `array`):

Career-site addresses, one per line. Workday: the full address of the careers page, for example https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite (tenant, server and site name are read from it; a company name alone is not enough). SmartRecruiters: https://jobs.smartrecruiters.com/ServiceNow. Personio: https://mobimeo.jobs.personio.de. Custom company domains cannot be auto-detected: open the company's careers page and copy the myworkdayjobs, smartrecruiters or personio address.

## `workdayCompanies` (type: `array`):

Optional short form for Workday sites, one per line: tenant/server/site, for example adobe/wd5/external_experienced. Same as pasting the full address above.

## `smartrecruitersCompanies` (type: `array`):

Optional SmartRecruiters company IDs, one per line, for example Sodexo or BoschGroup. The ID is case-sensitive and is the last part of jobs.smartrecruiters.com/<ID>. An unknown ID returns an error row.

## `personioCompanies` (type: `array`):

Optional Personio company names, one per line, for example mobimeo (the first part of mobimeo.jobs.personio.de). Both the .de and .com domains are tried.

## `keywords` (type: `array`):

Optional. A job is kept if its title, department or (when 'Include job description' is on) description contains ALL words of at least one line, for example 'data engineer'. Leave empty for all jobs. Filtered-out jobs are not charged.

## `locations` (type: `array`):

Optional. Keep only jobs whose location contains one of these texts, for example india, london, germany. Matching is case-insensitive. Workday rows that only say '3 Locations' are checked through the job detail page.

## `remoteOnly` (type: `boolean`):

Keep only jobs marked or described as remote (SmartRecruiters remote flag, 'Remote' in the title or location). Workday jobs need an extra detail request each, so this run is slower.

## `postedSinceDays` (type: `integer`):

Optional. Keep only jobs posted within the last N days. Workday only shows a relative age ('Posted 3 Days Ago', everything older than 30 days is '30+'), so for Workday the date is approximate to the day.

## `includeDescription` (type: `boolean`):

Add the job description as plain text (HTML removed, cut at the length below). Workday and SmartRecruiters need one extra request per job, so the run is slower (about 1 job per second); price per job stays the same. Personio feeds include the text anyway.

## `descriptionMaxChars` (type: `integer`):

Cut the description text after this many characters. Only used when 'Include job description' is on.

## `maxJobsPerCompany` (type: `integer`):

Maximum number of jobs returned for each career site. Large employers have thousands of openings, so keep this small for a quick sample.

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

Hard cap on jobs returned over all career sites in this run. You are charged per job returned, so this caps the cost of a run.

## `dedupe` (type: `boolean`):

Drop a job that appears twice in the run (for example the same career site listed twice, or two keyword searches finding the same job).

## Actor input object example

```json
{
  "careerSiteUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://jobs.smartrecruiters.com/ServiceNow",
    "https://mobimeo.jobs.personio.de"
  ],
  "remoteOnly": false,
  "includeDescription": false,
  "descriptionMaxChars": 4000,
  "maxJobsPerCompany": 200,
  "maxItems": 500,
  "dedupe": true
}
```

# Actor output Schema

## `jobs` (type: `string`):

All jobs as a table with the key columns.

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

Every row with all fields.

## `runSummary` (type: `string`):

Jobs per career site, errors, HTTP statuses and charged events.

# 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 = {
    "careerSiteUrls": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://jobs.smartrecruiters.com/ServiceNow",
        "https://mobimeo.jobs.personio.de"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapefy/workday-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 = { "careerSiteUrls": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://jobs.smartrecruiters.com/ServiceNow",
        "https://mobimeo.jobs.personio.de",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("scrapefy/workday-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 '{
  "careerSiteUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://jobs.smartrecruiters.com/ServiceNow",
    "https://mobimeo.jobs.personio.de"
  ]
}' |
apify call scrapefy/workday-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapefy/workday-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/LxN3TosXMEwewV8e7/builds/G6SRlRQGbyRugSxk9/openapi.json
