# Personio Jobs Scraper (`redomic/personio-jobs`) Actor

Scrape open jobs from Personio company career pages (jobs.personio.de/.com XML feeds). Returns clean rows with title, department, office, employment, seniority, dates, URLs and descriptions. Supports monitor mode for new/changed/removed jobs.

- **URL**: https://apify.com/redomic/personio-jobs.md
- **Developed by:** [Redomic](https://apify.com/redomic) (community)
- **Categories:** Jobs, Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 results

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

## Personio Jobs Scraper: Open Jobs from Personio Career Pages

Get every open job from any company whose career page runs on Personio (`company.jobs.personio.de` or `.com`), as a spreadsheet or an API. Thousands of European small and mid-sized companies hire through Personio, and their jobs often don't reach the big job boards. Each row gives you the title, department, office, city and country, contract type, level, remote or onsite, the salary when the company states it, the posting date and the full description.

Run it on a schedule and you get only the jobs that are new, changed or taken down since the last run.

### Who uses it

- **Recruiters and agencies** watching the companies they place candidates with.
- **Job boards** that list European SME jobs and need them the day they're posted.
- **Sales teams** looking for companies that are hiring in a department they sell to.

### What you get

Four Personio companies, newest jobs first, on 15 November 2026:

| Company | Job | Department | City | Contract | Level | Salary | Posted |
|---|---|---|---|---|---|---|---|
| Personio | Business Development Representative | Go to Market | Berlin | permanent | entry-level | | 11 Nov |
| Solactive | Client Services Specialist | Client Services | Toronto | permanent | entry-level | | 10 Nov |
| Solactive | Intern, Equity Index Structuring | Product | Amsterdam | intern | student | | 5 Nov |
| Vivid | Product Engineer (Mid/Senior) | Customer experience | Remote | permanent | experienced | EUR 65,000 to 75,000 a year | 22 Oct |
| Vivid | Compliance Officer, France | Operations | Paris | permanent | experienced | EUR 50,000 to 60,000 a year | 2 Oct |

Each row also carries the job and apply URLs, the office and any extra offices, the country code, full-time or part-time, years of experience when stated, the occupation category, how many days the job has been open, how many roles the company has open in total, and the description as HTML and plain text. We also pull the skills, responsibilities, requirements and benefits out of the description into separate lists.

### Three modes

| Mode | What you get | Good for |
|---|---|---|
| `jobs` (default) | Every open job at the companies you list | Exports, one-off checks |
| `monitor` | Only jobs that are new, changed or removed since the last run | Daily alerts |
| `search` | A keyword search across a built-in list of Personio companies, no company names needed | Trying it out, quick market checks |

### Price

$1.50 per 1,000 jobs. There's no start fee. In monitor mode you pay for the first run's jobs and after that only for new and changed jobs, so a quiet day costs nothing. 100 jobs cost $0.15.

### How to run it

Add the companies you care about. You can use the part before `.jobs.personio.de`, or paste the career page URL:

```json
{
  "companies": ["solactive", "https://vivid.jobs.personio.de"],
  "maxItems": 100
}
```

Only engineering jobs in Germany with a posted salary:

```json
{
  "companies": ["solactive", "vivid", "personio"],
  "countries": ["Germany"],
  "departments": ["Engineering", "Tech"],
  "hasSalary": true
}
```

For alerts, save your company list as a task with `"mode": "monitor"` and schedule it daily. For a changed job, `changedFields` tells you what changed (title, office, salary) and `changes` gives the old and new values.

### Per-input coverage

Every run writes a per-input coverage report to the key-value store: `INPUT-REPORT` (JSON array) and `INPUT-REPORT.csv`. Each company gets one row in input order with `input`, `status`, `org`, `jobsFound`, `jobsEmitted`, and `error`.

### Input

| Field | Default | What it does |
|---|---|---|
| `companies` | `["solactive"]` | Personio subdomains or career page URLs. |
| `mode` | `jobs` | `jobs`, `monitor` or `search`. |
| `search` | none | Keep jobs whose title, location, department or description contains this text. |
| `titleKeywords`, `excludeTitleKeywords` | none | Keep or drop jobs by words in the title. |
| `locations`, `countries`, `cities`, `excludeLocations` | all | Filter by place. |
| `departments`, `jobFunctions` | all | Filter by department or job function. |
| `employmentTypes` | all | For example `permanent`, `intern`, `working_student`, `freelance`. |
| `seniorities` | all | For example `entry-level`, `experienced`, `student`, `executive`. |
| `schedules` | all | `full-time` or `part-time`. |
| `workplaceTypes`, `remoteOnly` | all | Remote, hybrid or onsite. |
| `hasSalary`, `minSalary`, `maxSalary` | off | Only jobs with a stated salary, or within a yearly range. |
| `skillsInclude`, `skillsExclude` | none | Keep or drop jobs that mention these skills. |
| `postedSinceDays`, `postedAfter` | none | Only recent jobs. |
| `excludeEvergreen` | `true` | Leave out jobs that have been up for a year or more. |
| `includeGeneralApplications` | `false` | Include open applications such as "Initiativbewerbung". |
| `includeDescription` | `true` | Add the full description. |
| `language` | `en` | The description language to request, for example `de`. |
| `sort` | `newest` | `newest` or `oldest`. |
| `includeUnchanged` | `false` | In monitor mode, also emit jobs still present and unchanged since the previous run as `changeType=unchanged` (never charged). |
| `maxItems` | `20` | The most jobs per company. |

### Output fields

`id`, `globalId`, `company`, `subcompany`, `title`, `department`, `recruitingCategory`, `office`, `additionalOffices`, `location`, `city`, `country`, `countryCode`, `workplaceType`, `employmentType`, `schedule`, `seniority`, `yearsOfExperience`, `occupation`, `occupationCategory`, `salary`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `createdAt`, `daysOpen`, `ageDays`, `companyOpenRoles`, `skills`, `responsibilities`, `requirements`, `benefits`, `keywords`, `descriptionText`, `descriptionHtml`, `url`, `applyUrl`, `firstSeenAt`, `lastSeenAt`, `scrapedAt`.

### FAQ

**Which companies work?**
Any company whose career page lives on Personio, at `name.jobs.personio.de` or `name.jobs.personio.com`. If the page isn't on Personio, the run tells you so.

**Where does the salary come from?**
Personio has no salary field, so we read it from the description when the company states a range. Numbers that aren't pay (revenue, funding) are ignored.

**How do I find a company's Personio name?**
Open their careers page. If the address looks like `acme.jobs.personio.de`, use `acme`.

**Something looks wrong in my results.**
Open an issue on the Issues tab with your input and we'll look at it.

### Auf Deutsch

Alle offenen Stellen von Unternehmen, deren Karriereseite auf Personio läuft, als Tabelle: Titel, Abteilung, Standort, Vertragsart, Level, Remote oder vor Ort, Gehalt (wenn angegeben), Datum und Beschreibung. Im Modus `monitor` bekommen Sie nur neue, geänderte und entfernte Stellen seit dem letzten Lauf.

### Use it from AI agents (MCP) and Python

**AI agents.** Add this Actor as a tool in Claude, Cursor, VS Code or any MCP client through the Apify MCP server. The agent reads the input schema, so a plain request is enough.

```json
{
  "mcpServers": {
    "personio-jobs": {
      "url": "https://mcp.apify.com?tools=redomic/personio-jobs",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

**Python.** `pip install apify-client`, then:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("redomic/personio-jobs").call(run_input={"companies": ["solactive"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

### More from us

- [Allabolag Scraper - Swedish Company Data](https://apify.com/redomic/allabolag-company-scraper): Scrape allabolag.se for Swedish company financials, industry, status and board role titles by search, org number or URL
- [Ashby Jobs Scraper with Salaries](https://apify.com/redomic/ashby-jobs-monitor): Ashby jobs scraper for any startup careers board (OpenAI, Ramp, Notion and thousands more), with salary ranges as numbers: min, max, currency, period, plus equity and bonus flags
- [ATS Jobs Search: Greenhouse, Lever & Ashby Jobs API](https://apify.com/redomic/ats-jobs-search): Search 300,000+ open jobs from 11,000+ companies on Greenhouse, Lever and Ashby in one run
- [Blocket Scraper: Car Monitor](https://apify.com/redomic/blocket-se-scraper): Blocket scraper: each run returns up to 2,500 Blocket Bil and Torget listings as flat rows with price, make, model, year, mileage, VIN, inspections and seller
- [Closed Jobs Tracker: Filled Roles & Time-to-Fill](https://apify.com/redomic/closed-jobs-tracker): Search closed jobs from 11,000+ companies with closedOn, daysOpen and time-to-fill signals
- [Google Jobs Scraper - Job Postings API](https://apify.com/redomic/google-jobs): Google Jobs and job postings scraper: get job title, company, location, posted date, salary, full description and every apply link for any search
- [Google Trends API: Trending Now & Interest Over Time](https://apify.com/redomic/google-trends): Trending-now searches and new-trends alerts/monitoring per country with the news behind them, plus interest over time, interest by region, and related and rising queries for any keyword
- [GovDeals Scraper](https://apify.com/redomic/govdeals-scraper): Search and monitor GovDeals government surplus auctions
- [Greenhouse Jobs Monitor & Scraper](https://apify.com/redomic/greenhouse-jobs-monitor): Monitor Greenhouse jobs from any company careers board (Airbnb, Stripe, Discord and thousands more)
- [Job Postings Dataset: 320k+ Open Jobs, Updated Daily](https://apify.com/redomic/job-postings-dataset): 320,000+ open jobs from 10,000+ company career sites (Greenhouse, Lever, Ashby), updated daily with first-seen and closed dates
- [Lever Jobs Scraper with Salaries](https://apify.com/redomic/lever-jobs-monitor): Lever jobs scraper for any company's careers page (Palantir, Spotify, Zoox, Shield AI and thousands more): flat rows with title, team, location, workplace, commitment, apply link, salary as numbers and the full description merged
- [Marktplaats.nl Scraper](https://apify.com/redomic/marktplaats-nl): Scrape Marktplaats.nl listings into clean rows: title, price in EUR, condition, seller, location, images
- [Otodom Scraper: Poland Property Listings & Price Monitor](https://apify.com/redomic/otodom-scraper): Scrape Otodom.pl property listings in Poland
- [Otomoto.pl Scraper](https://apify.com/redomic/otomoto-scraper): Scrape Otomoto.pl Polish car listings: one run returns used-car rows with price, make, model, year, mileage, fuel, gearbox, seller and URL
- [Salary Data API: Pay Ranges by Job Title & Level](https://apify.com/redomic/salary-data): Get salary benchmarks (p10-p90, annualised, with n) and pay ranges from 150,000+ employer-posted jobs across 11,000+ companies, updated daily
- [SIRET Company Scraper](https://apify.com/redomic/siret-company-scraper): Search French companies by name, SIREN or SIRET
- [SUUMO Scraper - Japan Property Listings](https://apify.com/redomic/suumo-scraper): Scrape SUUMO Japan property listings from search URLs for rentals and sales
- [Talabat Scraper — Menus, Prices & Delivery Fees](https://apify.com/redomic/talabat-scraper): Talabat menus, prices and delivery fees as flat rows in 8 MENA markets
- [Workday Jobs Scraper](https://apify.com/redomic/workday-jobs): Search and monitor public Workday (myworkdayjobs.com) job postings

# Changelog

This Actor's version history is a separate document: https://apify.com/redomic/personio-jobs/changelog.md

# Actor input Schema

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

Personio company subdomains or career-page URLs. The part before .jobs.personio.de/.com (for example solactive, join). You can also paste full board URLs or a single job URL; the subdomain (and job, if given) is resolved. Feeds are read from {subdomain}.jobs.personio.de/xml or .com. Leave empty or omit with mode=search (or with a search term and no companies) to search the 114 bundled verified Personio boards without a subdomain.

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

Language code requested from the Personio feed (for example en or de). Default en requests English descriptions; leave empty to use the board default. When the requested language leaves a description blank, it is filled from the board-default feed.

## `mode` (type: `string`):

jobs lists every open job; monitor reports only new, changed and removed jobs since the last run (stateful, unchanged monitor rows are not charged); search searches across the bundled Personio boards (or the listed companies), deduped by globalId and ordered newest-first with a global maxItems cap.

## `sort` (type: `string`):

Order in which jobs are returned before the maxItems cap: newest posts the highest createdAt first; oldest posts the lowest createdAt first. Undated rows go last.

## `excludeEvergreen` (type: `boolean`):

Drop stale postings whose ageDays is 365 or more, so a first run shows live roles, not years-old evergreen ads. On by default. A board that only has old postings still returns them, so a valid company never looks empty.

## `search` (type: `string`):

Keep only jobs whose title, location, office, department or description contains this text (case-insensitive). Leave empty for every open job.

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

Keep only jobs created within the last N days (by createdAt). 0 keeps every open job.

## `postedAfter` (type: `string`):

Keep only jobs published on or after this date (YYYY-MM-DD, inclusive, by createdAt). Leave empty to disable.

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

Keep only jobs whose office/location/additional offices contain one of these values (case-insensitive, OR). Leave empty for every location. Structured location fields (city, country, location, locations) are returned on each row.

## `countries` (type: `array`):

Keep only jobs whose parsed country contains one of these values (case-insensitive, OR). Matches the country field derived from office/additional offices. Leave empty for every country.

## `cities` (type: `array`):

Keep only jobs whose parsed city contains one of these values (case-insensitive, OR). Matches the city field derived from office/additional offices. Leave empty for every city.

## `departments` (type: `array`):

Keep only jobs whose department or recruiting category contains one of these values (case-insensitive, OR). Leave empty for every department.

## `employmentTypes` (type: `array`):

Keep only jobs with one of these Personio employment types (permanent, working_student, intern, freelance, temporary; case-insensitive, OR). Leave empty for every type.

## `schedules` (type: `array`):

Keep only jobs with one of these schedules (full-time, part-time; case-insensitive, OR). Leave empty for every schedule.

## `workplaceTypes` (type: `array`):

Keep only jobs with one of these workplace types (case-insensitive, OR). Derived from office/location/title/description (remote, hybrid, onsite). Leave empty for every type.

## `seniorities` (type: `array`):

Keep only jobs with one of these Personio seniority values (entry-level, experienced, student, executive, ...; case-insensitive, OR). Leave empty for every seniority.

## `minSalary` (type: `number`):

Keep only jobs whose annualised salaryMin (salaryPeriod converted to yearly: year x1, month x12, hour x2080) is at least this EUR amount. Jobs with no published salary are excluded when set. 0 means no minimum.

## `maxSalary` (type: `number`):

Keep only jobs whose annualised salaryMax (salaryPeriod converted to yearly: year x1, month x12, hour x2080) is at most this EUR amount. Jobs with no published salary are excluded when set. 0 means no maximum.

## `skillsInclude` (type: `array`):

Keep only jobs whose keywords, occupation or description contains one of these skills (case-insensitive, OR). Leave empty for every skill.

## `skillsExclude` (type: `array`):

Drop jobs whose keywords, occupation or description contains one of these skills (case-insensitive, OR). Leave empty to exclude nothing.

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

Keep only jobs whose derived workplace type is remote. Off by default.

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

Keep only jobs whose title contains one of these values (case-insensitive, OR). Leave empty for every title.

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

Drop jobs whose title contains one of these values (case-insensitive, OR). Leave empty to exclude nothing.

## `excludeLocations` (type: `array`):

Drop jobs whose location, office, city or country contains one of these values (case-insensitive, OR). Leave empty to exclude nothing.

## `hasSalary` (type: `boolean`):

Keep only jobs that publish a pay range (salaryMin or salaryMax is set). Off by default.

## `jobFunctions` (type: `array`):

Keep only jobs whose title, department, occupation or occupation category contains one of these values (case-insensitive, OR). Leave empty for every function.

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

Add the full job description as HTML and plain text (plus per-section HTML) to every row. On by default.

## `includeGeneralApplications` (type: `boolean`):

Include general/speculative application postings (e.g. Initiativbewerbung, Spontanbewerbung, open application). Off by default to return live role postings; placeholder/test rows and postings without a department are also excluded unless this is enabled.

## `includeRawJson` (type: `boolean`):

Attach the original, unmodified Personio position payload to each job row under the `raw` field. Off by default.

## `includeRemoved` (type: `boolean`):

In monitor mode, also emit jobs that disappeared since the previous run as `changeType=removed` rows. On by default. Set to false to receive only `new` and `changed` rows (state is still updated, so later removals remain detectable if re-enabled).

## `includeUnchanged` (type: `boolean`):

In monitor mode, also emit jobs that are still present and unchanged since the previous run as `changeType=unchanged` rows. Off by default. Unchanged rows are never charged.

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

Upper limit on job rows per company (and on what you pay); in search mode it is the global cap across all boards. Default 100, maximum 5000.

## Actor input object example

```json
{
  "companies": [
    "solactive"
  ],
  "language": "en",
  "mode": "jobs",
  "sort": "newest",
  "excludeEvergreen": true,
  "search": "",
  "postedSinceDays": 0,
  "postedAfter": "",
  "locations": [],
  "countries": [],
  "cities": [],
  "departments": [],
  "employmentTypes": [],
  "schedules": [],
  "workplaceTypes": [],
  "seniorities": [],
  "minSalary": 0,
  "maxSalary": 0,
  "skillsInclude": [],
  "skillsExclude": [],
  "remoteOnly": false,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "excludeLocations": [],
  "hasSalary": false,
  "jobFunctions": [],
  "includeDescription": true,
  "includeGeneralApplications": false,
  "includeRawJson": false,
  "includeRemoved": true,
  "includeUnchanged": false,
  "maxItems": 20
}
```

# Actor output Schema

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

All result rows from the default dataset.

# 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": [
        "solactive"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("redomic/personio-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": ["solactive"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("redomic/personio-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": [
    "solactive"
  ],
  "maxItems": 20
}' |
apify call redomic/personio-jobs --silent --output-dataset

```

## MCP server setup

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