# Personio Jobs Scraper & API — Any Personio Board (`vonsensey/personio-jobs-scraper-api`) Actor

Personio jobs API. Paste a Personio board slug, board URL or company domain and get every open role on that board, read live when you run it. One normalized schema with parsed salary, seniority, remote flag and direct apply URLs. Pay per job; boards we cannot reach are free.

- **URL**: https://apify.com/vonsensey/personio-jobs-scraper-api.md
- **Developed by:** [Blackcube Agency AB](https://apify.com/vonsensey) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Personio Jobs Scraper & API — Any Personio Board

**Every open role from any Personio job board, in one clean schema.**

Paste a Personio board slug, a board URL, or just the company's domain — you get
every open posting on that board, read live at the moment you run it.

> **You pay per job returned. A board we cannot reach is free, and a run that returns
> nothing costs nothing** — there is no start fee on any plan. Every company that
> produces no jobs still comes back as a row telling you why.

> **Unofficial.** Not affiliated with, endorsed by, or connected to Personio or any
> employer whose board it reads. It reads only the vacancies employers publish for
> the public on their own career pages.

***

### Why this one for Personio

**Live, not a snapshot.** This is not a pre-crawled database with an indexing delay.
Every row carries a `scrapedAt` timestamp and was open on that board at that moment.
If a role closed an hour ago, it is not in your results.

**Personio's strength is used properly.** Its own seniority and schedule fields, which most boards do not publish at all, plus strong DACH coverage that US-centric competitors under-serve.

**And its limits are published, not hidden.** It publishes no remote flag and no salary, so both come back null rather than guessed. Everything it does publish lands in the same schema as every other platform here.

**You are never billed someone else's jobs.** Company names collide across boards, and
a plausible-looking match can belong to an unrelated account. Where the board cannot be
confirmed as the company you asked for, you get a free row saying so — not a stranger's
postings on your invoice.

### Finding the board

It is the subdomain of the board: `<company>.jobs.personio.de`.

You can also pass a bare company domain and let the Actor find the board for you.

### What Personio actually gives you

Measured on a real run, not claimed. These are field fill rates:

| Department | Seniority | Location | Remote | Employment type | Salary | Posted date |
|---|---|---|---|---|---|---|
| 100% | 100% | 100% | 0% | 100% | 0% | 100% |

Where a field is unavailable you get an explicit `null` — never a guess, and never a
missing key. Seniority is **derived rather than reported**: almost no ATS publishes it
at all, so it is normalized here to 11 comparable levels.

### Input

| Field | Default | What it does |
|---|---|---|
| **companies** | `personio:stark` | Personio board slugs, board URLs, or company domains. |
| **onlyNewJobs** | `false` | Return only postings that are new or materially changed since your last run. |
| **titleKeywords** / **excludeTitleKeywords** | — | Filter on the job title. |
| **locationKeywords**, **remoteOnly** | — | Filter on location; remote excludes hybrid and unknown. |
| **seniority**, **employmentType**, **departments** | — | Filter on the normalized fields. |
| **hasSalary**, **postedWithinDays** | — | Only postings with a parsed range / published recently. |
| **maxJobs**, **maxJobsPerCompany** | — | Caps, honoured exactly. |
| **includeDescription** | `false` | Add the full description as HTML and text. |

Filters are applied **before** billing, so you pay for the rows you keep.

### Repeat runs only charge for what changed

Turn on **Only new or changed jobs** and a scheduled run returns — and bills for —
only postings that are new, or whose title, department, location, salary, employment
type or workplace type moved since last time. Description edits deliberately do not
count, so a re-worded posting is never re-charged.

### Need more than Personio?

This is one platform's listing of a 14-ATS engine. If you track companies across
several systems, the multi-ATS version takes a mixed list and detects each one for
you — same schema, same billing, same code.

***

### Legal & responsible use

Reads **only publicly published job listings** — the vacancies employers deliberately
publish for candidates to find. It does not log in, bypass authentication, solve
captchas, or touch applicant data.

Job postings are company information rather than personal data. You are responsible
for how you use the output, including any outreach you base on it.

***

*Unofficial project. Uses only publicly available data. Not affiliated with Personio
or any employer.*

# Actor input Schema

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

The Personio boards to pull open roles from — one per line. Paste a board slug (`personio:stark`), a board URL, or a plain company domain and the Actor finds the board itself. It is the subdomain of the board: `<company>.jobs.personio.de`. A company whose board cannot be found returns a free row explaining why.

## `presets` (type: `array`):

Curated company bundles you can run without researching a single slug. Every company in these lists is verified against its live board. Merged with anything in Companies above and de-duplicated.

## `onlyNewJobs` (type: `boolean`):

Return only postings that are new, or whose title, department, location, salary, employment type or workplace type changed since a previous run of this feed. Description edits do not count as a change, so a re-worded posting is never re-charged. This is what makes a daily or hourly schedule cheap: the first run returns the full board, and every run after it returns — and charges for — only what genuinely moved. Leave it off for a one-off full export.

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

Keep only postings published within this many days. Leave empty for all open roles. Postings whose board publishes no date are excluded when this is set, because they cannot be proven fresh.

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

Keep only jobs whose title contains one of these words (case-insensitive). For example: engineer, designer, account executive. Leave empty for every open role.

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

Drop jobs whose title contains any of these words. Applied after the include filter above. Useful for stripping intern, contract, or non-English duplicates out of a feed.

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

Keep only jobs matching one of these locations. Every location on a multi-site posting is checked, not just the first one, so a role open in both Berlin and Lisbon matches either.

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

Keep only roles the board marks as remote. Hybrid roles are excluded, and so are roles where nothing on the posting says either way — 'remote only' should not return maybes.

## `seniority` (type: `array`):

Keep only these seniority levels. Seniority is inferred from the job title, because almost no ATS publishes it as a field. Entry level means genuinely junior and graduate roles — not a '0-2 years' bucket that returns experienced ones.

## `employmentType` (type: `array`):

Keep only these employment types, normalized across every platform's own vocabulary (Lever's 'Full-time', Ashby's 'FullTime' and Personio's 'permanent' all become fullTime).

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

Keep only jobs whose department matches one of these, for example: engineering, sales, marketing.

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

Keep only postings where a real salary range could be parsed into numbers. Boards differ a lot here — Ashby publishes structured pay, most others only mention it in prose, and some never do.

## `maxJobs` (type: `integer`):

Hard cap on billable jobs for the entire run. The Actor stops at exactly this number — never over, and it tells you in the log when a cap is the reason results were truncated. Leave empty for no cap.

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

Cap per company, applied after filtering and newest-first sorting, so a handful of very large boards cannot dominate a run. Leave empty for every matching role.

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

Add the complete job description as both HTML and plain text. Off by default because descriptions are large and most feeds do not need them. On Workday and SmartRecruiters this needs one extra request per job, so those boards get noticeably slower — the price per job does not change.

## `atsPlatforms` (type: `array`):

Defaults to Personio only, which is what this Actor is for. The same engine covers 14 platforms — add more here, or use the multi-ATS version if you routinely mix them.

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

How many company boards to fetch in parallel. The default is deliberately polite to the career sites being read; raise it for very large lists.

## `stateKey` (type: `string`):

Names the memory used by 'Only new or changed jobs'. Leave empty and it is derived from your company list, so two different lists automatically keep separate memories. Set it explicitly when you run several schedules over overlapping companies and want each to track changes independently — or set the same name on two tasks to share one memory.

## Actor input object example

```json
{
  "companies": [
    "personio:stark"
  ],
  "presets": [],
  "onlyNewJobs": false,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "seniority": [],
  "employmentType": [],
  "departments": [],
  "hasSalary": false,
  "includeDescription": false,
  "atsPlatforms": [
    "personio"
  ],
  "maxConcurrency": 10
}
```

# Actor output Schema

## `jobs` (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": [
        "personio:stark"
    ],
    "atsPlatforms": [
        "personio"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vonsensey/personio-jobs-scraper-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": ["personio:stark"],
    "atsPlatforms": ["personio"],
}

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

```

## MCP server setup

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