# StepStone.de - German Jobs, Employers & Salaries (`abotapi/stepstone-de`) Actor

Scrape StepStone.de jobs by keyword, location, filters, or URL. Extract 35+ fields including employer, logo, location, posting date, home-office status, and optional full descriptions, GPS, employment type, and company profiles.

- **URL**: https://apify.com/abotapi/stepstone-de.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 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.
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?

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

## StepStone.de Jobs Scraper

Scrape job postings from [StepStone.de](https://www.stepstone.de) — Germany's #1 job platform. Fast, reliable. Returns 35+ listing fields per job. Optional detail enrichment adds 12 extra fields including the full German description, GPS coordinates, employment type, and company profile. Supports search by keyword, location and seven working filters, plus URL paste mode.

### Key features

- **Search mode** — keyword + location plus working-hours, contract-type, date, home-office, language, experience and apply-method filters. One run per location. Leave locations empty to search all of Germany.
- **URL mode** — paste one or more StepStone.de search-result URLs; each is expanded page by page.
- **Detail enrichment** (`fetchDetails`) — adds full description (HTML + plain text), structured address with lat/lng, employment type, contract type, work type, valid-through date, company profile (benefits, industries, size, logo, videos), and best-effort recruiter contact harvesting from tel:/mailto: links and the description text.
- **35+ SERP fields** — title, employer (name, id, logo URL, profile URL), location text, date posted, work-from-home status, labels, text snippet, partnership info.
- **Resume & recurring updates built in** (`resumeFromRunId` continues an interrupted run; incremental mode (NEW/UPDATED/REAPPEARED/EXPIRED) remembers state across scheduled daily/weekly runs — no dataset ID to paste each time).
- **MCP connector export** — pipe results into Notion / Linear / Airtable / Apify (discovery from the input form, no extra config).

### Filters — what each one actually does

Every filter below was measured live against the search **`python` in Berlin**, which returned
**310 jobs** with no filter applied. The number is how many of those 310 remain when the filter is
set. Nothing is advertised here that does not move the result count.

| Field | Value | Jobs left (of 310) |
|---|---|---|
| `workType` | `vollzeit` (full time) | 278 |
| | `teilzeit` (part time) | 57 |
| `contractType` | `permanent` (Feste Anstellung) | 261 |
| | `student` (Werkstudent) | 19 |
| | `fixed-term` (Befristeter Vertrag) | 6 |
| | `internship` (Praktikum) | 6 |
| | `freelance` (Freie Mitarbeit) | 5 |
| | `apprenticeship` (Ausbildung, Studium) | 4 |
| | `trainee` (Berufseinstieg) | 3 |
| | `doctorate` (Promotion/Habilitation) | 3 |
| `datePosted` | `1` (last 24 hours) | 22 |
| | `7` (last 7 days) | 116 |
| | `14` | 254 |
| | `30` | 309 |
| `workFromHome` | `remote` (Nur Home-Office) | 15 |
| | `hybrid` (Teilweise Home-Office) | 208 |
| `jobLanguage` | `de` (German advert) | 222 |
| | `en` (English advert) | 86 |
| `experienceLevel` | `experienced` (Mit Berufserfahrung) | 264 |
| | `entry` (Ohne Berufserfahrung) | 83 |
| | `management` (Mit Personalverantwortung) | 6 |
| `applyMethod` | `company-website` | 170 |
| | `quick-apply` (Schnelle Bewerbung) | 112 |
| `sortBy` | `relevance` / `date` | 310 — sorting changes the **order**, not the match count |

All eight filters combine: `vollzeit` + permanent + last 7 days + German + hybrid + experienced +
company-website + newest-first returns 27 of the 310.

#### What StepStone.de does *not* offer

- **No salary filter.** The site's salary facet exists but ships no options at all, so this actor
  deliberately has no minimum-salary input rather than a switch that silently does nothing.
- **No salary in listing results either.** Across a 25-job sample every `salary` and
  `unifiedSalary.min`/`max` came back empty (19 of 25 were flagged "salary available" while still
  publishing no figure). `salary` is returned for completeness and is usually `null`.
- **No employer-vs-agency filter**, and **no salary or distance sort** — StepStone.de offers
  relevance and most-recent only.
- **No per-advert language field.** Ad language exists as a *search filter* (`jobLanguage`) but the
  job pages expose no language attribute, so there is no language column in the output.
- `skills`, `postCode`, `travelTime`, `publishFromDate`/`publishToDate` and `crossPostedCount` were
  empty for all 25 jobs in the sample — the site serves them as null, and detail enrichment does not
  fill them either.

### Input reference

| Section | Field | Description |
|---|---|---|
| **Mode** | `mode` | `search` (filters) or `url` (paste links) |
| **Search mode** | `locations` | Array of cities/regions; prefilled `["Berlin"]`. Empty = all of Germany |
| | `keywords` | Job title or skill (e.g. `Softwareentwickler`) |
| | `workType` | `vollzeit` or `teilzeit` |
| | `contractType` | `permanent`, `fixed-term`, `trainee`, `freelance`, `apprenticeship`, `internship`, `student`, `doctorate` |
| | `datePosted` | Days: 1, 7, 14, 30 |
| | `workFromHome` | `remote` or `hybrid` |
| | `jobLanguage` | `de` or `en` |
| | `experienceLevel` | `entry`, `experienced`, `management` |
| | `applyMethod` | `quick-apply` or `company-website` |
| | `sortBy` | `relevance` or `date` |
| **URL mode** | `urls` | Array of StepStone.de SERP URLs; each paginated forward |
| **Output & limits** | `fetchDetails` | Fetch full detail pages (needs Residential DE proxy) |
| | `maxListings` | **The cap** — total jobs to collect (default 20; 0 = unlimited) |
| | `maxPages` | Optional page cap per search (default 0 = unlimited — stops at Max jobs or the site's last page) |
| | `maxResidentialRequests` | Safety cap on residential proxy usage (0 = unlimited) |
| **Resume & recurring** | `resumeFromRunId` | Continue an interrupted run (delta) |
| | `incrementalMode` | Daily monitoring — only changes returned on later runs |
| | `stateKey` | Optional — name this campaign to share state |
| | `emitUnchanged` | Also return UNCHANGED rows (extra rows billed) |
| | `emitExpired` | Also return EXPIRED rows when a complete scan proves they're gone (extra rows billed) |
| **Connection** | `proxy` | Proxy settings (Residential DE recommended; detail enrichment requires it) |
| **Export to apps** | `mcpConnectors` | MCP connector IDs to forward each item to (e.g. Notion, Linear, Airtable, Apify) |
| | `notionParentPageUrl` | Parent page URL/id for the Notion connector (required for the Notion export) |
| | `maxNotifyListings` | Max items exported per connector per run (default 50; does not affect the dataset) |

### Output (per-job record)

#### Listing fields (always present)

- `jobId`, `harmonisedId`, `jobUrl`, `applyUrl`, `title`, `datePosted`, `publishFromDate`, `publishToDate`
- `brand` (stepstone-de), `backend`, `scrapedAt`
- `employer` (id, name, url, logoUrl, isAnonymous)
- `location` (text, postalCode + detail-only: locality, region, country, latitude/longitude (also lat/lng), streetAddress)
- `salary` (min, max, currency, period — see "What StepStone.de does not offer": usually `null`)
- `workFromHome`, `labels`, `skills`, `textSnippet`, `crossPostedCount`
- `isSponsored`, `isHighlighted`, `isTopJob`, `isTrafficFromPartner`
- `partnership` (isBackfilled, isCrossPosted, isPartnershipJob, sourceSiteFriendlyName)
- `travelTime`, `sourceSite`, `sourceSearchUrl`

Multi-city adverts put every city into one `location.text` string ("Berlin, Frankfurt, Hamburg,
München, Münster"); when `fetchDetails` is on, the coordinates that come with such an advert are
for one of those cities only, not for all of them.

#### Detail fields (only when `fetchDetails: true`)

- `description`, `descriptionText` (full German job description)
- `employmentType`, `industry`, `contractType`, `workType`
- `jobLocationType`, `applicantLocationRequirements`
- `directApply`, `applyType` (DirekteBewerbung / ExterneBewerbung)
- `validThrough`, `externalId`
- `contactPhones`, `contactEmails` (best-effort)
- `company` (benefits, industries, size, founded, description, videos, images, jobsCount)
- `detailFetched` (boolean flag)

#### Incremental metadata (only in incremental mode)

- `changeType` (NEW / UPDATED / REAPPEARED / EXPIRED)
- `changedFields`, `firstSeenAt`, `lastSeenAt`

#### Scanned vs emitted caps

`maxListings` counts jobs **scanned**, not jobs returned. In incremental mode a quiet run that
suppresses most of a page therefore stops paging at the same depth as a noisy one, instead of paging
deeper to backfill the quota — which is what makes recurring runs genuinely cheaper.

### Send results into your apps (MCP connectors)

Optionally pipe results into the apps you already use. Authorize a connector once under Apify,
Settings, Integrations, then select it in the input. Set `notionParentPageUrl` for Notion. Each
connector receives a condensed, human-readable summary per item (title plus key fields), not the
full JSON; the complete record always stays in the Apify dataset. Supported: Notion, Linear,
Airtable, Apify.

### Proxy & connection

Search-result pages work on the default connection for most users. Detail pages (for `fetchDetails`)
always require Apify Residential proxy with country set to DE; job pages refuse every other connection
shape. Free-tier and non-Residential users get SERP-only results and the actor logs a clear warning.
`maxResidentialRequests` puts a hard ceiling on Residential usage for a run.

**StepStone.de sometimes refuses a page outright**, on any connection — certain search URLs are
simply expensive for the site to build, and it answers by dropping the request. The actor recognizes
this as the site having a bad moment, not your connection being blocked, so it does not spend extra
proxy budget retrying the same request: your proxy spend never goes up because of it.

If job detail pages start being refused across the board, detail enrichment is **switched off for the
rest of the run** instead of retrying every remaining job: you still get every listing row, and the
detail-enrichment event is not charged for jobs that were not enriched. The run summary reports this
as `detailEnrichmentDisabled`.

If a run cannot read a single result page it **fails with an explanatory message** rather than
reporting an empty search — an empty result set from this actor always means the filters really
matched nothing. The message distinguishes the two causes, so you are never told to upgrade your
proxy for a problem that a proxy cannot fix.

# Actor input Schema

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

How to find jobs: build a search from filters, or paginate URLs you paste.

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

Cities, states, or regions in Germany (Search mode). Examples: 'Berlin', 'München', 'Hamburg', 'Frankfurt am Main', 'Bayern'. One search runs per location. Leave empty for all of Germany.

## `keywords` (type: `string`):

Job title, skill, or company to search for. Examples: 'Softwareentwickler', 'Ingenieur', 'Marketing Manager', 'Pflegefachkraft'. Leave empty to list all jobs in the location.

## `workType` (type: `string`):

Full-time (Vollzeit) or part-time (Teilzeit). Measured on 'python' in Berlin: 310 jobs unfiltered, 278 Vollzeit, 57 Teilzeit.

## `contractType` (type: `string`):

Kind of contract offered. Measured on 'python' in Berlin (310 unfiltered): permanent 261, student/Werkstudent 19, fixed-term 6, internship 6, freelance 5, apprenticeship 4, trainee 3, doctorate 3.

## `datePosted` (type: `string`):

Only jobs posted within this many days. Measured on 'python' in Berlin (310 unfiltered): 24 hours 22, 7 days 116, 14 days 254, 30 days 309.

## `workFromHome` (type: `string`):

Remote-working arrangement. Measured on 'python' in Berlin (310 unfiltered): fully remote 15, hybrid 208.

## `jobLanguage` (type: `string`):

Language the job advert is written in. Measured on 'python' in Berlin (310 unfiltered): German 222, English 86. Note StepStone.de exposes ad language only as a search filter — there is no per-advert language field to return in the results.

## `experienceLevel` (type: `string`):

Seniority the advert asks for. Measured on 'python' in Berlin (310 unfiltered): with experience 264, without experience 83, with staff responsibility 6.

## `applyMethod` (type: `string`):

Whether the advert takes applications on StepStone or sends you to the company's own site. Measured on 'python' in Berlin (310 unfiltered): quick apply 112, company website 170.

## `sortBy` (type: `string`):

Result ordering. StepStone.de offers exactly these two; sorting changes the order, not how many jobs match. (There is no salary or distance sort on this site.)

## `urls` (type: `array`):

StepStone.de search-result URLs to scrape (URL mode). Multi-URL supported. Filter fields above are ignored. A page number in the URL is honoured as the starting point for results.

## `fetchDetails` (type: `boolean`):

Fetch detail pages to add the full description, GPS coordinates, structured address, employment type, valid-through date, and company profile. Adds ~12 fields. Needs an Apify Residential DE proxy and costs more per job. Off = fast, cheap, 35 listing fields.

## `maxListings` (type: `integer`):

Total jobs to collect across all searches. This is the main limit. Set 0 for unlimited (bounded by Max pages).

## `maxPages` (type: `integer`):

Optional safety limit on pages walked per location/URL (25 jobs per page). Leave at 0 to walk the whole catalogue: the run stops at Max jobs, the site's own last page, or a repeat-page guard, not an artificial page cap.

## `maxResidentialRequests` (type: `integer`):

Safety cap on how many Residential proxy requests this run may use (listing pages and detail pages). 0 = unlimited. When the cap is reached, listing falls back to datacenter and detail enrichment stops, so a run can never overspend on residential bandwidth.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Jobs already in that dataset are skipped, so this run returns only NEW jobs (a delta). Combine both runs' datasets for the full set. Max jobs then counts only the new jobs. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching jobs as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED jobs. Turn on "Emit unchanged" or "Emit expired" only when you also want those jobs returned (and billed). State is kept separately for each distinct search/filter setup (or by State key below).

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

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from your search/filter settings — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return jobs that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return jobs that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max jobs or Max pages capped it, or when Resume was used. This returns — and bills — extra synthetic rows.

## `proxy` (type: `object`):

Connection settings. StepStone.de accepts Apify Residential with country DE most reliably (the default); listing pages occasionally work on Datacenter but it is frequently refused, and full job details (fetchDetails) always need Residential DE. Datacenter and non-DE residential are refused on job pages.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "locations": [
    "Berlin"
  ],
  "workType": "any",
  "contractType": "any",
  "datePosted": "0",
  "workFromHome": "any",
  "jobLanguage": "any",
  "experienceLevel": "any",
  "applyMethod": "any",
  "sortBy": "relevance",
  "urls": [
    "https://www.stepstone.de/jobs/in-berlin"
  ],
  "fetchDetails": false,
  "maxListings": 20,
  "maxResidentialRequests": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DE"
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

Individual job records with 35+ fields, plus ~12 detail fields when fetchDetails is enabled.

## `output` (type: `string`):

Run summary: total jobs, detail pages fetched, pages walked, residential usage, duration.

# 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 = {
    "mode": "search",
    "locations": [
        "Berlin"
    ],
    "urls": [
        "https://www.stepstone.de/jobs/in-berlin"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "DE"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/stepstone-de").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 = {
    "mode": "search",
    "locations": ["Berlin"],
    "urls": ["https://www.stepstone.de/jobs/in-berlin"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "DE",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/stepstone-de").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 '{
  "mode": "search",
  "locations": [
    "Berlin"
  ],
  "urls": [
    "https://www.stepstone.de/jobs/in-berlin"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "DE"
  }
}' |
apify call abotapi/stepstone-de --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/stepstone-de"
        }
    }
}

```

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/Fn4PQWvp0EjyUyCv7/builds/3aeJ4M3kWTt2m9QoM/openapi.json
