# Career Page Jobs API (Workday, Personio, Greenhouse +5) (`latentlark/ats-career-jobs`) Actor

Get all open jobs from company career pages as clean JSON. Paste websites or job board URLs; Workday, Personio, Recruitee, Greenhouse, Lever, Ashby, Workable and SmartRecruiters are auto-detected. Filter by keyword, location, remote, date, or get only new jobs.

- **URL**: https://apify.com/latentlark/ats-career-jobs.md
- **Developed by:** [LatentLark](https://apify.com/latentlark) (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

$1.50 / 1,000 job results

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

## Career Page Jobs API: Workday, Personio, Greenhouse, Lever, Ashby, Recruitee, Workable, SmartRecruiters

Get **every open job from any company's career page** as clean, normalized JSON. Give it a company website (`celonis.com`), a job board URL (`https://jobs.lever.co/spotify`, `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`) or `ats:slug` (`personio:ottonova`). The actor finds which applicant tracking system (ATS) the company uses and pulls the jobs from that ATS's **official public job-board feed**. It doesn't use a browser or proxies, and it doesn't break when a page layout changes.

One actor covers the enterprise ATS **Workday** (used by most Fortune 500 companies), the DACH/EU systems **Personio** and **Recruitee**, and the startup favourites Greenhouse, Lever, Ashby, Workable and SmartRecruiters.

![Career Page Jobs API results: one table of jobs from Workday, Personio, Greenhouse, Lever, Ashby, Recruitee, Workable and SmartRecruiters](https://api.apify.com/v2/key-value-stores/Tv899H9p8Kim9T8M2/records/career-jobs-results-table.png?signature=1l2EtzNmp3VIkEkfQT9KK)

*Real output from our test runs: 8 different ATS, one schema.* Want it in a spreadsheet? Send every run to Google Sheets with [Google Sheets Export & Import (No OAuth)](https://apify.com/latentlark/sheets-export) — see [Export jobs to Google Sheets](#export-jobs-to-google-sheets).

### Quick start (first run in about 30 seconds)

1. Click **Try for free** (or **Start** in Console). The form is prefilled with 3 companies: `personio:ottonova`, Spotify's Lever board and `huggingface.co`.
2. Click **Start**. You get about 100 jobs in the **Output** tab, which costs roughly $0.16.
3. Replace the companies with your own list. Company websites work (`celonis.com`), so you don't need to know which ATS they use.
4. Optional: add `keywords` / `locations`, set **Only new jobs since last run** to a name like `my-watchlist`, then **Save as task** and add a daily **Schedule**.

Tip: big Workday employers (NVIDIA, Adobe) have thousands of jobs. Set **Max results in total** (`maxResults`) on your first try to keep the cost predictable.

**How the first run looks** (fill the form, click Start, get results):

![Walkthrough: the prefilled form, click Start, then the jobs table in the Output tab](https://api.apify.com/v2/key-value-stores/Tv899H9p8Kim9T8M2/records/career-jobs-walkthrough.gif?signature=1m3uLe53AIkNP9Pth0SWt)

### Use cases

- **Hiring-signal lead lists (sales, agencies, investors):** a company opening 10 data roles is likely shopping for data tools. Feed your target accounts in, filter by `keywords` (e.g. `data`, `security`, `sales`), and turn new openings into warm leads for your CRM.
- **Job aggregators and niche job boards:** pull hundreds of company boards into one normalized feed every day, e.g. all AI jobs in Berlin, all remote Rust roles, all climate-tech companies.
- **Recruiter and agency monitoring:** watch your client and target companies and see new roles as soon as they appear on the company's own career page.
- **Competitor hiring tracking:** see which teams, locations and seniority levels your competitors are hiring for. Run it weekly and compare.
- **German & EU career pages:** **Personio** and **Recruitee** power a large share of DACH and EU companies (Mittelstand, startups, scale-ups) that most US-focused scrapers ignore. German titles like `(m/w/d)` and `Werkstudent` come through as-is, and `excludeKeywords` can drop them.
- **Scheduled runs with "only new jobs":** set `onlyNewSinceLastRunKey`, schedule the actor daily, and each run returns (and bills) only jobs that weren't there yesterday. Connect it to Google Sheets, Slack, email, Make, Zapier or n8n.
- **AI agents and LLM pipelines:** structured job data with plain-text descriptions, ready for RAG, matching or classification.
- **Market research:** salary ranges where boards publish them (Ashby, some Greenhouse and Lever boards), remote share, team growth.

### Why this instead of a single-ATS scraper

- **8 systems in one actor:** Workday, Personio, Recruitee, Greenhouse, Lever, Ashby, Workable and SmartRecruiters. A company list usually mixes them; with single-ATS scrapers you need one actor, one input format and one output format per system.
- **Auto-detection from the company domain:** paste `celonis.com` and the actor finds the board for you. No need to look up which ATS a company uses or what its board slug is.
- **One output schema:** the same fields (`company`, `title`, `location`, `workplaceType`, `postedAt`, `url`, `salary`, ...) for every ATS, so you can merge, filter and export without per-source mapping.
- **Pay per result:** you pay only for jobs saved to the dataset. Companies where no board is found cost nothing, and monitor mode only bills new jobs.
- **Workday without the 2,000-job cap:** large Workday boards are split and merged automatically.

### Supported ATS

| ATS | Typical URL | Notes |
|---|---|---|
| Personio | `company.jobs.personio.de` | XML feed, DE/EN |
| Recruitee | `company.recruitee.com` | Includes salary when published |
| Greenhouse | `boards.greenhouse.io/company` | US and EU instances |
| Lever | `jobs.lever.co/company` | US and EU instances, salary range |
| Ashby | `jobs.ashbyhq.com/company` | Compensation included |
| Workable | `apply.workable.com/company` | |
| SmartRecruiters | `jobs.smartrecruiters.com/Company` | Full descriptions |
| Workday | `company.wd5.myworkdayjobs.com/Site` or `wd5.myworkdaysite.com/recruiting/company/Site` | Exact posting date, country code, all locations. Boards over 2,000 jobs are split automatically |

### Input

| Field | What it does |
|---|---|
| `companies` (required) | A list of websites, job board URLs, `ats:slug` values or bare slugs |
| `keywords` / `excludeKeywords` | Match against the title (and the description if `searchInDescription` is on) |
| `locations`, `includeRemoteForLocations`, `remoteOnly` | Location filters |
| `postedWithinDays` | Keep only recent postings |
| `includeDescription`, `descriptionFormat` | `text` or `html` |
| `onlyNewSinceLastRunKey` | Monitor mode: output only jobs not seen in earlier runs that used the same key |
| `maxJobsPerCompany`, `maxResults`, `maxConcurrency` | Limits |

Example:

```json
{
  "companies": ["celonis.com", "https://jobs.lever.co/spotify", "personio:ottonova", "greenhouse:gitlab", "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
  "keywords": ["engineer", "data"],
  "locations": ["Berlin", "Munich", "Germany"],
  "includeRemoteForLocations": true,
  "onlyNewSinceLastRunKey": "my-watchlist",
  "maxResults": 500
}
```

### Output

Each dataset item is one job:

```json
{
  "ats": "greenhouse",
  "boardSlug": "celonis",
  "company": "Celonis",
  "jobId": "7012345",
  "title": "Senior Backend Engineer",
  "department": "Engineering",
  "location": "Munich, Germany",
  "locations": ["Munich, Germany"],
  "country": "Germany",
  "workplaceType": "hybrid",
  "employmentType": "full-time",
  "postedAt": "2026-10-20T09:12:00+00:00",
  "url": "https://job-boards.greenhouse.io/celonis/jobs/7012345",
  "applyUrl": "https://job-boards.greenhouse.io/celonis/jobs/7012345",
  "salary": {"min": null, "max": null, "currency": null, "interval": null, "text": null},
  "description": "About the role ...",
  "sourceInput": "celonis.com",
  "detectedVia": "website",
  "scrapedAt": "2026-10-24T10:00:00+00:00"
}
```

A run summary showing each input, the board it was detected as, its job count and any errors is saved in the key-value store under `SUMMARY`.

### How detection works

1. `ats:slug` values and job board URLs are used directly.
2. For a website, the actor loads the homepage and common career paths (`/careers`, `/jobs`, `/karriere`, ...) and looks for embedded ATS links or widgets.
3. If nothing is found, it tries the domain name as a slug on each ATS (except Workday, whose board address can't be guessed: give the Workday URL or the company website that links to it).
4. For a Workday URL without a site path (e.g. `https://adobe.wd5.myworkdayjobs.com/`), the public career site is read from the tenant's robots.txt.

If no board is found, the input is logged and listed in `SUMMARY`. Bad inputs never fail the whole run.

### Export jobs to Google Sheets

Get a **job sheet that fills itself every morning** with [Google Sheets Export & Import (No OAuth)](https://apify.com/latentlark/sheets-export). No "Sign in with Google" and no OAuth token that expires and silently stops your schedule:

![Jobs from Career Page Jobs API in a Google Sheet, added by Google Sheets Export & Import](https://api.apify.com/v2/key-value-stores/Tv899H9p8Kim9T8M2/records/jobs-in-google-sheets.png?signature=lWqMaV8G28EGnQjfChzv)

1. Share your sheet with `sheets-export@latentlark.iam.gserviceaccount.com` as **Editor**.
2. On this Actor (or your saved task), open **Integrations** → **Add integration** → **Actor**, pick **Google Sheets Export & Import (No OAuth)** and set the trigger to **Run succeeded**.
3. Use this input: `{"spreadsheet": "YOUR_SHEET_URL", "datasetId": "{{resource.defaultDatasetId}}", "mode": "append", "dedupeBy": ["url"]}`

Schedule this Actor daily (with `onlyNewSinceLastRunKey` set) and your sheet only gets the new jobs. Rows already in the sheet are skipped by `url`, and skipped rows aren't billed.

### Pricing

**Pay per result: $1.50 per 1,000 jobs** ($0.0015 per job). You only pay for jobs saved to the dataset. Inputs with no board found cost nothing. Use `maxResults`, keywords and location filters to control spend, and monitor mode so scheduled runs only pay for new jobs.

### Speed

It uses plain HTTP API calls with no browser or proxies. In tests on Apify, 20 companies (2,035 jobs) took 55 seconds and 5 companies (326 jobs) took 19 seconds.

### FAQ

**Does it need proxies?** No. The ATS feeds are public APIs.
**How do I find a company's board?** Just give its website. The actor checks the homepage and common career paths. If you already know the board, `greenhouse:stripe` or the board URL is fastest.
**Can I get only new jobs every day?** Yes. Set `onlyNewSinceLastRunKey` (for example `my-watchlist`) and schedule the actor. Seen job IDs are stored in a named key-value store in your account.
**How do I add a Workday company?** Paste any URL from its Workday career site, e.g. a job link like `https://mastercard.wd1.myworkdayjobs.com/CorporateCareers/job/...`, or use `workday:mastercard.wd1/CorporateCareers`. The company website also works if it links to Workday.
**Workday shows max 2,000 jobs per search. Do I get them all?** Yes. Above 2,000 the actor splits the search by Workday's own filters (job category, location) and merges the results without duplicates. In tests, NVIDIA's 2,743 jobs were all returned.
**Why did a company return 0 jobs?** Open the `SUMMARY` record in the run's key-value store. It shows which board was detected (or that none was found) for each input. Try the ATS board URL or `ats:slug` directly, or check that the company uses one of the 8 supported ATS.
**Which country is covered?** All of them. DACH/EU coverage is strong because of Personio and Recruitee.

### Legal and data notes

The actor reads only the **public job-board APIs that ATS vendors provide so that job postings can be syndicated**. It doesn't collect applicant data or personal profiles. Recruiter contact emails that some feeds expose are dropped on purpose. You are responsible for how you use the data.

### Limitations

- It only covers companies whose career page runs on one of the supported ATS. Custom in-house career sites aren't supported.
- Salary appears only where the company publishes it.
- Workday: with `includeDescription` off, `postedAt` is derived from Workday's relative "Posted N Days Ago" text and is empty for "30+ Days Ago". With descriptions on (the default), the exact posting date is used. Workday has no department field.
- Some field names vary between ATS, and `extra` keeps ATS-specific fields.

### More from LatentLark

- [Google Sheets Export & Import (No OAuth)](https://apify.com/latentlark/sheets-export): send any Actor's results to a Google Sheet on a schedule without Google login or expiring tokens.
- [Competitor Ads Monitor](https://apify.com/latentlark/competitor-ads-monitor): track competitor ads from Google Ads Transparency Center and LinkedIn Ad Library, with new-ad alerts.
- [Telegram Channel Monitor](https://apify.com/latentlark/telegram-channel-monitor): scrape and monitor public Telegram channels (posts, views, reactions) with no login, only-new mode and webhooks.

# Actor input Schema

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

One per line. Accepts a company website (e.g. ottonova.de), a career page URL, an ATS job board URL (e.g. https://jobs.lever.co/spotify), or an explicit board as ats:slug (personio:ottonova, recruitee:bunq, greenhouse:gitlab, lever:spotify, ashby:linear, workable:huggingface, smartrecruiters:BoschGroup, workday:nvidia.wd5/NVIDIAExternalCareerSite). Workday career-site URLs (myworkdayjobs.com, myworkdaysite.com) are detected automatically.

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

Keep jobs whose title contains at least one of these words (case-insensitive). Leave empty for all jobs.

## `searchInDescription` (type: `boolean`):

Also match keywords against the job description (requires 'Include description').

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words, e.g. Werkstudent, Intern.

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

Keep jobs whose location or country contains one of these strings, e.g. Berlin, München, Germany, DE.

## `includeRemoteForLocations` (type: `boolean`):

When a location filter is set, also keep fully remote jobs.

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

Keep only jobs marked as remote.

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

Only jobs posted in the last N days. 0 = no limit. (Jobs without a posting date are kept.)

## `onlyNewSinceLastRunKey` (type: `string`):

Set any name (e.g. 'my-watchlist') to output only jobs not seen in previous runs with the same key. Ideal for scheduled daily alerts. Leave empty to always get all jobs.

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

Include the full job description. Turn off for faster, smaller results.

## `descriptionFormat` (type: `string`):

Plain text is best for LLMs, spreadsheets and RAG; HTML keeps the original formatting.

## `personioLanguage` (type: `string`):

Optional language for Personio feeds, e.g. 'en' or 'de'. Default: the company's default language.

## `guessFromDomain` (type: `boolean`):

If a website does not link to its ATS, try boards named after the domain (e.g. ottonova.de -> personio:ottonova). Guessed boards are marked with detectedVia = 'slug-guess'.

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

Limit jobs per company, e.g. 50 to sample many companies cheaply. 0 = all jobs.

## `maxResults` (type: `integer`):

Stop after this many jobs in total. Handy to cap cost on a first try (each job costs $0.0015). 0 = no limit.

## `maxConcurrency` (type: `integer`):

How many companies are processed at the same time.

## Actor input object example

```json
{
  "companies": [
    "personio:ottonova",
    "https://jobs.lever.co/spotify",
    "huggingface.co"
  ],
  "searchInDescription": false,
  "includeRemoteForLocations": false,
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": true,
  "descriptionFormat": "text",
  "guessFromDomain": true,
  "maxJobsPerCompany": 0,
  "maxResults": 0,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `overview` (type: `string`):

All job postings found in this run (company, title, location, department, posting date, URLs, salary if published, 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 = {
    "companies": [
        "personio:ottonova",
        "https://jobs.lever.co/spotify",
        "huggingface.co"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("latentlark/ats-career-jobs").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": [
        "personio:ottonova",
        "https://jobs.lever.co/spotify",
        "huggingface.co",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("latentlark/ats-career-jobs").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": [
    "personio:ottonova",
    "https://jobs.lever.co/spotify",
    "huggingface.co"
  ]
}' |
apify call latentlark/ats-career-jobs --silent --output-dataset

```

## MCP server setup

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

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/sPL9tirnV87NMRnmc/builds/Xh56yJZtp3tD3fWys/openapi.json
