The Bundesagentur für Arbeit (Federal Employment Agency) runs Germany's largest job board, the Jobsuche. Its search is also reachable as data, which makes it the best free starting point if you need German job listings for a job board, a market report, a recruiting dashboard or just a daily alert for yourself.
This guide shows how to get those listings filtered by keyword, city, radius and date, as JSON or as a CSV file, and how to turn that into a daily alert. All numbers below are from real runs on 8 November 2026.
Disclosure: I built the tool used below, the Job Search API on Apify. It calls the official public job APIs; you don't need an Arbeitsagentur account.
Step 1: one city, one role, last 7 days
curl -X POST "https://api.apify.com/v2/acts/fast_data_asap~job-search-api/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "keywords": "Data Analyst", "location": "Hamburg", "radiusKm": 25, "postedWithinDays": 7, "sources": ["arbeitsagentur"], "maxJobsPerSource": 20 }' This took 6.3 seconds and returned 7 jobs published in the Hamburg area during the previous week. Each one is a flat row:
{ "title": "Data Analyst (m/w/d)", "company": "fabplus GmbH", "location": "21129, Hamburg, Germany", "remote": false, "employmentType": "full-time", "postedAt": "2026-11-03T00:00:00+00:00", "url": "https://www.arbeitsagentur.de/jobsuche/jobdetail/…", "source": "arbeitsagentur", "sourceJobId": "…", "tags": [], "salaryMin": null } Two things stood out in those 7 rows:
- Two of the seven were the same client job, advertised by service providers. "Data Analyst (d/f/m) for AIRBUS" and "Data Analyst (d/m/w) für AIRBUS" were posted by two different companies. If you count jobs per employer, you need to account for ads that name the actual client only in the title.
- Keyword matching is broader than the title. The search also returned a "Client Partner Banking" and a "Senior Business Analyst" role, presumably because the agency's own search matches tags and descriptions. If you need strict title matches, filter afterwards (see step 3).
Step 2: all of Germany, as CSV
Leave location empty to search the whole country. Add &format=csv to the endpoint and you get a CSV file instead of JSON, ready for Excel or Google Sheets:
curl -X POST "https://api.apify.com/v2/acts/fast_data_asap~job-search-api/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN&format=csv" \ -H "Content-Type: application/json" \ -d '{"keywords": "Python", "location": "", "postedWithinDays": 7, "sources": ["arbeitsagentur"], "maxJobsPerSource": 50}' \ -o python-jobs.csv My run hit the limit of 50 jobs in 7.5 seconds, so the real number of new Python listings that week is higher. Raise maxJobsPerSource for a complete pull. A few facts from those 50 rows:
| Metric | Value |
|---|---|
| Marked as remote or home office | 6 of 50 |
| Full-time | 48 of 50 |
| With a salary figure | 0 of 50 |
| Most frequent cities | Berlin 5, Hamburg 4, Rostock 4, Bremen 3, Kassel 3 |
The salary row is the real gotcha: the Arbeitsagentur feed does not include salary data, so salaryMin and salaryMax stay empty for this source. If salary matters for your use case, add the arbeitnow, remoteok or himalayas sources, which sometimes include it, or read it from the description text with includeDescription: true.
Step 3: clean it up in Python
import httpx, pandas as pd ENDPOINT = ( "https://api.apify.com/v2/acts/" "fast_data_asap~job-search-api/run-sync-get-dataset-items" ) def german_jobs(keywords, location="", days=7, limit=200, token="YOUR_APIFY_TOKEN"): r = httpx.post( ENDPOINT, params={"token": token}, json={ "keywords": keywords, "location": location, "radiusKm": 25, "postedWithinDays": days, "sources": ["arbeitsagentur"], "maxJobsPerSource": limit, }, timeout=300, ) r.raise_for_status() return pd.DataFrame(r.json()) df = german_jobs("Data Analyst", "Hamburg") # strict title match: the agency search is broader than the title df = df[df["title"].str.contains("data analyst", case=False)] # drop the same job advertised by several service providers df["title_key"] = df["title"].str.lower().str.replace(r"\(.*?\)", "", regex=True).str.strip() df = df.drop_duplicates(subset=["title_key", "location"]) print(df[["title", "company", "location", "postedAt", "url"]]) On the Hamburg example the title filter leaves 4 Data Analyst jobs instead of 7. Two of those are still the same Airbus role (one ad says "for", the other "für"), so for serious counting add a client-name rule or a fuzzy match on top.
Step 4: a daily job alert in n8n
- Schedule Trigger: every weekday at 07:00.
- HTTP Request node:
POSTthe input from step 1 to the endpoint, withpostedWithinDays: 1and your Apify token as a Header Auth credential (Authorization: Bearer …). - Code node: the strict title filter and duplicate removal from step 3 (a few lines of JavaScript).
- IF node: stop when there are no rows.
- Gmail, Slack or Telegram node: send one message listing title, company and link.
Swap step 5 for a Google Sheets append and you have a growing log of every matching job, which after a few weeks shows you which companies hire most in your field and city.
Other filters worth knowing
-
remoteOnly: truekeeps only remote or home-office jobs. -
filterByLocation: truedrops jobs whose location text does not contain your city, useful when the radius pulls in too much. -
companies: add careers-page URLs on Greenhouse, Lever, Ashby or SmartRecruiters to watch specific employers in the same run. -
sources: combine the Arbeitsagentur with Arbeitnow, Remote OK and Himalayas for remote roles.
Cost
The actor charges $2.50 per 1,000 jobs returned. The 57 jobs in this article cost about 14 cents at that rate. A daily alert that returns 10–20 new jobs costs a few cents per month.
Data: Bundesagentur für Arbeit Jobsuche, queried 8 November 2026. Company names are as published in the job ads.
