# Personio, Softgarden & JOIN Jobs Scraper – DACH ATS (`ninhothedev/dach-ats-jobs-scraper`) Actor

Scrape jobs from Personio, Softgarden, JOIN, Recruitee, Teamtailor, SmartRecruiters & Workable career pages. Auto-detects the ATS from any company URL. For recruiters, staffing & sales teams.

- **URL**: https://apify.com/ninhothedev/dach-ats-jobs-scraper.md
- **Developed by:** [ninhothedev](https://apify.com/ninhothedev) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 3 total users, 2 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

## Personio, Softgarden & JOIN Jobs Scraper – DACH ATS

Scrape job postings from the career pages of the applicant-tracking systems German, Austrian and Swiss companies actually use: **Personio, Softgarden, JOIN, Recruitee, Teamtailor, SmartRecruiters and Workable**. Paste career URLs, slugs or just company websites. The actor detects the ATS, reads its public job feed and returns clean, flat job data: title, department, location, country, remote flag, employment type, seniority, salary, posting date, apply link and full description text. You don't need an API key, login or proxy, and you pay per job.

LinkedIn and Indeed only show part of the market, and the data arrives late. Most DACH SMEs and scale-ups publish every opening first on their own Personio or Softgarden page. This actor reads those pages directly.

### What data can you extract?

| Field | Example |
|---|---|
| `company` | tado GmbH |
| `ats` | personio / softgarden / join / recruitee / teamtailor / smartrecruiters / workable |
| `job_id` | 2502124 |
| `title` | Team Lead Accounting (m/f/d) |
| `department` | Finance |
| `location`, `city`, `country` | Germany - Munich · Munich · DE |
| `remote`, `workplace_type` | false · onsite / hybrid / remote |
| `employment_type` | Permanent, Full-time |
| `seniority` | Experienced / Junior / Student … |
| `salary` (+ `salary_min`, `salary_max`, `salary_currency`, `salary_period`) | 80.000–100.000 EUR / year |
| `posted_at` | 2026-01-22T09:48:25+00:00 |
| `url`, `apply_url` | https://tado.jobs.personio.de/job/2502124 |
| `description_text` | Full description as plain text |
| `language` | de / en |
| `company_slug`, `category`, `career_page`, `scraped_at` | extra context |

Field names match our [ATS Jobs Scraper (Greenhouse & Lever)](https://apify.com/ninhothedev/ats-jobs-scraper) wherever the two overlap, so you can merge the datasets.

#### Supported ATS and accepted inputs

| ATS | Example input |
|---|---|
| Personio | `https://acme.jobs.personio.de` (or `.com`) |
| Softgarden | `https://acme.softgarden.io` |
| JOIN | `https://join.com/companies/acme` |
| Recruitee | `https://acme.recruitee.com` (custom domains too) |
| Teamtailor | `https://acme.teamtailor.com` (custom domains too) |
| SmartRecruiters | `https://jobs.smartrecruiters.com/Acme` |
| Workable | `https://apply.workable.com/acme` |
| Any website | `acme.de` or `https://www.acme.com/karriere`: the ATS is detected automatically |
| Bare slug | `acme`: all ATS are probed, or you can set the ATS field |

### How to use

1. Add companies: career URLs, slugs or plain websites.
2. Optionally filter by keywords in the title, location (city, country or ISO code), department or remote only.
3. Click **Start** and download the results as JSON, CSV or Excel, or fetch them via the API.

```json
{
  "companies": ["https://tado.jobs.personio.de", "feri.softgarden.io", "join.com/companies/pflegia", "amperecloud.com"],
  "keyword": "engineer, entwickler, sales",
  "location": "Berlin, München, AT, CH",
  "remoteOnly": false,
  "includeDescription": true,
  "maxJobsPerCompany": 0,
  "maxItems": 500
}
```

### Output example

Real item from a test run:

```json
{
  "company": "tado GmbH",
  "ats": "personio",
  "job_id": "2502124",
  "title": "Team Lead Accounting (m/f/d)",
  "department": "Finance",
  "location": "Germany - Munich",
  "city": "Munich",
  "country": "DE",
  "remote": false,
  "employment_type": "Permanent, Full-time",
  "seniority": "Experienced",
  "salary": "80.000–100.000 EUR / year",
  "posted_at": "2026-01-22T09:48:25+00:00",
  "url": "https://tado.jobs.personio.de/job/2502124",
  "apply_url": "https://tado.jobs.personio.de/job/2502124",
  "description_text": " …",
  "language": "",
  "company_slug": "tado",
  "workplace_type": "",
  "salary_min": 80000,
  "salary_max": 100000,
  "salary_currency": "EUR",
  "salary_period": "year",
  "category": "People & Culture",
  "career_page": "https://tado.jobs.personio.de",
  "scraped_at": "2026-10-15T18:57:56+00:00"
}

```

A `RUN_SUMMARY` record in the key-value store lists, for each input, the detected ATS, how it was detected, jobs listed and saved, and any error.

### Pricing

Pay per result: **$1.50 per 1,000 jobs**, plus a tiny start fee. 1,000 jobs cost about $1.50. Use `maxItems` to cap the cost. Runs are plain HTTP requests (no browser, no proxy), so they are fast and cheap. 100 companies usually finish in a few minutes.

### Use cases

- **Recruiters and staffing agencies:** monitor the career pages of target clients and find new openings before they reach job boards.
- **Sales teams:** use hiring signals as buying intent. A company hiring "Head of Finance" or 10 engineers is a lead.
- **HR-tech and job boards:** aggregate DACH jobs from the ATS sources directly, normalised into one schema.
- **Market and salary research:** compare salary ranges (Personio, JOIN, Recruitee) and remote shares across companies.
- **Competitive intelligence:** track which teams competitors are building.
- **Investors:** watch headcount growth in portfolio or pipeline companies.

### Integrations

Use the actor via the [Apify API](https://docs.apify.com/api/v2), schedules, webhooks, or connect it to **Make, Zapier, n8n, Google Sheets, Airtable, Slack** and others.

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("ninhothedev/dach-ats-jobs-scraper").call(run_input={
    "companies": ["https://tado.jobs.personio.de", "grohe"], "keyword": "engineer"})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["company"], job["title"], job["url"])
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('ninhothedev/dach-ats-jobs-scraper').call({ companies: ['join.com/companies/pflegia'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

### FAQ

**Is this legal?** The actor reads only public job postings that companies publish so applicants can find them, using the feeds the ATS provides for career pages. No personal data or logins are involved.

**How does auto-detection work?** For a website, the actor loads the homepage and the careers pages and looks for links to a supported ATS. It also recognises boards on custom domains (Recruitee, Teamtailor, Softgarden). If nothing turns up, it tries the brand name as a slug on every ATS. Companies whose job widget loads only through JavaScript may still need their ATS URL.

**Why do some fields stay empty?** Each ATS publishes different data. Salary appears only if the company publishes it. Personio feeds sometimes omit descriptions. Softgarden lists no department.

**Do I need proxies?** No. All sources are public, light-weight feeds.

**Can I schedule it?** Yes. Use an Apify schedule (for example daily) and a webhook or integration to push new jobs to your CRM or Google Sheet.

**Something broken or an ATS missing?** Open an issue on the actor page. We usually respond within 24 h.

### Related actors by ninhothedev

- [ATS Jobs Scraper – Greenhouse & Lever](https://apify.com/ninhothedev/ats-jobs-scraper)
- [XING HR Scraper](https://apify.com/ninhothedev/xing-hr-scraper)

# Actor input Schema

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

One entry per company. Accepts ATS career-page URLs (https://acme.jobs.personio.de, https://acme.softgarden.io, https://join.com/companies/acme, https://acme.recruitee.com, https://acme.teamtailor.com, https://jobs.smartrecruiters.com/Acme, https://apply.workable.com/acme), bare slugs ('acme' – the ATS is probed automatically or taken from the 'ATS' field) or a plain company website (acme.de) – the actor then finds the ATS on the site.

## `ats` (type: `string`):

Which ATS a bare slug (e.g. 'tado') belongs to. 'Auto-detect' checks all supported systems and uses the one with open jobs. URLs and websites are always detected automatically.

## `detectFromWebsite` (type: `boolean`):

For plain company domains (acme.de, https://www.acme.com/karriere) fetch the homepage and careers pages, find the linked ATS board and scrape it. Falls back to trying the brand name as slug on every ATS.

## `keyword` (type: `string`):

Only keep jobs whose title contains any of these comma-separated words, e.g. 'engineer, entwickler, developer'. Case-insensitive.

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

Also match keywords inside the job description (broader, slower for Softgarden/JOIN/SmartRecruiters because each job page is loaded).

## `location` (type: `string`):

Only keep jobs whose location matches any of these comma-separated values: city ('Berlin, München'), country name ('Germany', 'Österreich', 'Schweiz') or ISO code ('DE, AT, CH').

## `department` (type: `string`):

Only keep jobs whose department/category contains any of these comma-separated values, e.g. 'Sales, Vertrieb'.

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

Only return fully remote jobs (ATS remote flag or 'Remote' / 'Homeoffice' in the location).

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

Add the full job description as clean plain text (HTML removed). Turn off for faster, smaller runs.

## `language` (type: `string`):

Language requested from boards that offer several (Personio feed, Softgarden, JOIN). Jobs are returned in the language the company published them in.

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

Stop after this many matching jobs per company. 0 = no limit.

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

Stop the whole run after this many jobs (controls cost). 0 = no limit.

## Actor input object example

```json
{
  "companies": [
    "https://tado.jobs.personio.de",
    "https://amperecloud.recruitee.com",
    "https://sandboxinteractive.teamtailor.com",
    "https://feri.softgarden.io",
    "https://join.com/companies/digitalesautohaus",
    "https://apply.workable.com/soulhouse"
  ],
  "ats": "auto",
  "detectFromWebsite": true,
  "searchInDescription": false,
  "remoteOnly": false,
  "includeDescription": true,
  "language": "",
  "maxJobsPerCompany": 20,
  "maxItems": 100
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "companies": [
        "https://tado.jobs.personio.de",
        "https://amperecloud.recruitee.com",
        "https://sandboxinteractive.teamtailor.com",
        "https://feri.softgarden.io",
        "https://join.com/companies/digitalesautohaus",
        "https://apply.workable.com/soulhouse"
    ],
    "maxJobsPerCompany": 20,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("ninhothedev/dach-ats-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 = {
    "companies": [
        "https://tado.jobs.personio.de",
        "https://amperecloud.recruitee.com",
        "https://sandboxinteractive.teamtailor.com",
        "https://feri.softgarden.io",
        "https://join.com/companies/digitalesautohaus",
        "https://apply.workable.com/soulhouse",
    ],
    "maxJobsPerCompany": 20,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("ninhothedev/dach-ats-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 '{
  "companies": [
    "https://tado.jobs.personio.de",
    "https://amperecloud.recruitee.com",
    "https://sandboxinteractive.teamtailor.com",
    "https://feri.softgarden.io",
    "https://join.com/companies/digitalesautohaus",
    "https://apply.workable.com/soulhouse"
  ],
  "maxJobsPerCompany": 20,
  "maxItems": 100
}' |
apify call ninhothedev/dach-ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ninhothedev/dach-ats-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/Bv4HYV6ensm9hMEM9/builds/vZ8kAUAIqgAXDlpuD/openapi.json
