# Arbeitsagentur Jobs in English + Salary & Market Insights (`zerahdata/arbeitsagentur-jobs-insights`) Actor

Official Arbeitsagentur jobs API data: English fields, normalized salary, market-insight summaries and change tracking for German job listings.

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

## Pricing

$0.90 / 1,000 results

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

## Arbeitsagentur Jobs in English + Salary & Market Insights

Search Germany's official Bundesagentur für Arbeit (Arbeitsagentur) job
listings using the **official public Jobsuche REST API** - the same data
source behind arbeitsagentur.de - with English field names and enum values,
normalized and currency-converted salary, a free market-insight summary per
search, and change tracking between scheduled runs. No HTML scraping, no
browser, no CAPTCHAs.

### Why this actor

- **English throughout, not just renamed keys.** Field names are English,
  and so are the values: German state names (`BADEN_WUERTTEMBERG` ->
  "Baden-Württemberg"), contract type, working-time pattern and occupational
  field are all translated, with the original German also kept so nothing is
  lost.
- **Real salary normalization, not a status code.** Structured salary
  fields (hourly/monthly/yearly) are annualized to a common EUR figure, and
  salary is additionally mined from free-text descriptions (e.g. "72.600 €",
  "ab 3.500 € brutto/Monat", "18,50 €/Std", "TVöD E9") when no structured
  figure is given - most competing tools only pass through the raw German
  "no data" status and stop there.
- **Your choice of currency.** Annualized salary is optionally converted to
  USD, GBP, CHF or ILS using the European Central Bank's own daily reference
  rate, alongside the original EUR figures.
- **Free market-insight summary per search.** Every search also returns a
  free summary row: total market size, top employers/cities/occupational
  fields, remote/hybrid share, contract-type mix, salary transparency and
  distribution, and plain-English insight bullets - not just a pile of rows.
- **New/removed job tracking.** Turn on change tracking and every run after
  the first reports which jobs are new, which disappeared, and how the total
  market size moved - a ready-made job-market monitor for a schedule.
- **Full description at the same price.** The complete job description
  (as markdown or plain text) is included on every job row, at no extra
  cost.
- **Official API, not scraping.** This actor only calls the Bundesagentur
  für Arbeit's own public Jobsuche REST API (the same one powering
  arbeitsagentur.de) - no browser, no CAPTCHA fights, no runs that silently
  break when the website's markup changes.
- **Apprenticeships and internships included.** Search regular jobs,
  apprenticeships (Ausbildung), internships/trainee programs, or
  self-employment listings - not just full-time employment.
- **Optional translation.** Bring your own DeepL or Google Cloud Translation
  API key and get the title and description translated into English or any
  of ten other languages, alongside the original German.

### What it does

For each keyword/location search, the actor:

1. Calls the official Jobsuche search API with your filters (offer type,
   contract type, working time, posting age, employer, and more).
2. Pages through results up to your requested maximum, automatically
   splitting very large searches by city to reach further than the API's
   own single-query depth limit.
3. Optionally fetches the full job-detail record for every listing
   (description, partner site, more).
4. Normalizes every field into English, parses/annualizes salary, and
   converts it to your chosen currency.
5. Optionally translates the title and description with your own
   translation API key.
6. Optionally compares this run to the last run with the same monitor name
   and reports what changed.
7. Writes a free per-search market-insight summary and a `RUN_REPORT`
   self-check to the key-value store.

### Use cases

- **Job-board aggregation** - pull normalized, English-language German job
  listings into your own site or app, with salary already annualized.
- **Labor-market research** - the free summary's salary distribution,
  remote-work share and occupational-field breakdown gives a quick read on
  hiring demand for a role or region without building your own analytics.
- **Recruiter/competitor intelligence** - search by employer or occupation
  to see what a company or sector is hiring for, and get alerted to new
  postings with change tracking on a schedule.
- **Salary benchmarking** - aggregate annualized, currency-converted salary
  figures across many postings for a role to estimate a market rate.
- **Relocation/job search tooling** - apprenticeship, internship and
  self-employment listings are searchable alongside regular jobs, with
  English field names throughout.

### How to use

1. Add one or more **Keywords (job titles)** and a **Location** (or leave
   Location empty to search all of Germany).
2. (Optional) Set **Offer type**, **Contract type**, **Working time**,
   **Posted within (days)**, and other filters.
3. (Optional) Turn on **Fetch full job details** (on by default), set a
   **Max results per search**, and pick a **Convert salary to currency**.
4. (Optional) Turn on translation with your own DeepL/Google key, or
   **Track new/removed jobs between runs**.
5. Run the actor.

#### Example input

```json
{
  "keywords": ["Softwareentwickler"],
  "location": "Berlin",
  "radiusKm": 25,
  "offerType": "job",
  "maxResults": 50,
  "includeDetails": true,
  "currency": "USD"
}
```

#### Example input with translation + monitoring

```json
{
  "keywords": ["Pflegefachkraft"],
  "location": "München",
  "maxResults": 150,
  "postedWithinDays": 14,
  "translateTo": "en",
  "translationProvider": "deepl",
  "translationApiKey": "YOUR_DEEPL_KEY",
  "trackChanges": true,
  "monitorName": "munich-nursing-jobs"
}
```

Run this on a schedule. The first run saves a baseline and reports no
changes; every run after that reports what's new and what disappeared.

#### Advanced: multiple keyword/location pairs

Instead of one keyword list + one location, use **Advanced: keyword |
location pairs** to run several independent searches in one go:

```json
{
  "searches": ["Softwareentwickler | Berlin", "Pflegefachkraft | München", "Koch | Hamburg"]
}
```

### Output example

A job row (charged):

```json
{
  "type": "job",
  "referenceNumber": "10001-1003891446-S",
  "title": "Softwareentwicklerin (w/m/d)",
  "occupation": "Softwareentwickler/in",
  "occupationEn": "Software Developer",
  "occupationalFieldEn": "Software development and programming",
  "employer": "GASAG AG",
  "offerType": "job",
  "contractType": "fixedTerm",
  "fixedTermMonths": 24,
  "workingTime": ["fullTime"],
  "remote": { "possible": true, "type": "percent", "percent": 50 },
  "remoteLabel": "hybrid 50%",
  "primaryCity": "Berlin",
  "primaryState": "Berlin",
  "salary": {
    "amount": 72600,
    "period": "year",
    "annualEur": 72600,
    "source": "structured",
    "converted": { "amount": 81224.88, "currency": "USD", "rateDate": "2026-10-26" }
  },
  "jobUrl": "https://www.arbeitsagentur.de/jobsuche/jobdetail/10001-1003891446-S",
  "description": "Wir suchen dich an unserem Standort in Berlin-Schöneberg...",
  "detailStatus": "OK",
  "searchKeyword": "Softwareentwickler",
  "searchLocation": "Berlin",
  "scrapedAt": "2026-10-26T20:07:33.186Z"
}
```

A free market-insight summary row (one per search):

```json
{
  "type": "summary",
  "searchKeyword": "Softwareentwickler",
  "searchLocation": "Berlin",
  "totalAvailable": 187,
  "fetched": 20,
  "topEmployers": [{ "name": "PerZukunft Arbeitsvermittlung GmbH & Co. KG", "count": 4 }],
  "topCities": [{ "name": "Berlin", "count": 16 }],
  "topOccupationalFields": [{ "name": "Software development and programming", "count": 160 }],
  "remoteShare": 20,
  "salaryTransparency": 55,
  "salaryStats": { "n": 11, "minEur": 2950, "medianEur": 60000, "p75Eur": 65000, "maxEur": 72600 },
  "insights": [
    "55% of 20 fetched postings state a salary; median EUR 60000/yr.",
    "20% allow remote or hybrid work.",
    "187 matching job(s) are currently available in total for this search."
  ],
  "generatedAt": "2026-10-26T20:07:33.219Z"
}
```

A free change row (one per search, when **Track new/removed jobs between
runs** is on):

```json
{
  "type": "change",
  "searchKeyword": "Data Scientist",
  "searchLocation": "Hamburg",
  "isFirstRun": false,
  "newJobsCount": 2,
  "newJobsExamples": [{ "referenceNumber": "...", "title": "...", "employer": "..." }],
  "removedJobsCount": 0,
  "totalAvailableDelta": { "old": 16, "new": 18, "change": 2 },
  "generatedAt": "2026-10-26T20:14:33.718Z"
}
```

A zero-result search produces an `info` row (free, never charged), not an
error:

```json
{
  "type": "info",
  "searchKeyword": "Softwareentwickler",
  "searchLocation": "Xyzzyville",
  "error": "NO_RESULTS",
  "message": "No jobs matched keyword \"Softwareentwickler\" in location \"Xyzzyville\"."
}
```

### Change tracking (monitoring mode)

Enable **Track new/removed jobs between runs** (`trackChanges: true`) to
turn repeated runs into a monitor. The actor keeps a compact per-search
state (reference numbers seen, total market size) in a named key-value
store, `arbeitsagentur-jobs-monitor-<monitorName>`, and compares it to the
new run.

- **Monitor name**: give runs that should be compared to each other the
  same **Monitor name**. Leave it empty to use `"default"`.
- **First run**: there is nothing to compare against yet, so the baseline is
  saved and a `change` row with `isFirstRun: true` is reported - this is
  expected.
- Every job row also gets an `isNew` flag once a baseline exists.

### Run report (self-check)

Every run writes a `RUN_REPORT` record to the key-value store with: inputs
processed, row counts by type, errors by code, warnings, salary-parse rate,
detail-fetch failure rate, and duplicate-reference-number checks.

### Pricing

This actor uses **pay-per-result** pricing: **$0.90 per 1,000 job rows**, no
start fee. Summary, change and info rows are always free. Error rows are
never charged.

### FAQ

**Is this legal?** Yes. The actor only calls the Bundesagentur für Arbeit's
own public Jobsuche REST API - the same public job data available on
arbeitsagentur.de - and does not scrape HTML, solve CAPTCHAs, or collect
recruiter personal contact data. If you process any personal data contained
in job postings further, you are responsible for your own GDPR compliance.

**Why do some jobs have `salary: null`?** Most German job postings do not
state a wage at all - this is normal, not a failure. The free summary row's
`salaryTransparency` tells you what share of a given search actually states
a figure.

**What's the deepest I can page into one search?** The official API caps
any single filter combination at about 10,000 reachable results. When a
search matches more than that and you ask for more, the actor automatically
splits the search by city (from the search's own location facet) to reach
further, and reports in `RUN_REPORT`/warnings how many unique jobs were
ultimately reachable.

**Does "Home office possible" filter on the server?** The official API has
no server-side home-office filter, so this option is applied after
fetching by checking each listing's own remote-work flag - this can return
fewer rows than **Max results per search** asks for, and the actor warns
when this happens.

**What happens if the translation/currency-conversion service is
unavailable?** The run still completes. Rows keep their original German
text (with a warning) if translation fails, and salary stays un-converted
(with a warning) if the European Central Bank's rate feed is unreachable.

**What other errors can show up?** `API_ERROR` (the search/detail API
returned an unexpected error), `RATE_LIMITED` (too many requests - the
actor already retries with backoff before giving up), `DETAIL_FAILED` (full
details could not be fetched for some jobs in a search - those rows are
still included, with `detailStatus: "FAILED"`), and `SPENDING_LIMIT_REACHED`
(your run's spending cap was hit before all searches finished).

**What are the limits?** `employer` must match the official employer name
exactly (no fuzzy matching). Whether a listing is from a temporary-work or
private placement agency can be filtered on but is not exposed as a
per-row field by the official API. Translation and currency conversion need
your own DeepL/Google key and an available ECB rate feed, respectively.

# Actor input Schema

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

One or more German job titles/keywords to search for (searched against the official "was" field). Example: "Softwareentwickler".

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

A German city, postal code or region to search around (the official "wo" field). Leave empty to search all of Germany.

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

Search radius around "Location" in kilometers. Ignored when Location is empty.

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

Optional. Advanced alternative to Keywords/Location: a list of "keyword | location" pairs, one search per line, e.g. "Pflegefachkraft | München". When non-empty, this replaces Keywords/Location and each pair uses the same Radius (km)/filters below. Leave empty to just use Keywords + Location.

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

Type of listing to search for (the official "angebotsart" field).

## `contractType` (type: `string`):

Filter by contract duration (the official "befristung" field).

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

Filter by working-time pattern (the official "arbeitszeit" field). Note: the official API has no server-side home-office filter, so "Home office possible" is applied after fetching, by checking each listing's remote-work flag.

## `postedWithinDays` (type: `integer`):

Optional. Only return listings published within this many days (the official "veroeffentlichtseit" field, 0-100). Leave empty for no limit.

## `excludeTemporaryAgencies` (type: `boolean`):

When on, excludes listings from temporary work agencies (Zeitarbeit, "zeitarbeit=false").

## `excludePrivateAgencies` (type: `boolean`):

When on, excludes listings placed via private placement agencies ("pav=false").

## `employer` (type: `string`):

Optional. Filter to one employer. Must match the official employer name exactly as Arbeitsagentur stores it (e.g. "Deutsche Bahn AG"), not a partial/fuzzy name.

## `maxResults` (type: `integer`):

Maximum number of job listings to fetch for each search (each keyword+location pair, or each row of the advanced "searches" list). The official API caps any single search at 10,000 matching results; this actor automatically splits large searches into date-window batches to reach higher totals where possible.

## `includeDetails` (type: `boolean`):

When on (default), fetches the full description and extra fields for every listing from the job-details endpoint. Turn off for a faster, lighter run using only search-result fields (no description, no detailed salary parsing from text).

## `descriptionFormat` (type: `string`):

Whether to keep the job description as markdown (as published) or convert it to plain text.

## `translateTo` (type: `string`):

Optional. Translates the job title and description into this language using your own DeepL or Google Cloud Translation API key. Leave as "none" to skip translation (default).

## `translationProvider` (type: `string`):

Which translation API to call. Ignored when "Translate title/description to" is "none".

## `translationApiKey` (type: `string`):

Your own DeepL or Google Cloud Translation API key. Never logged or included in output. Ignored when "Translate title/description to" is "none".

## `currency` (type: `string`):

Annualized salary figures are additionally converted to this currency using the European Central Bank's daily reference rate (fetched once per run). Original EUR figures are always kept.

## `trackChanges` (type: `boolean`):

When enabled, the actor remembers what it saw last time (in a named key-value store) and every run after the first emits a free "change" row: new listings, listings no longer available, and how the total market size moved. Combine with a schedule for ongoing monitoring.

## `monitorName` (type: `string`):

Name of the key-value store used to persist state when "Track new/removed jobs" is on, stored as "arbeitsagentur-jobs-monitor-<sanitized name>". Use the same name across scheduled runs of the same search; use different names to keep multiple monitors independent. Leave empty to use "default".

## `outputMode` (type: `string`):

"all" (default): job, summary and change rows. "jobsOnly": only job rows (no summary/change rows). "summaryOnly": only summary/change rows, no individual job rows (cheapest - summary/change rows are always free).

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

Optional. Not required to call the official Jobsuche API, but can help if you run many searches from a shared environment.

## Actor input object example

```json
{
  "keywords": [
    "Softwareentwickler"
  ],
  "location": "Berlin",
  "radiusKm": 25,
  "searches": [],
  "offerType": "job",
  "contractType": "any",
  "workingTime": [],
  "excludeTemporaryAgencies": false,
  "excludePrivateAgencies": false,
  "employer": "",
  "maxResults": 20,
  "includeDetails": true,
  "descriptionFormat": "text",
  "translateTo": "none",
  "translationProvider": "deepl",
  "currency": "EUR",
  "trackChanges": false,
  "monitorName": "",
  "outputMode": "all",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

All job rows, plus summary, change, info and error rows.

## `runReport` (type: `string`):

Per-run summary written to the key-value store: inputs processed, row counts by type, errors by code, warnings, and output validation.

# 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": [
        "Softwareentwickler"
    ],
    "location": "Berlin",
    "searches": [],
    "workingTime": [],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zerahdata/arbeitsagentur-jobs-insights").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": ["Softwareentwickler"],
    "location": "Berlin",
    "searches": [],
    "workingTime": [],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("zerahdata/arbeitsagentur-jobs-insights").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": [
    "Softwareentwickler"
  ],
  "location": "Berlin",
  "searches": [],
  "workingTime": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call zerahdata/arbeitsagentur-jobs-insights --silent --output-dataset

```

## MCP server setup

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

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/XUUsh2frjlWBXNjej/builds/Pa6l1WKxwLynfLqZO/openapi.json
