# StepStone Jobs Scraper (Germany & Austria job search) (`dataforge-tools/stepstone-jobs`) Actor

Get job listings from StepStone search results on stepstone.de and stepstone.at: title, employer, location, home-office option, salary when published, posting date, a short excerpt and the link. Plain requests to public pages, robots.txt obeyed, no login. Tracks new vs. existing jobs between runs.

- **URL**: https://apify.com/dataforge-tools/stepstone-jobs.md
- **Developed by:** [DataForge](https://apify.com/dataforge-tools) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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.

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

## StepStone Jobs Scraper: job search results from stepstone.de and stepstone.at

Give it a keyword (and optionally a city) and get **the jobs StepStone lists for that search** as clean JSON: title, employer, location, home-office option, salary when the employer published one, posting date, a short excerpt and the link to the posting. Each run also tells you **what changed since the last run**: new jobs, jobs still listed, and jobs that dropped off.

- **Germany and Austria:** `stepstone.de` (default) and `stepstone.at`. Enter `python in berlin`, `at: python in wien`, or paste a StepStone results URL.
- **Plain requests to public pages, nothing else:** one request per search to the same results page a visitor opens. No login, no proxies, no CAPTCHA or anti-bot workarounds, and the site's robots.txt is checked and obeyed during the run.
- **Structured data, not scraped HTML:** the results page embeds its job list as data; the Actor reads that, so fields are consistent and do not break when the page design changes.
- **First page per search, by design.** StepStone's robots.txt forbids crawlers from using query strings on these pages, which is how page numbers, filters and sorting work. The Actor therefore returns **the first results page of each search, 25 jobs**, and never requests page 2. To cover more ground, add more specific searches (keyword + city, several keywords).
- **Change tracking built in:** every record has `changeStatus` (`new` or `existing`) and `firstSeenAt`. Turn on **Only new jobs** and schedule the Actor daily to get a feed of what appeared.
- **Reliable and polite:** timeouts, retries with backoff on server errors, at most two requests at a time and one per second per host, a descriptive User-Agent. A bad search is reported in the run summary and the rest still runs.

### Use cases

- **Daily job alerts:** "python in berlin", "pflegefachkraft in hamburg", "controller in wien": run every morning with **Only new jobs** on and pipe the result into Slack, e-mail or a sheet.
- **Recruiters and staffing agencies:** watch which employers are hiring for your specialities, with the employer's company page and the posting date.
- **Labour-market and salary research:** sample postings per keyword and city, including the published pay ranges and home-office options; `searchTotalResults` gives StepStone's total count per search for trend lines.
- **Sales intelligence:** hiring is a buying signal; find companies opening "SAP", "Data Engineer" or "Cloud" roles in your region this week.

### How to use it

1. Add searches to **Searches**, one per line, in any of these forms:
   - `python` (keyword, all of Germany)
   - `data engineer in hamburg` (keyword and city)
   - `at: python in wien` (Austria; `de:` is the default)
   - `https://www.stepstone.de/jobs/python/in-berlin` (a results URL without `?...`)
2. For daily monitoring, schedule the Actor with **Only new jobs** turned on.
3. Run it. Jobs land in the dataset; a run summary lands in the `OUTPUT` record.

Spelling follows StepStone's own URLs: words joined by hyphens, umlauts as written (`in-münchen`), `c#` for C#. A keyword that StepStone does not know still returns a page of related jobs, as it does for a visitor.

#### Input example

```json
{
    "searches": ["https://www.stepstone.de/jobs/python/in-berlin", "data engineer in hamburg", "at: python in wien"],
    "onlyNew": false,
    "maxItemsPerSource": null
}
```

| Field | Default | What it does |
|---|---|---|
| `searches` | 1 sample search | Keyword, `keyword in city`, optional `de:`/`at:` prefix, or a StepStone results URL. One request per search. |
| `onlyNew` | `false` | Outputs only jobs that earlier runs with the same searches have not seen. |
| `maxItemsPerSource` | all (25) | Keeps at most N jobs per search. |

### Output

One record per job. Fields are stable and documented in `.actor/dataset_schema.json`.

```json
{
    "id": "stepstone:de/python/in-berlin:14504872",
    "source": "stepstone",
    "sourceKey": "de/python/in-berlin",
    "title": "Werkstudent Data Analytics / Python (all genders)",
    "url": "https://www.stepstone.de/stellenangebote--Werkstudent-Data-Analytics-Python-all-genders-Berlin-Forvis-Mazars-GmbH-Co-KG--14504872-inline.html",
    "country": "de",
    "company": "Forvis Mazars GmbH & Co. KG",
    "companyUrl": "https://www.stepstone.de/cmp/de/forvis-mazars-gmbh-%26-co-kg-207661/jobs",
    "location": "Berlin",
    "workFromHome": "partial",
    "salary": null,
    "postedAt": "2026-10-01T06:11:57.000Z",
    "descriptionExcerpt": "Wir sind Forvis Mazars und du findest uns in mehr als 100 Ländern, weil wir aus Audit, Tax und Advisory weltweit nicht wegzudenken sind. Als integrierte Partnerschaft mit über 40.000 Professionals, davon mehr als 2.700 an 13 deutschen Standorten, gehören wir zu den führenden Prüfungs- und Beratungsg",
    "searchUrl": "https://www.stepstone.de/jobs/python/in-berlin",
    "searchTotalResults": 267,
    "firstSeenAt": "2026-10-08T05:34:36.000Z",
    "changeStatus": "new",
    "scrapedAt": "2026-10-08T05:34:36.489Z"
}
```

- `workFromHome`: `none`, `partial` (hybrid), `only` (fully remote), or `null` when the employer gave no option.
- `salary`: `{ "min", "max", "currency", "period" }` when the employer published a range (`period` is `yearly`, `monthly`, `weekly` or `hourly`); otherwise `null`. Most German postings publish none.
- `descriptionExcerpt`: the first 300 characters of the posting as plain text. The full text is never output; follow `url`.
- `company` and `companyUrl` are `null` for anonymous postings.

#### Run summary (`OUTPUT` record)

Totals (searches requested, succeeded and failed; jobs output, new, existing, removed since the last run), one entry per search with its status and error message, and the list of errors. One bad search never fails the run. A search that StepStone refuses (HTTP 403) is reported as such and never retried or worked around.

### Change tracking: how it works

History is kept in a named key-value store, keyed by the set of searches. The first run outputs everything as `new`; later runs mark jobs `new` or `existing`, and the `OUTPUT` record lists IDs that dropped off the first page since the last run. With **Only new jobs** on, only `new` records are output. Jobs that disappear are remembered for 90 days, so a job that returns to the first page is not reported as new again. Because only the first page is read, "removed" means "no longer on the first page", not necessarily "closed".

### Pricing

Pay per event: one `job` event per record. Suggested price: $0.002 per job; free to try with the Apify free plan. The owner sets the actual price in the Store; it is never in the code.

What that costs, with every search returning the full first page of 25 jobs:

- The default input (one search) costs about 5 cents per run.
- Five searches with **Only new jobs** off (the default) output 125 jobs per run, about $0.25; run daily, about $7.50 a month (30 runs).
- Five searches with **Only new jobs** on output only jobs not seen before. After the first run (about $0.25) a daily run costs only the newly posted jobs: a few cents on a quiet day, up to $0.25 if the whole first page turned over.

### FAQ

- **Why only 25 jobs per search? Can it fetch page 2?** No. StepStone's robots.txt disallows every query string on `/jobs/...` pages except a bare `?q=`, and page numbers, filters and sorting are query strings. The Actor only requests what robots.txt allows. Use several narrower searches instead (add a city, use more specific keywords).
- **Can it filter by full-time, part-time or home office?** Not through StepStone's filters (query strings again, and the `/jobs/vollzeit/`, `/jobs/teilzeit/` and `/jobs/home-office/` listing pages are disallowed by robots.txt). Filter the output on `workFromHome`, `title` and `location` instead.
- **Does it fetch the full job description?** No. The results page already carries a 300-character excerpt, which is what the Actor outputs together with the link. Job detail pages are not requested.
- **Does it log in or bypass bot protection?** No. Plain requests with a descriptive User-Agent, robots.txt obeyed at run time. If StepStone blocks a request, the run reports it and moves on.
- **How fresh is the data?** It is read live during the run, in StepStone's default (relevance) order.
- **Other StepStone countries?** Only `.de` and `.at` for now. They share the same page structure; other country sites were not checked.

### Legal

This Actor reads only public job postings that employers publish on StepStone, sends plain GET requests to pages that StepStone's robots.txt allows for every crawler, identifies itself with a descriptive User-Agent, obeys robots.txt at run time, requests nothing robots.txt disallows (no query strings, no pagination, no filters, no APIs), and outputs only a 300-character excerpt of each posting plus the link. It stores no personal data beyond what the employer wrote into the posting. StepStone's terms of use for job seekers contain a clause against scraping; see `LEGAL.md` for the exact wording, dates and the open questions. "StepStone" is a trademark of its owner; this Actor is not affiliated with or endorsed by StepStone.

# Actor input Schema

## `searches` (type: `array`):

One search per line. Forms: "python", "data engineer in hamburg", "at: python in wien" (country prefix de: or at:, default de), or a results URL such as https://www.stepstone.de/jobs/python/in-berlin. Each search returns the first results page (25 jobs). Keep the list short: the default must finish in under 5 minutes.

## `onlyNew` (type: `boolean`):

Output only jobs not seen in previous runs with the same searches. The first run outputs everything; later runs only what is new. Every record carries changeStatus ('new' or 'existing') and firstSeenAt either way.

## `maxItemsPerSource` (type: `integer`):

Upper limit of jobs per search (a page never has more than 25). Leave empty for the whole page.

## Actor input object example

```json
{
  "searches": [
    "https://www.stepstone.de/jobs/python/in-berlin"
  ],
  "onlyNew": false
}
```

# Actor output Schema

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

The jobs found in this run, one per record (the default dataset).

## `summary` (type: `string`):

Totals, per-search status and any errors for this run (the OUTPUT record).

# 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 = {
    "searches": [
        "https://www.stepstone.de/jobs/python/in-berlin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataforge-tools/stepstone-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 = { "searches": ["https://www.stepstone.de/jobs/python/in-berlin"] }

# Run the Actor and wait for it to finish
run = client.actor("dataforge-tools/stepstone-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 '{
  "searches": [
    "https://www.stepstone.de/jobs/python/in-berlin"
  ]
}' |
apify call dataforge-tools/stepstone-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataforge-tools/stepstone-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/6pxm1uqoMgXyxoVzt/builds/5b6iZzZiUvva1H7aw/openapi.json
