StepStone.de - German Jobs, Employers & Salaries
Pricing
from $1.20 / 1,000 job results
StepStone.de - German Jobs, Employers & Salaries
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.
Pricing
from $1.20 / 1,000 job results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
3 days ago
Last modified
Categories
Share
StepStone.de Jobs Scraper: Job Listings, Employer & Salary Data
StepStone.de Jobs Scraper turns StepStone.de, Germany's job platform, into structured job data. Search by keyword, location and eight working filters, or paste search-result links, and get 35 or more fields per job: title, employer, location, posting date, home-office status, labels and more. Turn on detail enrichment for the full German job description, GPS coordinates, employment type, valid-through date and company profile. Any salary figures the site publishes are in EUR. Export to JSON, CSV or Excel, or pull results straight into your app through the API.
Why This Scraper?
- Two ways to find jobs. Build a search from keyword, location and eight working filters, or paste one or more StepStone.de search-result links and let the actor paginate them forward.
- 35+ listing fields per job, with optional detail enrichment adding around 12 more: full description, GPS coordinates, structured address, employment type, contract type, valid-through date and a company profile (benefits, industries, size, logo).
- Filters that are honestly documented. Every filter option was measured against a real search so nothing advertised here silently does nothing, and the one filter StepStone.de does not actually support (salary) is deliberately left out of the input rather than shipped as a switch that does nothing.
- Resume and recurring updates built in. Continue an interrupted run by pasting its run or dataset id, or turn on incremental mode so scheduled runs return only NEW, UPDATED and REAPPEARED jobs, with UNCHANGED ones suppressed by default.
- Best-effort recruiter contact harvesting. When detail enrichment is on, phone numbers and email addresses are pulled from tel:/mailto: links and the job description text.
- A refused run fails loudly instead of returning an empty dataset, so "no jobs matched your filters" is never confused with "the site could not be read".
- MCP connector export. Pipe results into Notion, Linear, Airtable or Apify, selected straight from the input form once you authorize a connector.
Use Cases
- Recruiters and talent teams: track newly posted roles from target employers or an industry across German cities.
- Job seekers and career coaches: monitor a keyword and location search daily so new postings are caught early.
- Labor market research: build historical datasets of demand by contract type, work model and experience level.
- Sales and lead generation: use employer and company-profile fields (industries, size, benefits) to prioritize outbound targets.
- HR tech and job-board products: pull structured German job data into your own product through the dataset or the API.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
rowType / brand / backend | "job" / "stepstone-de" / "unified" |
jobId / harmonisedId | 12345678 / "12345678" |
jobUrl / applyUrl | "https://www.stepstone.de/stellenangebote--Beispiel-Job-Berlin-Beispiel-GmbH--12345678-inline.html" |
title | "Softwareentwickler (m/w/d)" |
datePosted / publishFromDate / publishToDate | "2026-09-20" / "2026-09-20" / "2026-10-20" |
sourceSite / sourceSearchUrl | "StepStone.de" / search URL that found the job |
employer | { "id": "9001", "name": "Beispiel GmbH", "url": "...", "logoUrl": "...", "isAnonymous": false } |
location | { "text": "Berlin", "postalCode": "10115", "locality": null, "region": null, "country": null, "latitude": null, "longitude": null, "streetAddress": null } (detail-only fields filled when fetchDetails is on) |
salary | { "rawText": null, "min": null, "max": null, "currency": null, "period": null } (almost always empty; see the tip below) |
workFromHome | "Home-Office teilweise möglich" or null |
labels / skills | ["Vollzeit"] / [] |
textSnippet | short teaser text from the search-result card |
crossPostedCount | 2 |
isSponsored / isHighlighted / isTopJob / isTrafficFromPartner | false / false / false / false |
partnership | { "isBackfilled": false, "isCrossPosted": false, "isPartnershipJob": false, "sourceSiteFriendlyName": null } |
travelTime | null |
description / descriptionText | full HTML / plain-text job description (detail-only) |
employmentType / contractType / workType | ["FULL_TIME"] / "permanent" / "vollzeit" (detail-only) |
jobLocationType / applicantLocationRequirements | "TELECOMMUTE" / "Germany" (detail-only) |
validThrough / externalId | "2026-11-20T00:00:00" / "REQ-2026-001" (detail-only) |
directApply / applyType | true / "DirekteBewerbung" (detail-only) |
contactPhones / contactEmails | ["+4930XXXXXXX"] / [] (best-effort, detail-only) |
company | { "id": "9001", "name": "Beispiel GmbH", "jobsCount": 12, "founded": "2005", "size": "201-500", "industries": ["IT"], "benefits": ["Home-Office"], "description": "..." } (detail-only) |
detailFetched / scrapedAt | true / ISO timestamp of this run |
changeType / changedFields / firstSeenAt / lastSeenAt | "NEW" / [] / ISO timestamp / ISO timestamp (incremental mode only) |
How to Use
- Pick a mode:
search(build a search from filters) orurl(paste one or more StepStone.de search-result links). - In search mode, set Locations and Keywords, and switch on any of the eight working filters. In URL mode, filter fields are ignored; each pasted link is paginated forward from wherever it starts.
- Turn on Fetch full job details for the extra description, geo and company fields (needs an Apify Residential DE proxy), and set Max jobs to control run size and cost.
- Click Start. Download the dataset as JSON, CSV or Excel, or read it through the API.
Search by keyword and location:
{"mode": "search","locations": ["Berlin"],"keywords": "Softwareentwickler","workType": "vollzeit","maxListings": 50}
Daily monitoring for new postings only:
{"mode": "search","locations": ["München"],"keywords": "Pflegefachkraft","datePosted": "1","incrementalMode": true,"maxListings": 100}
Full detail enrichment for one city:
{"mode": "search","locations": ["Hamburg"],"keywords": "Marketing Manager","fetchDetails": true,"maxListings": 30,"proxy": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"],"apifyProxyCountry": "DE"}}
Paste search-result links:
{"mode": "url","urls": ["https://www.stepstone.de/jobs/ingenieur/in-frankfurt-am-main"],"maxListings": 50}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/stepstone-de").call(run_input={"mode": "search", "locations": ["Berlin"], "keywords": "Softwareentwickler", "maxListings": 20})for job in client.dataset(run["defaultDatasetId"]).iterate_items():print(job["title"], job["employer"]["name"], job["location"]["text"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/stepstone-de').call({mode: 'search', locations: ['Berlin'], keywords: 'Softwareentwickler', maxListings: 20,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Filters that actually narrow results (measured)
Every filter was measured live against the search "python" in Berlin, which returned 310 jobs with no filter applied. These are illustrative of how much each filter narrows a result set; your own search will differ.
| Field | Value | Effect (of the 310 measured) |
|---|---|---|
workType | vollzeit / teilzeit | 278 / 57 |
contractType | permanent / student / fixed-term / internship / freelance / apprenticeship / trainee / doctorate | 261 / 19 / 6 / 6 / 5 / 4 / 3 / 3 |
datePosted | 1 / 7 / 14 / 30 (days) | 22 / 116 / 254 / 309 |
workFromHome | remote / hybrid | 15 / 208 |
jobLanguage | de / en | 222 / 86 |
experienceLevel | experienced / entry / management | 264 / 83 / 6 |
applyMethod | company-website / quick-apply | 170 / 112 |
sortBy | relevance / date | changes order, not the match count |
All eight filters combined can narrow a search sharply: vollzeit plus permanent plus last 7 days plus German plus hybrid plus experienced plus company-website plus newest-first returned 27 of the 310 in that same measurement.
There is deliberately no salary filter (the site's own salary facet ships no selectable options), no employer-vs-agency filter, and no salary or distance sort. Ad language exists only as a search filter, not as a per-job output field, because the job pages themselves carry no language attribute.
What StepStone.de does not offer
Salary is usually null: across a sample of 25 jobs, every salary and structured salary min/max came back empty, even on adverts flagged internally as "salary available". salary is still returned on every record for consistency, but expect it empty far more often than not. skills, postalCode, travelTime and crossPostedCount are also frequently empty; the site itself serves them as null, and detail enrichment does not fill them in.
Scanned vs emitted caps
Max jobs counts jobs scanned, not jobs returned. In incremental mode, a quiet run that suppresses most of a page 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.
Tip: EXPIRED rows are not limited by Max jobs. Once a run has fully scanned a tracked search, every previously-tracked job that is no longer found is returned as an EXPIRED row in that same run, with no relation to your Max jobs setting. If a state key has not run in a while and many jobs disappeared at once, expect one large EXPIRED batch that can be bigger than Max jobs. Only the NEW/UPDATED/REAPPEARED collection itself is bounded by Max jobs.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset id and the actor skips every job already collected there, so you don't pay twice. This works the same way for both search mode and URL mode. - Incremental mode (
incrementalMode) is for scheduled runs over the same search. Each job is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED(only after a run that scanned the whole tracked search, and only withemitExpiredon).stateKeynames or shares the stored state; leave it empty to let the actor derive one automatically from your mode, locations or pasted links, filters and the Fetch full job details setting. Changing any of those (for example switching Fetch full job details on) starts a fresh baseline, so the next run returns every job as NEW again. - Platform migration or Resurrect restarts the same run from its saved position, without re-pushing or re-billing jobs already collected before the interruption.
Tip: a job re-billed as UPDATED right after a resumed monitoring campaign starts is expected. If you start a NEW incremental-mode campaign using resumeFromRunId to seed it from an older run, those resumed jobs are added to the new baseline by id only (their content is not stored, since it was never re-scraped this run). The FIRST time incremental mode later re-scrapes one of those jobs, it is classified UPDATED (with every field listed in changedFields) even if the job itself did not change, simply because there is nothing to compare it against yet. Treat that first appearance as informational rather than a real change.
Tip: keep State key empty unless two runs really are the same search. A manually shared State key across two differently configured runs (different locations, keywords or filters) makes them share one baseline. Jobs that only exist in one of those searches can then be marked EXPIRED purely because the other run's search never saw them, and then come back as REAPPEARED (and are billed again) when the first search runs next, so the same jobs flip back and forth run after run. Only reuse a State key across runs with identical search settings.
Tip: for a recurring URL-mode campaign, paste the first-page link. A pasted search-result link that starts on a later page (one with ?page=2 or higher) skips every earlier page on every run. Combined with incremental mode and Emit expired jobs, such a run still counts as a complete scan once it reaches the site's last page, even though it never checked the skipped earlier pages. A tracked job that moves up onto one of those skipped pages (because jobs ahead of it were removed or re-ranked) is then wrongly marked EXPIRED while it is still live, and if the link shares a State key with a first-page link, every job on the skipped pages is marked EXPIRED. Use the plain first-page link for any URL you plan to schedule with incremental mode.
Tip: with several locations and a small Max jobs, raise the cap or split the run. Max jobs is split evenly between locations (rounded up), and a share one location does not use is not passed on to the others: with Max jobs 10 across a location that has 2 jobs and one that has 30, the run returns 7. A cross-posted job that falls beyond one location's share is also treated as already handled, so it is left out of every later location's results too, even when that location still had room for it. Raise Max jobs, or run each location as its own actor run, if you need full coverage in a multi-location search. (This only affects how many jobs are returned; it never causes a false EXPIRED, because a run cut short by the cap skips EXPIRED detection.)
Tip: a run that was migrated or resurrected mid-way is less exact in two ways. The restarted run counts Max jobs again from zero, so the run as a whole can return more jobs than Max jobs (up to the jobs collected before the interruption plus a full Max jobs after it). In incremental mode, the jobs pushed before the interruption are protected from being re-pushed in that run, but their NEW or UPDATED classification is not saved to the stored baseline, so the next scheduled run reports them as NEW or UPDATED (and bills them) a second time. That run also skips EXPIRED detection. If exact counts matter, check the next scheduled run after any migrated run.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify, Settings, Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" (
mcpConnectors) input field. (If the picker is empty, you haven't authorized a connector yet.) - For Notion, also set
notionParentPageUrlto the page where items should be created. maxNotifyListingscaps how many items are sent to each connector per run; it does not affect the dataset.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (build from filters) or url (paste links). |
locations | array | prefilled ["Berlin"] | Cities, states, or regions in Germany (search mode). One search runs per location; empty means all of Germany. |
keywords | string | (none) | Job title, skill, or company to search for (search mode). |
workType | string | any | any, vollzeit (full time), or teilzeit (part time). |
contractType | string | any | any, permanent, fixed-term, trainee, freelance, apprenticeship, internship, student, or doctorate. |
datePosted | string | 0 | Days since posting: 0 (any time), 1, 7, 14, or 30. |
workFromHome | string | any | any, remote, or hybrid. |
jobLanguage | string | any | any, de (German), or en (English) advert language. |
experienceLevel | string | any | any, entry, experienced, or management. |
applyMethod | string | any | any, quick-apply, or company-website. |
sortBy | string | relevance | relevance or date (most recent). |
urls | array | prefilled sample StepStone.de search URL | StepStone.de search-result URLs to scrape (URL mode). A page number in the URL is honoured as the starting point. |
fetchDetails | boolean | false | Fetch detail pages for the full description, GPS, employment type, valid-through date and company profile. Needs an Apify Residential DE proxy. |
maxListings | integer | 20 | Total jobs to collect across all searches. 0 = unlimited (bounded by Max pages). |
maxPages | integer | 0 | Safety limit on pages walked per location/URL (25 jobs per page). 0 = walk the whole catalogue. |
maxResidentialRequests | integer | 0 | Safety cap on Residential proxy requests this run may use. 0 = unlimited. |
resumeFromRunId | string | (none) | Continue a previous run or dataset by id; returns only the delta of jobs not already collected there. |
incrementalMode | boolean | false | Turn on for recurring monitoring: later runs return only NEW, UPDATED and REAPPEARED jobs. |
stateKey | string | (none) | Name or share an incremental-mode state. Leave empty to derive one automatically per search/filter setup. |
emitUnchanged | boolean | false | Also return (and bill) UNCHANGED jobs. |
emitExpired | boolean | false | Also return (and bill) EXPIRED jobs, only after a run that fully scanned the tracked search. |
proxy | object | prefilled Residential, country DE | Connection settings. Detail enrichment always needs Residential DE; listing pages work on the default connection for most users. |
mcpConnectors | array | (none) | Optional: send a summary of each job to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which job pages are created. |
maxNotifyListings | integer | 50 | Cap on items exported per connector per run. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"rowType": "job","brand": "stepstone-de","backend": "unified","sourceSearchUrl": "https://www.stepstone.de/jobs/vollzeit/softwareentwickler/in-berlin","jobId": 12345678,"harmonisedId": "12345678","jobUrl": "https://www.stepstone.de/stellenangebote--Beispiel-Job-Berlin-Beispiel-GmbH--12345678-inline.html","applyUrl": "https://www.stepstone.de/stellenangebote--Beispiel-Job-Berlin-Beispiel-GmbH--12345678-inline.html","sourceSite": "StepStone.de","title": "Softwareentwickler (m/w/d)","datePosted": "2026-09-20","publishFromDate": "2026-09-20","publishToDate": "2026-10-20","employer": {"id": "9001","name": "Beispiel GmbH","url": "https://www.stepstone.de/cmp/de/beispiel-gmbh-9001","logoUrl": "https://images.stepstone.de/beispiel-gmbh-logo.png","isAnonymous": false},"location": {"text": "Berlin","postalCode": "10115","locality": "Berlin","region": "Berlin","country": "DE","latitude": 52.52,"longitude": 13.405,"lat": 52.52,"lng": 13.405,"streetAddress": null},"salary": { "rawText": null, "min": null, "max": null, "currency": null, "period": null },"workFromHome": "Home-Office teilweise möglich","labels": ["Vollzeit"],"skills": [],"textSnippet": "Wir suchen einen erfahrenen Softwareentwickler zur Verstärkung unseres Teams...","crossPostedCount": 1,"isSponsored": false,"isHighlighted": false,"isTopJob": false,"isTrafficFromPartner": false,"partnership": {"isBackfilled": false,"isCrossPosted": false,"isPartnershipJob": false,"sourceSiteFriendlyName": null},"travelTime": null,"description": "<p>Wir suchen einen erfahrenen Softwareentwickler...</p>","descriptionText": "Wir suchen einen erfahrenen Softwareentwickler...","validThrough": "2026-11-20T00:00:00","employmentType": ["FULL_TIME"],"industry": "Informationstechnologie","directApply": true,"applyType": "DirekteBewerbung","jobLocationType": null,"applicantLocationRequirements": null,"externalId": "REQ-2026-001","contractType": "permanent","workType": "vollzeit","company": {"id": "9001","name": "Beispiel GmbH","jobsCount": 12,"founded": "2005","size": "201-500","industries": ["Informationstechnologie"],"benefits": ["Home-Office", "Weiterbildung"],"description": "Beispiel GmbH ist ein fiktives Beispielunternehmen."},"contactPhones": [],"contactEmails": [],"detailFetched": true,"scrapedAt": "2026-09-30T09:00:00+00:00"}
Plan Requirement
The default connection works for search-result pages for most users. Detail enrichment (fetchDetails) always needs an Apify Residential proxy with country set to DE; pick it under Connection, or leave the prefilled Residential DE setting in place. maxResidentialRequests puts a hard ceiling on Residential usage for a run if you want one.
FAQ
How much does it cost?
You pay per job returned, plus an optional per-job surcharge when detail enrichment is on. The Pricing tab shows the current rates. Use Max jobs to cap the cost of any run.
Is it legal to scrape StepStone.de?
This actor collects only publicly available job listing data. You are responsible for how you use it: follow StepStone.de's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Job postings are generally facts, but employer logos and images may be subject to third-party rights.
Can I get only new job postings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only NEW, UPDATED and REAPPEARED jobs, and unchanged ones are not billed unless you turn on Emit unchanged.
Why is the salary field almost always empty?
StepStone.de itself rarely publishes a salary figure on its listings, even on adverts internally flagged as having one. This actor returns the salary field for consistency, but expect it null far more often than not; there is also no salary filter or salary sort, because the site does not offer one.
Why did a large batch of jobs suddenly come back as EXPIRED, more than my Max jobs setting?
EXPIRED detection only runs after a complete scan of a tracked search, and the EXPIRED batch itself is not limited by Max jobs: every previously-tracked job no longer found is returned in that one run. If a monitoring campaign has not run in a while, expect a correspondingly large EXPIRED batch the next time it runs.
Can incremental mode mark a job UPDATED even though nothing about it changed?
It can, in one specific case: right after you bootstrap a new monitoring campaign with resumeFromRunId, the resumed jobs are known only by id, not by content. The first time incremental mode later re-scrapes one of those jobs, it is classified UPDATED because there is nothing stored yet to compare it against, even if the job itself is unchanged. Treat that one occurrence as informational.
Why did my run fail instead of returning an empty dataset?
If the site refuses every request, the run stops with a clear message so "no jobs matched" is never confused with "nothing could be read". Run it again in a few minutes. If only some locations or URLs could be read, you still get what was found.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
🔗 Want more jobs data?
Pair this actor with these related scrapers from the same team:
| 💼 Totaljobs Scraper Scrape UK job listings from Totaljobs.com by keyword, location, filters, or URL. Extract... | 💼 ZipRecruiter Scraper From $1/1K. Scrape ZipRecruiter jobs with titles, companies, salaries, locations... |
| 💼 CareerBuilder Scraper Scrape CareerBuilder.com job listings by search or URL. Returns title, company profile... | 💼 Monster Jobs Scraper Scrape Monster.com job listings from search and direct job URLs. Extract full... |
| 💼 JobTeaser Scraper Scrape early-career jobs and internships from JobTeaser across Europe. Search with 15... | 🏷️ FINN.no Jobs Scraper Scrape active FINN.no job listings by search filters or URL. Extract 30+ fields including... |
💬 Support
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.