# Arbeitsagentur Jobs Monitor – New German Job Listings (`neverempty/arbeitsagentur-jobs-monitor`) Actor

Search the job board of the German Federal Employment Agency (Bundesagentur für Arbeit) by keyword, place, radius, offer type, working time and contract. One row per job: title, employer, location, salary, dates, links. Monitoring mode returns only new jobs.

- **URL**: https://apify.com/neverempty/arbeitsagentur-jobs-monitor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.99 / 1,000 job returneds

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 Monitor – New German Job Listings

Search the job board of the German Federal Employment Agency (Bundesagentur für Arbeit, "Jobsuche") and get **one row per job**: title, employer, occupation, place with coordinates, working time, contract, salary when the listing gives one, publication dates, the job page and the external apply link.

Turn on **monitoring mode**, put the run on a schedule, and each run returns **only the jobs that were not there before**. You do not download the same thousand listings every hour and compare them yourself.

- No login, no browser, no proxy needed. The Actor reads the same public search interface that the agency's own job search page uses.
- Search terms are German, exactly as on the job board: `Pflegefachkraft`, `Softwareentwickler`, `Lagerhelfer`, `Erzieher`, `Elektroniker`, `Kaufmann für Büromanagement`.
- A run that returns nothing always says why, in a row that is not charged.

### What you can search by

| Input | Meaning | Example |
|---|---|---|
| `what` | Job title, occupation or keyword (the board's "was") | `Pflegefachkraft` |
| `where` | City or five-digit postal code (the board's "wo") | `Berlin`, `München`, `10115` |
| `radiusKm` | Distance around the place, 0 to 200 km | `25` |
| `offerType` | `job`, `apprenticeship`, `internship-trainee`, `self-employment` | `apprenticeship` |
| `workingTime` | `full-time`, `part-time`, `shift-night-weekend`, `mini-job` | `part-time` |
| `contract` | `permanent` or `fixed-term` | `permanent` |
| `publishedWithinDays` | Only jobs published in the last N days: `0`, `1`, `7`, `14` or `28` | `7` |
| `maxJobs` | Stop after this many rows (default 100, up to 10000) | `500` |
| `monitoringMode` | Return only new jobs since the previous run of the same search | `true` |

Every filter is applied by the job board itself. Each one was checked against the returned rows on 2026-10-05 (for example: `offerType: apprenticeship` returned 50 of 50 rows of type `AUSBILDUNG`; `contract: fixed-term` returned 50 of 50 rows marked `BEFRISTET`).

#### Example: nursing jobs within 25 km of Berlin

```json
{
    "what": "Pflegefachkraft",
    "where": "Berlin",
    "radiusKm": 25,
    "maxJobs": 100
}
```

#### Example: watch for new software developer jobs around Munich

```json
{
    "what": "Softwareentwickler",
    "where": "München",
    "radiusKm": 50,
    "monitoringMode": true
}
```

Schedule this run (for example every hour). The first run only remembers what is listed. Every later run returns the new jobs.

### What a row looks like

```json
{
    "status": "ok",
    "refnr": "12618-177311-S",
    "title": "Pflegefachkraft in der stationären Pflege",
    "employer": "AWO Pflege Schleswig-Holstein gGmbH",
    "occupation": "Pflegefachmann/-frau (Ausbildung)",
    "allOccupations": ["Pflegefachmann/-frau (Ausbildung)"],
    "offerTypeCode": "ARBEIT",
    "city": "Lübeck",
    "postalCode": "23562",
    "region": "SCHLESWIG_HOLSTEIN",
    "country": "DEUTSCHLAND",
    "latitude": 53.8306119,
    "longitude": 10.7169372,
    "locationsCount": 1,
    "locations": [{ "city": "Lübeck", "postalCode": "23562", "region": "SCHLESWIG_HOLSTEIN", "country": "DEUTSCHLAND", "latitude": 53.8306119, "longitude": 10.7169372 }],
    "fullTime": false,
    "partTime": true,
    "shiftNightWeekend": true,
    "miniJob": false,
    "homeOfficePossible": false,
    "careerChangersWelcome": false,
    "contract": "permanent",
    "contractCode": "UNBEFRISTET",
    "fixedTermMonths": null,
    "fixedTermUntil": null,
    "salaryMin": 40000,
    "salaryMax": 50000,
    "salaryPeriod": "year",
    "salaryCode": "JAHRESGEHALT",
    "startDate": "2026-10-04",
    "firstPublishedDate": "2026-10-04",
    "publishedFrom": "2026-10-04",
    "modifiedAtLocal": "2026-10-04T16:45:40.618",
    "jobUrl": "https://www.arbeitsagentur.de/jobsuche/jobdetail/12618-177311-S",
    "externalUrl": "https://www.awo-jobs.de/index.php?id=114&uid=177311",
    "searchedWhat": "Pflege",
    "searchedWhere": null,
    "resolvedPlace": null,
    "scrapedAt": "2026-10-05T09:00:00.000Z"
}
```

Notes on the columns:

- **A value the listing does not give is `null`.** Nothing is filled with 0, `false` or an empty string. `fullTime`, `partTime`, `homeOfficePossible` and the other yes/no columns are `null` when the listing does not say.
- **Salary** is what the employer entered: a range (`salaryMin` to `salaryMax`) or one fixed amount (then both are the same). `salaryPeriod` is `year` or `hour`; other ways of stating pay keep the board's own code in `salaryCode` and leave `salaryPeriod` empty. The board does not send a currency. Most listings give no salary: in 500 listings for "Pflege" read on 2026-10-05, 134 carried a figure.
- **`offerTypeCode`, `contractCode`, `salaryCode`, `region`, `country`** are the board's own German codes, unchanged (`ARBEIT`, `AUSBILDUNG`, `PRAKTIKUM_TRAINEE`, `SELBSTAENDIGKEIT`; `UNBEFRISTET`, `BEFRISTET`, `KEINE_ANGABE`).
- **`jobUrl`** is the job's page on arbeitsagentur.de. **`externalUrl`** is the employer's or partner board's page when the listing has one (183 of the same 500).
- **`modifiedAtLocal`** is the board's timestamp as sent, without a time zone (German local time).
- **`resolvedPlace`** is how the board understood `where`. `locations` lists every workplace of a job with several (the first one fills `city`, `postalCode` and the coordinates).
- In monitoring mode each row also has `change: "new-job"` and `previousCheckedAt`.

Not in the rows, on purpose: street addresses, contact persons, phone numbers, e-mail addresses and the full description text. They are not part of the search result list, and the Actor does not open the detail and contact pages (some of them sit behind a CAPTCHA, which this Actor never tries to pass).

### Monitoring mode

1. **First run of a search**: the jobs published today and yesterday are remembered. No job row is returned, nothing is charged. The dataset holds one `baseline-saved` row that says so.
2. **Every later run of the same search**: the Actor asks the board only for recently published jobs (the last 1, 7, 14 or 28 days, whichever covers the time since the previous completed run), compares them with what it remembers and returns the ones it has not seen. Each is charged as one `job-returned`.
3. **A run that finds nothing new** returns one `no-new-jobs` row and is charged one `search-checked` event.

Details that matter:

- The memory is kept per search (`what`, `where`, `radiusKm`, `offerType`, `workingTime`, `contract`). A different search has its own memory. `maxJobs` and `publishedWithinDays` are not used in monitoring mode; a monitoring run returns every new job.
- "New" means: published on or after the day before the first monitoring run, and not seen at an earlier run. Older listings are never reported as new. A job that an employer takes down and publishes again under a new reference number is a new job.
- If the maximum total charge of a run is too small for all new jobs, the rest are not remembered and come back at the next run.
- If the board cannot be read, nothing is charged and nothing is remembered; the next run looks at the same days again.
- Do not schedule two runs of the same search at the same moment: the memory is a key-value store without locking, and one run could overwrite the other's record.
- `resetMonitoringState: true` forgets the memory of this one search.

### Rows that are not jobs (never charged)

| `status` | When |
|---|---|
| `no-jobs` | The board says the search holds 0 jobs |
| `no-new-jobs` | Monitoring: nothing new since the previous run |
| `baseline-saved` | Monitoring: first run of this search |
| `no-such-location` | The board does not know the place in `where` |
| `invalid-input` | An input value could not be used; nothing was read |
| `source-unreadable` / `source-blocked` | The board did not answer, answered in an unknown format, or refused (HTTP 403/429) |
| `partly-read` | A later page could not be read; the jobs read before it are returned |
| `limit-reached` | The search holds more than the 10,000 jobs the board lets anyone page through |
| `budget-reached` / `not-read` | The maximum total charge of the run was reached |

### Limits you should know

- **`publishedWithinDays` takes only 0, 1, 7, 14 or 28.** Measured on 2026-10-05: the board silently ignores any other number (2, 3, 5, 10, 30, 60 and 100 all returned the full unfiltered result), so the Actor rejects other values instead of returning rows you did not ask for.
- **10,000 jobs per search.** The board refuses to page beyond that (HTTP 400). Narrow the search by place, radius, offer type or days to reach everything.
- **The board's order is by relevance, not by date**, and it can shift a little between pages. The Actor removes a job that shows up on two pages; a job that slips between two pages can be missing from a large one-off run. Monitoring runs look at overlapping days, so such a job is picked up at the next run.
- **Search terms are matched by the board.** The Actor does not re-filter the results; what the agency's own search returns for your words is what you get.
- The Actor sends one request at a time with a pause between requests, and does not retry a refusal.

### Pricing

- `job-returned`: one per job row.
- `search-checked`: monitoring mode only, one per run that read the search completely and found no new job.
- No start fee. Rows that explain an empty or partial result are free. The first monitoring run of a search is free.

### Data source

Public job search of the Bundesagentur für Arbeit (arbeitsagentur.de). This Actor is not affiliated with the agency.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `what` (type: `string`):

What to search for, in German, as on the job board: a job title, an occupation or a keyword. Examples: Pflegefachkraft, Softwareentwickler, Lagerhelfer, Erzieher, Elektroniker. Leave empty to search every job in the chosen place.

## `where` (type: `string`):

A German city or a five-digit postal code. Examples: Berlin, München, Köln, 10115. Leave empty to search all of Germany. A place the job board does not know comes back as a no-such-location row and is not charged.

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

Distance around the place, in kilometres (0 to 200). Needs a place. Leave empty to use the default of the job board.

## `offerType` (type: `string`):

Only offers of this type. Leave empty for all types.

## `workingTime` (type: `string`):

Only jobs offered with this working time. Leave empty for all.

## `contract` (type: `string`):

Only jobs with this kind of contract. Jobs that do not say are left out when this is set. Leave empty for all.

## `publishedWithinDays` (type: `integer`):

Only jobs published in the last N days. The job board accepts 0 (today), 1, 7, 14 or 28 and silently ignores any other number, so only these five are allowed here. Leave empty for all listed jobs. Not used in monitoring mode, which works out the days from the previous run.

## `maxJobs` (type: `integer`):

Stop after this many job rows (1 to 10000). The job board lets anyone page through the first 10,000 jobs of a search. Not used in monitoring mode: a monitoring run returns every new job.

## `monitoringMode` (type: `boolean`):

Run the same search again and again (for example every hour on a schedule) and get only jobs that were not there before. The first run of a search only remembers the jobs published today and yesterday: it returns no job row and is free. Later runs return one row per new job. A run that finds no new job is charged one search-checked event instead.

## `resetMonitoringState` (type: `boolean`):

Forget what was remembered for this search before the run. The run then behaves like a first monitoring run.

## Actor input object example

```json
{
  "what": "Pflegefachkraft",
  "where": "Berlin",
  "radiusKm": 25,
  "maxJobs": 100,
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per job found by the search on the job board of the German Federal Employment Agency: title, employer, occupation, place, working time, contract, salary as given, dates, the job page and the external apply link. Searches that could not be read, unknown places, runs with no job or no new job, and runs that hit a limit come back as their own rows and are not charged.

# 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 = {
    "what": "Pflegefachkraft",
    "where": "Berlin",
    "radiusKm": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/arbeitsagentur-jobs-monitor").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 = {
    "what": "Pflegefachkraft",
    "where": "Berlin",
    "radiusKm": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/arbeitsagentur-jobs-monitor").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 '{
  "what": "Pflegefachkraft",
  "where": "Berlin",
  "radiusKm": 25
}' |
apify call neverempty/arbeitsagentur-jobs-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/arbeitsagentur-jobs-monitor"
        }
    }
}
```

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/oLymP59JxGeTaYqfB/builds/MAhBgs8LC9cheajVY/openapi.json
