# Workday, Personio, Breezy, Rippling - ATS Jobs API (`thedeadpoet/workday-personio-breezy-rippling-ats-jobs-api`) Actor

Scrape every open job from a company's Workday, Personio, Breezy or Rippling career site into one normalised schema. Paste a mixed list of career-site URLs, get one job-listings dataset back. No API key, no proxy, no browser.

- **URL**: https://apify.com/thedeadpoet/workday-personio-breezy-rippling-ats-jobs-api.md
- **Developed by:** [Pablo D](https://apify.com/thedeadpoet) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 job listing returneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Workday Jobs API — plus Personio, Breezy and Rippling, in one normalised schema

**Every ATS scraper on the market covers Greenhouse, Lever and Ashby. Almost none of them cover the enterprise.** The Fortune 500 does not recruit on Greenhouse — it recruits on **Workday**. Half of the DACH mid-market recruits on **Personio**. And the moment your job dataset has to include a company with more than a thousand employees, you discover the gap.

This Actor takes a list of career-site URLs and returns **one row per open job**, normalised across **Workday, Personio, Breezy HR and Rippling**. No API key, no proxy, no browser.

```
nvidia.wd5.myworkdayjobs.com/… ─┐                    ┌─ title, location, department
personio.jobs.personio.de      ─┼──►  one row each ──┼─ employmentType, isRemote, postedAt
breezy.breezy.hr               ─┤                    ├─ salaryText, url, applyUrl
ats.rippling.com/acme/jobs     ─┘                    └─ descriptionText + descriptionHtml
```

### Workday jobs API — the part that is normally hard

A Workday career site is a JavaScript application. Point a normal scraper at `nvidia.wd5.myworkdayjobs.com` and you get an empty shell. The job data lives behind the site's own public JSON endpoint, and to call it you need three things out of the URL: the **tenant**, the **data centre** (`wd1`, `wd3`, `wd5` …) and the **career-site name**.

This Actor parses all three out of any Workday URL you paste, pages the listing endpoint to the end, and then fetches each job's detail record to get the full description, the requisition ID, the employment type and every location on a multi-site posting. In a measured run, a single Workday tenant returned **2,000 open roles**.

You can also give it the parts explicitly: `workday:nvidia:wd5:NVIDIAExternalCareerSite`.

### Supported ATS platforms

| Platform | Entry format | Descriptions | Notes |
|---|---|---|---|
| **Workday** | `https://{tenant}.wd5.myworkdayjobs.com/{SiteName}` | Yes | One extra request per job. Enterprise-scale tenants, thousands of roles |
| **Personio** | `https://{company}.jobs.personio.de` | When the company fills them in | Personio's own documented public XML job feed. Heavily used across DACH |
| **Breezy HR** | `https://{company}.breezy.hr` | **No** — see limitations | Carries salary, department, employment type and every location |
| **Rippling** | `https://ats.rippling.com/{company}/jobs` | Yes | One extra request per job |

### The same schema as the Greenhouse/Lever/Ashby/Workable Actor

The output shape here is **field-for-field identical** to [Greenhouse, Lever, Ashby, Workable — Career Site Jobs API](https://apify.com/thedeadpoet/greenhouse-lever-ashby-workable-ats-jobs-api). Run both, concatenate the two datasets, and you have one job feed spanning eight applicant tracking systems with no mapping work — `source` tells you which platform each row came from.

### Input

```json
{
  "companies": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://personio.jobs.personio.de",
    "https://breezy.breezy.hr",
    "https://ats.rippling.com/rippling/jobs"
  ],
  "defaultProvider": "personio",
  "includeDescription": true,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 0,
  "maxJobsTotal": 0,
  "requestDelaySeconds": 1,
  "respectRobotsTxt": true,
  "emitErrorItems": false
}
```

### Output

```json
{
  "source": "workday",
  "companySlug": "nvidia",
  "companyName": null,
  "jobId": "JR2024319",
  "title": "ISV GTM Sales Strategist - DACH",
  "url": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/Germany-Munich/ISV-GTM-Sales-Strategist---DACH_JR2024319",
  "applyUrl": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/Germany-Munich/ISV-GTM-Sales-Strategist---DACH_JR2024319",
  "location": "Germany, Munich",
  "locations": ["Germany, Munich", "UK, Remote"],
  "country": "Munich",
  "department": null,
  "team": null,
  "employmentType": "Full time",
  "isRemote": false,
  "workplaceType": null,
  "postedAt": "2026-09-10T00:00:00+00:00",
  "updatedAt": null,
  "salaryText": null,
  "descriptionText": "NVIDIA has been transforming computer graphics, PC gaming and accelerated computing for more than 25 years...",
  "descriptionHtml": "<p>NVIDIA has been transforming...</p>",
  "boardUrl": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
  "scrapedAt": "2026-09-10T18:22:01+00:00"
}
```

### Use cases

- **Job boards and aggregators** — cover the enterprise employers your Greenhouse-only feed is missing.
- **Recruitment market research** — how many roles is a given enterprise advertising, in which departments, in which countries, week over week.
- **Competitor hiring intelligence** — a competitor opening twelve roles in one city is a signal that arrives before any announcement.
- **Candidate alerting** — `titleKeywords` plus `postedWithinDays: 3` and a daily schedule gives a focused feed.
- **DACH recruiting** — Personio coverage is the reason this Actor exists for anyone hiring in German-speaking Europe.

### Honest limitations

- **Breezy HR jobs have no description.** Breezy's public feed carries title, salary, department, employment type and locations, but not the job text, and Breezy publishes no public detail endpoint. `descriptionText` and `descriptionHtml` are `null` for Breezy rows rather than filled with something invented.
- **Personio descriptions depend on the company.** The feed exposes a `jobDescriptions` block, but plenty of tenants publish it empty. When it is empty, the field is `null`.
- **Workday `department` is usually `null`.** Workday exposes job families as search facets rather than as a field on the posting, so there is nothing honest to put there.
- **Workday costs one request per job** when `includeDescription` is on. On a 2,000-role tenant that is 2,000 requests — set `maxJobsPerCompany`, or turn descriptions off, unless you actually want all of it.
- **Workday publishes a relative posting date** ("Posted 5 Days Ago"), which is converted to a date. Expect day-level accuracy, not timestamp-level.
- **A tenant that does not exist returns a redirect**, which is reported as an error rather than as zero jobs, so you can tell "wrong token" from "nobody is hiring".
- **SmartRecruiters and Recruitee are deliberately not supported.** Their `robots.txt` disallows automated access for a generic user agent, so they are out of scope. This is a choice, not an omission.

### robots.txt and data sources

Every provider here exposes a public, unauthenticated career-site endpoint that the ATS publishes so job aggregators can consume it. No login, no anti-bot circumvention, no proxies, no browser. Checked 2026-09-10:

- `{tenant}.wdN.myworkdayjobs.com` — `Allow: /{SiteName}/`, with only `/talentcommunity/` and `/refreshFacet/` disallowed.
- `{company}.jobs.personio.de` — `/xml` is Personio's documented public job-feed export, published for job boards to consume.
- `{company}.breezy.hr` — only `/css`, `/fonts`, `/stylesheets` and `/javascripts` are disallowed.
- `ats.rippling.com` — only `/internal/` is disallowed.

On top of that, `respectRobotsTxt` is **on by default** and each career-site host's own `robots.txt` is fetched once per run: a tenant that disallows its job feed is skipped, not scraped. Crawl-delay directives are honoured.

### Works with

- [Greenhouse, Lever, Ashby, Workable — Career Site Jobs API](https://apify.com/thedeadpoet/greenhouse-lever-ashby-workable-ats-jobs-api) — the other four ATS platforms, same schema.
- [Website Tech Stack & Email Finder](https://apify.com/thedeadpoet/website-tech-stack-email-finder-wappalyzer-builtwith) — give it a domain and it tells you which ATS the company recruits with and its board token, which is exactly the input this Actor takes.
- [Remote Jobs Feed API](https://apify.com/thedeadpoet/remote-jobs-feed-api-himalayas-remotive-arbeitnow) — remote-first job boards.

### FAQ

**How do I find the Workday data centre and site name?**
They are both in the career-site URL. `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` gives tenant `nvidia`, data centre `wd5`, site `NVIDIAExternalCareerSite`. Paste the URL and the Actor works it out.

**Does it need a proxy?**
No. Every endpoint is public and unauthenticated. There is no anti-bot system to defeat because there is nothing being defended.

**Why is `country` sometimes odd on Workday rows?**
Workday's location strings are free text ("2 Locations", "Germany, Munich"), so `country` is parsed best-effort and left `null` when it cannot be determined honestly. The raw string is always in `location` and `locations`.

**Can I get only remote roles?**
`remoteOnly: true`. It keeps jobs the ATS flags as remote and jobs whose location text says so.

**What does it cost?**
Pay per event: a fixed fee to start the run, plus a small fee per job returned.

# Actor input Schema

## `companies` (type: `array`):

One entry per company. Accepted formats:
• A career-site URL - https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, https://personio.jobs.personio.de, https://breezy.breezy.hr, https://ats.rippling.com/rippling/jobs
• An explicit pair - personio:acme, breezy:acme, rippling:acme
• Workday needs the tenant, the data centre and the site name - workday:nvidia:wd5:NVIDIAExternalCareerSite

The Workday data centre (wd1, wd3, wd5 …) and the site name are both visible in the career-site URL.

## `defaultProvider` (type: `string`):

Used only for entries that are a bare token with no URL and no provider prefix. Workday cannot be a default because it also needs a data centre and a site name.

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

Adds descriptionText and descriptionHtml to every job. On Workday the description is only available from a per-job request, so this makes a Workday run roughly one request per job slower - and much more useful.

## `titleKeywords` (type: `array`):

Case-insensitive. A job is kept if its title contains at least one of these strings. Leave empty to keep every title.

## `excludeTitleKeywords` (type: `array`):

Case-insensitive. Jobs whose title contains any of these are dropped.

## `locationKeywords` (type: `array`):

Case-insensitive match against the job location string, e.g. "Berlin", "United Kingdom", "Remote".

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

Keep only jobs the ATS flags as remote, or whose location text contains "remote".

## `postedWithinDays` (type: `integer`):

0 = no date filter. Uses the posting date the ATS reports; jobs with no date are always kept. Workday publishes a relative date ("Posted 5 Days Ago"), which is converted to a date.

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

0 = no limit. Worth setting for Workday: a single enterprise tenant can publish several thousand roles.

## `maxJobsTotal` (type: `integer`):

0 = no limit. A hard stop so a large company list cannot run away with your budget.

## `requestDelaySeconds` (type: `number`):

Politeness delay between requests to the same career site. Raised automatically when a host's robots.txt asks for a longer crawl delay.

## `respectRobotsTxt` (type: `boolean`):

On by default. Each career-site host's own robots.txt is fetched once per run; a host that disallows its job feed is skipped rather than scraped.

## `emitErrorItems` (type: `boolean`):

If a career site returns 404 or an error, push a row with the error instead of silently skipping it. Useful when you are validating a large list of company tokens.

## Actor input object example

```json
{
  "companies": [
    "https://personio.jobs.personio.de",
    "https://breezy.breezy.hr",
    "https://ats.rippling.com/rippling/jobs"
  ],
  "defaultProvider": "personio",
  "includeDescription": true,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 0,
  "maxJobsTotal": 0,
  "requestDelaySeconds": 1,
  "respectRobotsTxt": true,
  "emitErrorItems": false
}
```

# Actor output Schema

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

One row per open job, normalised across Workday, Personio, Breezy and Rippling.

# 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 = {
    "companies": [
        "https://personio.jobs.personio.de",
        "https://breezy.breezy.hr",
        "https://ats.rippling.com/rippling/jobs"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thedeadpoet/workday-personio-breezy-rippling-ats-jobs-api").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 = { "companies": [
        "https://personio.jobs.personio.de",
        "https://breezy.breezy.hr",
        "https://ats.rippling.com/rippling/jobs",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("thedeadpoet/workday-personio-breezy-rippling-ats-jobs-api").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 '{
  "companies": [
    "https://personio.jobs.personio.de",
    "https://breezy.breezy.hr",
    "https://ats.rippling.com/rippling/jobs"
  ]
}' |
apify call thedeadpoet/workday-personio-breezy-rippling-ats-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thedeadpoet/workday-personio-breezy-rippling-ats-jobs-api"
        }
    }
}
```

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/cmqUhEtnsjA4rvskI/builds/u12BFkN62NWr7s26d/openapi.json
