# Germany Jobs API - Arbeitsagentur Job Board Scraper (`lost.fany/arbeitsagentur-germany-jobs-api`) Actor

Search the German Federal Employment Agency (Arbeitsagentur) job board by keyword, city and radius: employer, location, start date, working time and the posting link. Official API, no key. dryRun prices a run before you pay; charged only for jobs delivered.

- **URL**: https://apify.com/lost.fany/arbeitsagentur-germany-jobs-api.md
- **Developed by:** [Natalia Ivanova](https://apify.com/lost.fany) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 job rows

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?

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

## Arbeitsagentur Jobs Scraper - Germany Jobs API

Get **job openings from the largest job board in Germany** — the public search of the Bundesagentur für Arbeit (the German Federal Employment Agency) — as one clean, structured dataset. Around two million postings are listed there at any time, from software engineering to nursing, logistics, crafts and apprenticeships, including thousands of jobs that never appear on commercial boards because employers publish them only through the agency.

One run gives you a normalized row per posting: title, employer, city, state, postal code, coordinates, home-office flag, working time, occupation, publication and start date, and the canonical link to the posting on arbeitsagentur.de. The field names match the other job Actors in this collection, so a job pipeline that already reads one of them reads this one without changes.

### What you can do with the Arbeitsagentur jobs scraper

- **Job alerts for Germany** — run it every morning with `publishedSince: 1` and your keywords, then push new matches to Slack, email or a Google Sheet through Apify integrations.
- **Niche job boards and newsletters** — fill a regional or sector site ("IT jobs in Bayern", "Pflege in Hamburg") with fresh listings that link back to the source.
- **Labour-market research** — count openings per occupation, state or radius over time; the board covers the whole country, including rural areas no commercial board indexes well.
- **Recruiting and competitor research** — see which employers are hiring for a given occupation right now, and which of them use temp agencies.
- **Relocation and salary research** — coordinates and postal codes on every row make map and commute analysis straightforward.
- **AI agents** — let an agent query the German labour market through the Apify MCP server (see below).

### How this German job board is read

**There is no documented public API for this source, and the Actor does not pretend otherwise.** The agency's job search is an Angular application rendered on the server, and the server embeds the raw search response in the page as JSON. The Actor reads that JSON block rather than parsing rendered HTML, so it does not depend on the page layout, class names or wording:

| Step | Endpoint | What it gives |
|---|---|---|
| Search | `www.arbeitsagentur.de/jobsuche/suche?was=…&wo=…&umkreis=…&page=…` | 25 postings per page with employer, location, dates, working time, occupation |
| Description | `rest.arbeitsagentur.de/jobboerse/jobsuche-service/pc/v4/jobdetails/<id>` | the full posting text plus the temp-agency and private-recruiter flags |

The description endpoint is the one the portal itself calls, and it is used only when `includeDescription` is on — one extra request per posting. No login, no browser, no cookies.

This is a public web source, not a contract: it can change or start refusing automated traffic at any time. The Actor is built for that — it pauses between requests, retries with backoff, handles HTTP 429 politely, and on an HTTP 403 switches to Apify Proxy when one is configured and says so in the log. The run summary reports how many requests were made and how many were refused.

### Jobs in Germany scraper: input

| Field | Type | Default | Description |
|---|---|---|---|
| `keywords` | array | `["python"]` | **Required.** One search per term; results are merged and deduplicated. German terms match best. |
| `location` | string | — | City, postal code or district (`Berlin`, `10247`, `München`). Empty = all of Germany. |
| `radiusKm` | integer | `25` | Radius around `location`, 0–200 km. Ignored without a location. |
| `publishedSince` | integer | — | Published within this many days. Empty = no date filter. |
| `employmentType` | string | `any` | `any`, `work`, `apprenticeship`, `internship`, `self_employment`. |
| `workingTime` | array | `[]` | Any of `full_time`, `part_time`, `shift_night_weekend`, `home_office`, `minor_employment`. |
| `temporaryWork` | boolean | `true` | `false` leaves out temp-agency (Zeitarbeit) postings. |
| `includeDescription` | boolean | `false` | Fetch the full posting text — one extra request per posting. |
| `maxItemsPerQuery` | integer | `500` | Cap per keyword, so one broad term cannot consume the run. |
| `maxItems` | integer | `1000` | Cap in total, after deduplication. |
| `proxyConfiguration` | object | off | Enable if the source starts refusing Apify's IPs. |

`keywords` is **required**: an API or scheduler call that omits it fails fast instead of running an empty search.

Example — full-time developer jobs within 50 km of Munich, published in the last week, no temp agencies, with descriptions:

```json
{
  "keywords": ["softwareentwickler", "python"],
  "location": "München",
  "radiusKm": 50,
  "publishedSince": 7,
  "employmentType": "work",
  "workingTime": ["full_time"],
  "temporaryWork": false,
  "includeDescription": true,
  "maxItems": 500
}
```

### Output

One dataset item per posting:

```json
{
  "id": "10001-1003841159-S",
  "refnr": "10001-1003841159-S",
  "title": "Junior Linux Systemadministrator (m/w/d)",
  "company": "Heinlein Support GmbH",
  "employer": "Heinlein Support GmbH",
  "employerAnonymous": false,
  "employerIdHash": "X8S-ikw6U8eE45OGfITtqg-HwamcRSqEAanh8aZsHeA=",
  "location": "Berlin, Berlin, Germany",
  "city": "Berlin",
  "region": "Berlin",
  "postalCode": "10119",
  "country": "Germany",
  "latitude": 52.533504,
  "longitude": 13.410531,
  "otherLocations": [],
  "remote": true,
  "homeOfficeType": "NACH_VEREINBARUNG",
  "employmentType": "full_time",
  "workingTime": ["full_time", "part_time_morning", "home_office"],
  "offerType": "ARBEIT",
  "offerTypeLabel": "Job",
  "contractDuration": "UNBEFRISTET",
  "contractMonths": null,
  "contractUntil": null,
  "occupation": "IT-Systemadministrator/in",
  "occupationGroup": ["IT-Systemadministrator/in", "IT-Administrator/in"],
  "publishedAt": "2026-10-12",
  "firstPublishedAt": "2026-10-12",
  "updatedAt": "2026-10-12T11:56:51Z",
  "startDate": "2026-10-12",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "salaryInfo": null,
  "isTemporaryWork": false,
  "isPrivateRecruiter": false,
  "suitableForCareerChangers": false,
  "distanceKm": 3,
  "url": "https://www.arbeitsagentur.de/jobsuche/jobdetail/10001-1003841159-S",
  "externalUrl": "https://www.yourfirm.de/job/detail/YF-48852/",
  "description": "Du suchst Abwechslung statt Alltagsroutine? …",
  "source": "arbeitsagentur",
  "sourceName": "Bundesagentur für Arbeit",
  "attribution": "Originally published on arbeitsagentur.de: https://www.arbeitsagentur.de/jobsuche/jobdetail/10001-1003841159-S",
  "scrapedAt": "2026-10-12T11:21:31Z"
}
```

`employmentType` uses the shared vocabulary of this collection (`full_time`, `part_time`, `temporary`, `freelance`, `internship`, `other`), while `offerType` keeps the board's own category. `description`, `isTemporaryWork`, `isPrivateRecruiter` and `externalUrl` are filled only when `includeDescription` is on; a `null` flag means the posting does not state it. Postings published under a Chiffre number have no employer name: `employerAnonymous` is `true` and `company` reads `Confidential (Chiffre)`.

The `OUTPUT` record of the key-value store holds the run summary: jobs pushed and matched, duplicates dropped, requests made, refused responses, whether the proxy was used, and the per-keyword counts.

### Personal data, attribution and affiliation

- **No personal data is collected.** Postings sometimes name a contact person with a phone number or e-mail address. The Actor never reads or stores those: contact fields are stripped from every record before it is normalized, and only employer and vacancy data reaches the dataset. Nothing in the output identifies a private individual.
- **The data belongs to the Bundesagentur für Arbeit** and the employers who published it. Every row keeps `url`, `source`, `sourceName` and an `attribution` line — please keep them when you display or republish the data.
- **This Actor is not affiliated with, endorsed by or operated by the Bundesagentur für Arbeit.** It is an independent tool that reads a public job search, and the agency's terms of use for its portal apply to whatever you do with the results.

### Use cases

#### Scrape Arbeitsagentur jobs by keyword

The plain case: a keyword, a city, a radius. Everything else defaults.

```json
{ "keywords": ["python"], "location": "Berlin", "radiusKm": 25 }
```

#### Daily job alert for Germany

Yesterday's postings only, several occupations in one run, descriptions off so it stays fast.

```json
{ "keywords": ["data engineer", "softwareentwickler"], "publishedSince": 1, "maxItems": 300 }
```

#### Apprenticeships and internships

The board is the main national source for Ausbildung and dual-study places.

```json
{ "keywords": ["fachinformatiker"], "employmentType": "apprenticeship", "location": "Hamburg", "radiusKm": 100 }
```

#### Direct employers only, with full text

Temp-agency postings dominate some occupations; turn them off and read the full posting text.

```json
{ "keywords": ["pflegefachkraft"], "temporaryWork": false, "includeDescription": true, "maxItems": 200 }
```

### Pricing

Pay per event: a small fee per Actor start plus a fee per job returned. Searches, paging, deduplication and filtering are free — you pay only for the rows that end up in your dataset. A daily alert run with 20–50 new matches costs a few cents. Current prices are in the **Pricing** tab.

### Limits and good practice

- The search returns **25 postings per page** and the portal ignores a larger page size, so a 1 000-job run is about 40 search requests. The Actor pauses between requests; keep schedules to a few runs a day rather than one a minute.
- The date filter is applied by the portal in steps — today, 1, 7, 14 and 28 days. A `publishedSince` of 3 is sent as 7 and then narrowed by the Actor itself, and anything above 28 days is filtered by the Actor alone.
- `temporaryWork: false` relies on the portal's own flag, which is coarse. With `includeDescription` on, the Actor additionally drops every posting whose detail record confirms it is temp agency.
- Results are sorted newest-first, which lets a date filter stop paging early instead of walking the whole result set.
- The board also carries some Austrian postings; `country` tells you which (`Germany`, `Austria`).
- Keyword matching is the portal's own German-language search, not a substring match, so it handles compound words and inflections — but English terms find less than German ones.

### Use with AI agents (MCP)

Add this Actor to the [Apify MCP server](https://apify.com/apify/actors-mcp-server) and an agent (Claude, Cursor, any MCP client) can ask for "nursing jobs within 30 km of Leipzig posted this week" and get structured rows with links. Typical tool input:

```json
{ "keywords": ["pflegefachkraft"], "location": "Leipzig", "radiusKm": 30, "publishedSince": 7, "maxItems": 50 }
```

### FAQ

**Why do I get fewer results than the website shows?** `maxItems` and `maxItemsPerQuery` cap the run, and `publishedSince` is applied twice (coarsely by the portal, then exactly by the Actor). Raise the caps for a full sweep.

**Is there an official API I should use instead?** The agency publishes no documented public job-search API. This Actor reads the same public search a browser does, which is why it also accepts a proxy configuration.

**Why is `description` empty?** Descriptions need one extra request per posting, so they are off by default. Set `includeDescription` to `true`.

**Why is there no salary on most rows?** German postings rarely state pay. When an employer does publish it, `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod` are filled.

**Do you collect contact people?** No — see the personal-data section above.

**Something broke or a field is missing?** Open an issue on the Actor's **Issues** tab; the source is a public portal and changes there are fixed quickly.

### Related Actors

- [Remote Jobs Scraper](https://apify.com/lost.fany/remote-jobs-scraper)
- [Career Page Jobs API](https://apify.com/lost.fany/greenhouse-lever-ats-jobs-scraper)
- [Tech Stack Detector](https://apify.com/lost.fany/website-tech-stack-detector)

### Cost control

Pay per event only works if you can see the bill before you pay it:

- Set `dryRun` to count every result and print what a full run would cost - nothing is charged, and only a few sample rows are stored.
- You are charged only for rows actually delivered to the dataset; a run that finds nothing costs nothing beyond the start event.
- `maxItems` is respected exactly, and the run stops before the platform's Max total charge for the run.
- Every run writes a `COST` record to the key-value store: chargeable events, charged events, the amount in USD and whether a limit was hit.

# Actor input Schema

## `keywords` (type: `array`):

Search terms, one search per term. German works best: `python`, `softwareentwickler`, `pflegefachkraft`, `bauleiter`. Results of all terms are merged and deduplicated by posting reference number.

## `location` (type: `string`):

City, postal code or district in Germany, e.g. `Berlin`, `10247`, `München`. Combined with `radiusKm` for a radius search. Leave empty to search all of Germany.

## `radiusKm` (type: `integer`):

Radius around `location` in kilometres. Ignored when no location is given.

## `publishedSince` (type: `integer`):

Only postings published within this many days. The portal filters server-side in steps (today, 1, 7, 14, 28 days); anything beyond 28 days is filtered by this Actor instead. Leave empty for no date filter.

## `employmentType` (type: `string`):

Which kind of offer to search: a regular job, an apprenticeship / dual study place, an internship or traineeship, or self-employment.

## `workingTime` (type: `array`):

Keep only postings with these working-time options. Empty = all.

## `temporaryWork` (type: `boolean`):

Temp-agency postings (Zeitarbeit / Arbeitnehmerüberlassung) make up a large share of the board. Turn off to ask the portal to leave them out; with `includeDescription` on, the Actor also drops every posting the detail record confirms as temp agency.

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

Fetch the full posting text, plus the temp-agency and private-recruiter flags, with one extra request per posting. Off by default because it makes a run noticeably slower.

## `maxItemsPerQuery` (type: `integer`):

Cap per search term, so one broad keyword cannot consume the whole run.

## `maxItems` (type: `integer`):

Stop after this many jobs in total, across all keywords and after deduplication.

## `proxyConfiguration` (type: `object`):

Not needed normally. The source is a public portal with no documented API, so it may start refusing datacenter IPs — enable Apify Proxy (ideally German residential) and the Actor switches to it automatically on an HTTP 403.

## `dryRun` (type: `boolean`):

Count every result and show what a full run would cost, without charging for it. Only a few sample rows are stored. Use it to check the price before you pay.

## Actor input object example

```json
{
  "keywords": [
    "python"
  ],
  "location": "",
  "radiusKm": 25,
  "employmentType": "any",
  "workingTime": [],
  "temporaryWork": true,
  "includeDescription": false,
  "maxItemsPerQuery": 500,
  "maxItems": 1000,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "dryRun": false
}
```

# Actor output Schema

## `results` (type: `string`):

One item per posting: title, employer, location with coordinates, working time, occupation, publication and start date, and the link to the posting on arbeitsagentur.de.

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

Jobs pushed and matched, duplicates dropped, requests made, refused responses, per-keyword counts and the filters used.

# 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 = {
    "keywords": [
        "python"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lost.fany/arbeitsagentur-germany-jobs-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 = { "keywords": ["python"] }

# Run the Actor and wait for it to finish
run = client.actor("lost.fany/arbeitsagentur-germany-jobs-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 '{
  "keywords": [
    "python"
  ]
}' |
apify call lost.fany/arbeitsagentur-germany-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lost.fany/arbeitsagentur-germany-jobs-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/rLSQeXyccHnOn2Ywm/builds/CLbebqGOWmmvM1zvg/openapi.json
